From cfc5d7784c556856ab31538dbb6392f07a40dece Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 21 Jan 2021 21:44:00 -0500 Subject: [PATCH 0001/1276] Resolved [#2479] v1 Create Company Issue (#2512) - Resolved issue in creating a v1 company - issue was related to None value for signing entity name when creating record - Updated logging and types for request_corporate_signature Signed-off-by: David Deal --- cla-backend/cla/controllers/company.py | 10 +++++----- cla-backend/cla/controllers/signing.py | 18 +++++++++--------- cla-backend/cla/models/docusign_models.py | 23 +++++++++++++---------- cla-backend/cla/routes.py | 13 +++++++------ 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/cla-backend/cla/controllers/company.py b/cla-backend/cla/controllers/company.py index 1f9424d78..6535eddf2 100644 --- a/cla-backend/cla/controllers/company.py +++ b/cla-backend/cla/controllers/company.py @@ -83,10 +83,10 @@ def get_company(company_id: str): def create_company(auth_user: AuthUser, company_name: str = None, signing_entity_name: str = None, - company_manager_id=None, - company_manager_user_name=None, - company_manager_user_email=None, - user_id=None, + company_manager_id: str = None, + company_manager_user_name: str = None, + company_manager_user_email: str = None, + user_id: str = None, response=None): """ Creates an company and returns the newly created company in dict format. @@ -118,7 +118,7 @@ def create_company(auth_user: AuthUser, "company_id": company.get("company_id")} } - cla.log.debug(f'{fn} - creating company with name: {company_name}') + cla.log.debug(f'{fn} - creating company with name: {company_name} with signing entity name: {signing_entity_name}') company = Company() company.set_company_id(str(uuid.uuid4())) company.set_company_name(company_name) diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index f283bf6a4..4cd846b3d 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -45,14 +45,14 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur def request_corporate_signature(auth_user, - project_id, - company_id, + project_id: str, + company_id: str, signing_entity_name: str = None, - send_as_email=False, - authority_name=None, - authority_email=None, - return_url_type=None, - return_url=None): + send_as_email: bool = False, + authority_name: str = None, + authority_email: str = None, + return_url_type: str = None, + return_url: str = None): """ Creates CCLA signature object that represents a company signing a CCLA. @@ -80,8 +80,8 @@ def request_corporate_signature(auth_user, """ return get_signing_service().request_corporate_signature( auth_user=auth_user, - project_id=str(project_id), - company_id=str(company_id), + project_id=project_id, + company_id=company_id, signing_entity_name=signing_entity_name, send_as_email=send_as_email, signatory_name=authority_name, diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index e0b67edd6..c712a7dd5 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -780,14 +780,14 @@ def handle_signing_new_corporate_signature(self, signature, project, company, us return response_model def request_corporate_signature(self, auth_user: object, - project_id: object, - company_id: object, + project_id: str, + company_id: str, signing_entity_name: str = None, - send_as_email: object = False, - signatory_name: object = None, - signatory_email: object = None, - return_url_type: object = None, - return_url: object = None) -> object: + send_as_email: bool = False, + signatory_name: str = None, + signatory_email: str = None, + return_url_type: str = None, + return_url: str = None) -> object: fn = 'models.docusign_models.request_corporate_signature' cla.log.debug(f'{fn} - ' @@ -820,7 +820,8 @@ def request_corporate_signature(self, auth_user: object, cla.log.debug(f'{fn} - loading user {auth_user.username}') users_list = User().get_user_by_username(auth_user.username) if users_list is None: - cla.log.debug(f'{fn} - unable to load auth_user by username: {auth_user.username} from the EasyCLA database.') + cla.log.debug(f'{fn} - unable to load auth_user by username: {auth_user.username} ' + 'from the EasyCLA database.') # Lookup user in the platform user service... us = UserService # If found, create user record in our EasyCLA database @@ -831,7 +832,8 @@ def request_corporate_signature(self, auth_user: object, 'Returning an error response') return {'errors': {'user_error': 'user does not exist'}} if len(platform_users) > 1: - cla.log.warning(f'{fn} - more than one user with same username: {auth_user.username} - using first record.') + cla.log.warning(f'{fn} - more than one user with same username: {auth_user.username} - ' + 'using first record.') # Grab the first user from the list - should only be one that matches the search query parameters platform_user = platform_users[0] @@ -980,7 +982,8 @@ def request_corporate_signature(self, auth_user: object, signatory_name=signatory_name, signatory_email=signatory_email, send_as_email=send_as_email, return_url_type=return_url_type, return_url=return_url) - cla.log.debug(f'{fn} - Previous unsigned CCLA signatures on file for project: {project_id}, company: {company_id}') + cla.log.debug(f'{fn} - Previous unsigned CCLA signatures on file for project: {project_id},' + f'company: {company_id}') # TODO: should I delete all but one? return self.handle_signing_new_corporate_signature( signature=signatures[0], project=project, company=company, user=cla_manager_user, diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 351e1ba1c..5a3f371d8 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -667,6 +667,7 @@ def post_company( auth_user, company_name=company_name, company_manager_id=company_manager_id, + signing_entity_name=company_name, company_manager_user_name=company_manager_user_name, company_manager_user_email=company_manager_user_email, response=response, @@ -1264,14 +1265,14 @@ def request_corporate_signature( # staff_verify(user) or company_manager_verify(user, company_id) return cla.controllers.signing.request_corporate_signature( auth_user=auth_user, - project_id=project_id, - company_id=company_id, + project_id=str(project_id), + company_id=str(company_id), signing_entity_name=signing_entity_name, send_as_email=send_as_email, - authority_name=authority_name, - authority_email=authority_email, - return_url_type=return_url_type, - return_url=return_url, + authority_name=str(authority_name), + authority_email=str(authority_email), + return_url_type=str(return_url_type), + return_url=str(return_url), ) From 363ea8db92c427d5bb130c0c6728012c0cf8c4bf Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 22 Jan 2021 18:41:35 +0300 Subject: [PATCH 0002/1276] [#2492] Bug/GitHub Redirect (#2513) - Added # to GitHub redirect sign URL when appending version Signed-off-by: wanyaland --- cla-backend/cla/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index fa2b71628..78079d689 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -778,7 +778,7 @@ def get_full_sign_url(repository_service, installation_id, github_repository_id, :type project_version: string """ - base_url = '{}/v2/repository-provider/{}/sign/{}/{}/{}'.format(cla.conf['API_BASE_URL'], repository_service, + base_url = '{}/v2/repository-provider/{}/sign/{}/{}/{}/#/'.format(cla.conf['API_BASE_URL'], repository_service, str(installation_id), str(github_repository_id), str(change_request_id)) From 1dbb528de88ee1ccbca25b6d886883548f5af031 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 22 Jan 2021 22:01:35 +0300 Subject: [PATCH 0003/1276] [#2462] Feature/ Domain lookup (#2514) - Leveraged org lookup (org-service) when updating/creating organizatin based on domain Signed-off-by: wanyaland --- cla-backend-go/v2/company/service.go | 2 +- .../v2/organization-service/client.go | 37 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 62776c054..498db2319 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1332,7 +1332,7 @@ func (s *service) GetCompanyLookup(ctx context.Context, orgName string, websiteN } orgClient := orgService.GetClient() log.WithFields(f).Debug("Looking up organization by name and website") - org, err := orgClient.SearchOrgLookup(orgName, websiteName) + org, err := orgClient.SearchOrgLookup(ctx, &orgName, &websiteName) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to lookup organization by name or website") return nil, err diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index 39867f6f8..2d62e74a3 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -543,16 +543,21 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName signingEntityName = companyName } - // Search for an existing record by website - existingRecords, lookupErr := osc.SearchOrganization(ctx, "", companyWebsite, "") + //Lookup Org based on domain + lookupOrg, lookupErr := osc.SearchOrgLookup(ctx, nil, &companyWebsite) if lookupErr != nil { log.WithFields(f).WithError(lookupErr).Warn("unable to search for existing company using company website value") return nil, lookupErr } - // If we have an existing record... should only be one record if any - if len(existingRecords) > 0 { - updatedModel, updateErr := osc.UpdateOrg(ctx, existingRecords[0], signingEntityName) + if lookupOrg.Payload.ID != "" { + // Get org based on ID + existingOrg, existingOrgErr := osc.GetOrganization(ctx, lookupOrg.Payload.ID) + if existingOrgErr != nil { + log.WithFields(f).WithError(existingOrgErr).Warnf("unable to get organization : %s ", lookupOrg.Payload.ID) + return nil, existingOrgErr + } + updatedModel, updateErr := osc.UpdateOrg(ctx, existingOrg, signingEntityName) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warn("unable to update for existing company") return nil, updateErr @@ -685,12 +690,17 @@ func (osc *Client) ListOrg(ctx context.Context, orgName string) (*models.Organiz } // SearchOrgLookup returns organization -func (osc *Client) SearchOrgLookup(orgName string, websiteName string) (*organizations.LookupOK, error) { +func (osc *Client) SearchOrgLookup(ctx context.Context, orgName, websiteName *string) (*organizations.LookupOK, error) { f := logrus.Fields{ "functionName": "organization_service.Lookup", - "orgName": orgName, - "websiteName": websiteName, } + if orgName != nil { + f["orgName"] = *orgName + } + if websiteName != nil { + f["websiteName"] = *websiteName + } + tok, err := token.GetToken() if err != nil { log.WithFields(f).WithError(err).Warn("unable to fetch token") @@ -699,9 +709,13 @@ func (osc *Client) SearchOrgLookup(orgName string, websiteName string) (*organiz clientAuth := runtimeClient.BearerToken(tok) params := &organizations.LookupParams{ - Name: aws.String(orgName), - Domain: aws.String(websiteName), - Context: context.TODO(), + Context: ctx, + } + if orgName != nil { + params.Name = orgName + } + if websiteName != nil { + params.Domain = websiteName } result, err := osc.cl.Organizations.Lookup(params, clientAuth) if err != nil { @@ -710,4 +724,5 @@ func (osc *Client) SearchOrgLookup(orgName string, websiteName string) (*organiz } return result, nil + } From 9b45372bd42133ebcbdd0d12f3200183771cfab2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 22 Jan 2021 18:22:22 -0500 Subject: [PATCH 0004/1276] Updated Permissions Checks for Create CLA Group (#2516) - Updated checks - check both foundation and project SFID against permissions model - Added context and debug to common permission check functions Signed-off-by: David Deal --- .../utils/utils_user_auth_lambda.go | 57 +++++++++++++++-- .../utils/utils_user_auth_standalone.go | 64 +++++++++++++++---- cla-backend-go/v2/cla_groups/handlers.go | 60 ++++++++++------- cla-backend-go/v2/company/handlers.go | 8 +-- cla-backend-go/v2/events/handlers.go | 8 +-- cla-backend-go/v2/gerrits/handlers.go | 6 +- .../v2/github_organizations/handlers.go | 8 +-- cla-backend-go/v2/project/handlers.go | 10 +-- cla-backend-go/v2/repositories/handlers.go | 10 +-- cla-backend-go/v2/signatures/handlers.go | 26 ++++---- 10 files changed, 179 insertions(+), 78 deletions(-) diff --git a/cla-backend-go/utils/utils_user_auth_lambda.go b/cla-backend-go/utils/utils_user_auth_lambda.go index 007687d37..8e8570386 100644 --- a/cla-backend-go/utils/utils_user_auth_lambda.go +++ b/cla-backend-go/utils/utils_user_auth_lambda.go @@ -6,7 +6,12 @@ package utils import ( + "context" + "strings" + "github.com/LF-Engineering/lfx-kit/auth" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/sirupsen/logrus" ) const ( @@ -32,36 +37,74 @@ func IsUserAuthorizedForOrganization(user *auth.User, companySFID string, adminS } // IsUserAuthorizedForProjectTree helper function for determining if the user is authorized for this project hierarchy/tree -func IsUserAuthorizedForProjectTree(user *auth.User, projectSFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projectSFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProjectTree", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "adminScopeAllowed": adminScopeAllowed, + } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") return true } - return user.IsUserAuthorized(auth.Project, projectSFID, true) + log.WithFields(f).Debug("checking scope...") + val := user.IsUserAuthorized(auth.Project, projectSFID, true) + log.WithFields(f).Debugf("user allowed: %T", val) + return val } // IsUserAuthorizedForProject helper function for determining if the user is authorized for this project -func IsUserAuthorizedForProject(user *auth.User, projectSFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProject", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "adminScopeAllowed": adminScopeAllowed, + } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") return true } - return user.IsUserAuthorizedForProjectScope(projectSFID) + log.WithFields(f).Debug("checking scope...") + val := user.IsUserAuthorizedForProjectScope(projectSFID) + log.WithFields(f).Debugf("user allowed: %t", val) + return val } // IsUserAuthorizedForAnyProjects helper function for determining if the user is authorized for any of the specified projects -func IsUserAuthorizedForAnyProjects(user *auth.User, projectSFIDs []string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForAnyProjects(ctx context.Context, user *auth.User, projectSFIDs []string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForAnyProjects", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFIDs": strings.Join(projectSFIDs, ","), + "adminScopeAllowed": adminScopeAllowed, + } + for _, projectSFID := range projectSFIDs { - if IsUserAuthorizedForProjectTree(user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("checking project tree scope for: %s...", projectSFID) + if IsUserAuthorizedForProjectTree(ctx, user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("project tree scope check passed for: %s...", projectSFID) return true } - if IsUserAuthorizedForProject(user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("checking project scope for: %s...", projectSFID) + if IsUserAuthorizedForProject(ctx, user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("project scope check passed for: %s...", projectSFID) return true } } + log.WithFields(f).Debugf("project scope checks failed for: %s...", strings.Join(projectSFIDs, ",")) return false } diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index a66b2fb81..09820aebf 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -6,8 +6,12 @@ package utils import ( + "context" "os" "strconv" + "strings" + + "github.com/sirupsen/logrus" "github.com/LF-Engineering/lfx-kit/auth" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -56,53 +60,91 @@ func IsUserAuthorizedForOrganization(user *auth.User, companySFID string, adminS } // IsUserAuthorizedForProjectTree helper function for determining if the user is authorized for this project hierarchy/tree -func IsUserAuthorizedForProjectTree(user *auth.User, projectSFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projectSFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProjectTree", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "adminScopeAllowed": adminScopeAllowed, + } + // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") return true } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") return true } - // Previously, we checked for user.Admin - admins should be in a separate role - // Previously, we checked for user.Allowed, which is currently not used (future flag that is currently not implemented) - return user.IsUserAuthorized(auth.Project, projectSFID, true) + log.WithFields(f).Debug("checking scope...") + val := user.IsUserAuthorized(auth.Project, projectSFID, true) + log.WithFields(f).Debugf("user allowed: %T", val) + return val } // IsUserAuthorizedForProject helper function for determining if the user is authorized for this project -func IsUserAuthorizedForProject(user *auth.User, projectSFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProject", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "adminScopeAllowed": adminScopeAllowed, + } + // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") return true } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") return true } - // Previously, we checked for user.Admin - admins should be in a separate role - // Previously, we checked for user.Allowed, which is currently not used (future flag that is currently not implemented) - return user.IsUserAuthorizedForProjectScope(projectSFID) + log.WithFields(f).Debug("checking scope...") + val := user.IsUserAuthorizedForProjectScope(projectSFID) + log.WithFields(f).Debugf("user allowed: %T", val) + return val } // IsUserAuthorizedForAnyProjects helper function for determining if the user is authorized for any of the specified projects -func IsUserAuthorizedForAnyProjects(user *auth.User, projectSFIDs []string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForAnyProjects(ctx context.Context, user *auth.User, projectSFIDs []string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForAnyProjects", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFIDs": strings.Join(projectSFIDs, ","), + "adminScopeAllowed": adminScopeAllowed, + } // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") return true } for _, projectSFID := range projectSFIDs { - if IsUserAuthorizedForProjectTree(user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("checking project tree scope for: %s...", projectSFID) + if IsUserAuthorizedForProjectTree(ctx, user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("project tree scope check passed for: %s...", projectSFID) return true } - if IsUserAuthorizedForProject(user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("checking project scope for: %s...", projectSFID) + if IsUserAuthorizedForProject(ctx, user, projectSFID, adminScopeAllowed) { + log.WithFields(f).Debugf("project scope check passed for: %s...", projectSFID) return true } } + log.WithFields(f).Debugf("project scope checks failed for: %s...", strings.Join(projectSFIDs, ",")) return false } diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 226412854..3ce030747 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -41,7 +41,11 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P f := logrus.Fields{ "functionName": "ClaGroupCreateClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "claGroupName": aws.StringValue(params.ClaGroupInput.ClaGroupName), + "claGroupName": utils.StringValue(params.ClaGroupInput.ClaGroupName), + "foundationSFID": utils.StringValue(params.ClaGroupInput.FoundationSfid), + "cclaEnabled": utils.BoolValue(params.ClaGroupInput.CclaEnabled), + "iclaEnabled": utils.BoolValue(params.ClaGroupInput.IclaEnabled), + "cclaRequiresIcla": utils.BoolValue(params.ClaGroupInput.CclaRequiresIcla), "claGroupDescription": params.ClaGroupInput.ClaGroupDescription, "projectSFIDList": strings.Join(params.ClaGroupInput.ProjectSfidList, ","), "authUsername": params.XUSERNAME, @@ -49,7 +53,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, aws.StringValue(params.ClaGroupInput.FoundationSfid), projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProject(ctx, authUser, utils.StringValue(params.ClaGroupInput.FoundationSfid), params.ClaGroupInput.ProjectSfidList, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s does not have access to create a CLA Group with project scope of: %s", authUser.UserName, aws.StringValue(params.ClaGroupInput.FoundationSfid)) log.WithFields(f).Warn(msg) return cla_group.NewCreateClaGroupForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -117,7 +121,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, []string{claGroupModel.ProjectExternalID}, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s does not have access to update an existing CLA Group with project scope of: %s", authUser.UserName, claGroupModel.FoundationSFID) log.WithFields(f).Warn(msg) return cla_group.NewUpdateClaGroupForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -190,7 +194,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, []string{claGroupModel.ProjectExternalID}, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s does not have access to delete the CLA Group with project scope of: %s", authUser.UserName, claGroupModel.FoundationSFID) log.WithFields(f).Warn(msg) return cla_group.NewDeleteClaGroupForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -255,7 +259,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, cg.FoundationSFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProject(ctx, authUser, cg.FoundationSFID, []string{cg.ProjectExternalID}, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s does not have access to enroll projects with project scope of: %s", authUser.UserName, cg.FoundationSFID) log.WithFields(f).Warn(msg) return cla_group.NewEnrollProjectsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -351,7 +355,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, cg.FoundationSFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProject(ctx, authUser, cg.FoundationSFID, []string{cg.ProjectExternalID}, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s does not have access to unenroll projects with project scope of: %s", authUser.UserName, cg.FoundationSFID) log.WithFields(f).Warn(msg) return cla_group.NewUnenrollProjectsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -425,7 +429,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // Check permissions log.WithFields(f).Debugf("checking permissions for %s", strings.Join(projectSFIDs, ",")) - if !utils.IsUserAuthorizedForAnyProjects(authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to list projects with project scope of: %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Warn(msg) return cla_group.NewListClaGroupsUnderFoundationForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -498,22 +502,34 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // isUserHaveAccessToCLAProject is a helper function to determine if the user has access to the specified project -func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, projectSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { // nolint +func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, parentProjectSFID string, projectSFIDs []string, projectClaGroupsRepo projects_cla_groups.Repository) bool { // nolint f := logrus.Fields{ - "functionName": "isUserHaveAccessToCLAProject", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "userName": authUser.UserName, - "userEmail": authUser.Email, + "functionName": "isUserHaveAccessToCLAProject", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "parentProjectSFID": parentProjectSFID, + "projectSFIDs": strings.Join(projectSFIDs, ","), + "userName": authUser.UserName, + "userEmail": authUser.Email, } - log.WithFields(f).Debug("testing if user has access to project SFID") - if utils.IsUserAuthorizedForProject(authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + // Check the parent project SFID + log.WithFields(f).Debug("testing if user has access to the parent project SFID") + if utils.IsUserAuthorizedForProject(ctx, authUser, parentProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user has access to the parent project SFID: %s", parentProjectSFID) return true } + log.WithFields(f).Debugf("user does not have access to the parent project SFID: %s", parentProjectSFID) - log.WithFields(f).Debug("user doesn't have direct access to the projectSFID - loading CLA Group from project id...") - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + // Check the project SFIDs + log.WithFields(f).Debug("testing if user has access to any of the provided project SFIDs") + if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user has access at least one of the provided project SFIDs: %s", strings.Join(projectSFIDs, ",")) + return true + } + log.WithFields(f).Debugf("user does not have access any of the provided project SFID: %s", projectSFIDs) + + log.WithFields(f).Debug("user doesn't have direct access to the parentProjectSFID or the provided projects SFIDs - loading CLA Group from project id...") + projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(parentProjectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") return false @@ -525,11 +541,11 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj f["foundationSFID"] = projectCLAGroupModel.FoundationSFID log.WithFields(f).Debug("testing if user has access to parent foundation...") - if utils.IsUserAuthorizedForProjectTree(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation tree...") return true } - if utils.IsUserAuthorizedForProject(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation...") return true } @@ -543,10 +559,10 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj return false } - projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) - f["projectIDs"] = strings.Join(projectSFIDs, ",") + mappedProjectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) + f["mappedProjectSFIDs"] = strings.Join(mappedProjectSFIDs, ",") log.WithFields(f).Debug("testing if user has access to any projects") - if utils.IsUserAuthorizedForAnyProjects(authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, mappedProjectSFIDs, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to at least of of the projects...") return true } diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 8bc0580f3..393a92f85 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -545,13 +545,13 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut } log.WithFields(f).Debug("testing if user has access to project SFID...") - if utils.IsUserAuthorizedForProject(authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to project SFID...") return true } log.WithFields(f).Debug("testing if user has access to project SFID tree...") - if utils.IsUserAuthorizedForProjectTree(authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to project SFID tree...") return true } @@ -591,12 +591,12 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // Check the foundation permissions f["foundationSFID"] = projectCLAGroupModel.FoundationSFID log.WithFields(f).Debug("testing if user has access to parent foundation...") - if utils.IsUserAuthorizedForProject(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation...") return true } log.WithFields(f).Debug("testing if user has access to parent foundation truee...") - if utils.IsUserAuthorizedForProjectTree(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation tree...") return true } diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index e5625b761..c210a045c 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -89,7 +89,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe } log.WithFields(f).Debug("checking permission...") - if !utils.IsUserAuthorizedForProjectTree(authUser, params.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Get Foundation Events for foundation %s.", authUser.UserName, params.FoundationSFID) log.WithFields(f).Warn(msg) return WriteResponse(http.StatusForbidden, runtime.JSONMime, runtime.JSONProducer(), utils.ErrorResponseForbidden(reqID, msg)) @@ -120,7 +120,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe } log.WithFields(f).Debug("checking permission...") - if !utils.IsUserAuthorizedForProjectTree(authUser, params.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Get Foundation Events for foundation %s.", authUser.UserName, params.FoundationSFID) log.WithFields(f).Warn(msg) return events.NewGetRecentEventsForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -165,7 +165,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe } log.WithFields(f).Debug("checking permission...") - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Get Project Events for foundation %s.", authUser.UserName, params.ProjectSFID) log.WithFields(f).Warn(msg) return WriteResponse(http.StatusForbidden, runtime.JSONMime, runtime.JSONProducer(), &models.ErrorResponse{ @@ -217,7 +217,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe } log.WithFields(f).Debug("checking permission...") - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Get Project Events for foundation %s.", authUser.UserName, params.ProjectSFID) log.WithFields(f).Warn(msg) return events.NewGetRecentEventsForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) diff --git a/cla-backend-go/v2/gerrits/handlers.go b/cla-backend-go/v2/gerrits/handlers.go index 7a2f62d0f..1dc419ab2 100644 --- a/cla-backend-go/v2/gerrits/handlers.go +++ b/cla-backend-go/v2/gerrits/handlers.go @@ -49,7 +49,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS }) } // verify user have access to the project - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { return gerrits.NewDeleteGerritForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to DeleteGerrit with Project scope of %s", @@ -84,7 +84,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) // verify user have access to the project - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { return gerrits.NewAddGerritForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to AddGerrit with Project scope of %s", @@ -150,7 +150,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) // verify user have access to the project - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { return gerrits.NewListGerritsForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to ListGerrits with Project scope of %s", diff --git a/cla-backend-go/v2/github_organizations/handlers.go b/cla-backend-go/v2/github_organizations/handlers.go index 116bfee01..514f224a0 100644 --- a/cla-backend-go/v2/github_organizations/handlers.go +++ b/cla-backend-go/v2/github_organizations/handlers.go @@ -37,7 +37,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "projectSFID": params.ProjectSFID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Get Project GitHub Organizations with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) @@ -77,7 +77,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "projectSFID": params.ProjectSFID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Add Project GitHub Organizations with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) @@ -146,7 +146,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { return github_organizations.NewDeleteProjectGithubOrganizationForbidden().WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Delete Project GitHub Organizations with Project scope of %s", @@ -185,7 +185,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { return github_organizations.NewUpdateProjectGithubOrganizationConfigForbidden().WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Update Project GitHub Organizations with Project scope of %s", diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 74aeb669d..1eb1eeda1 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -73,7 +73,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewGetProjectByIDNotFound().WithXRequestID(reqID) } - if !utils.IsUserAuthorizedForProjectTree(user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { return project.NewGetProjectByIDForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Project By ID with Project scope of %s", @@ -94,7 +94,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForProjectTree(user, params.ExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, user, params.ExternalID, utils.ALLOW_ADMIN_SCOPE) { return project.NewGetProjectsByExternalIDForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Projects By External ID with Project scope of %s", @@ -142,7 +142,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewGetProjectByNameNotFound().WithXRequestID(reqID) } - if !utils.IsUserAuthorizedForProjectTree(user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { return project.NewGetProjectByNameForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Project By Name with Project scope of %s", @@ -179,7 +179,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewDeleteProjectByIDBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } - if !utils.IsUserAuthorizedForProjectTree(user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { return project.NewDeleteProjectByIDForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Delete Project By ID with Project scope of %s", @@ -217,7 +217,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service } return project.NewUpdateProjectNotFound().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } - if !utils.IsUserAuthorizedForProjectTree(user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { return project.NewUpdateProjectForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Update Project By ID with Project scope of %s", diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 21d9137e0..19380a3c4 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -42,7 +42,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "projectSFID": params.ProjectSFID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Get GitHub Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) @@ -90,7 +90,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "projectSFID": params.ProjectSFID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Add GitHub Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) @@ -147,7 +147,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "repositoryID": params.RepositoryID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Delete GitHub Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) @@ -205,7 +205,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "repositoryID": params.RepositoryID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Query Protected Branch GitHub Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) @@ -259,7 +259,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "repositoryID": params.RepositoryID, } - if !utils.IsUserAuthorizedForProjectTree(authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Update Protected Branch GitHub Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 1f8078e2b..fd9973597 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -1240,14 +1240,14 @@ func isUserHaveAccessOfSignedSignaturePDF(ctx context.Context, authUser *auth.Us f["foundationSFID"] = foundationID // First, check for PM access - if utils.IsUserAuthorizedForProjectTree(authUser, foundationID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, foundationID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user is authorized for %s scope for foundation ID: %s", utils.ProjectScope, foundationID) return true, nil } // In case the project tree didn't pass, let's check the project list individually - if any has access, we return true for _, proj := range projects { - if utils.IsUserAuthorizedForProject(authUser, proj.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, proj.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user is authorized for %s scope for project ID: %s", utils.ProjectScope, proj.ProjectSFID) return true, nil } @@ -1349,11 +1349,11 @@ func isUserHaveAccessToCLAGroupProjects(ctx context.Context, authUser *auth.User foundationSFID := projectCLAGroupModels[0].FoundationSFID f["foundationSFID"] = foundationSFID log.WithFields(f).Debug("testing if user has access to parent foundation...") - if utils.IsUserAuthorizedForProjectTree(authUser, foundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, foundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation tree...") return true } - if utils.IsUserAuthorizedForProject(authUser, foundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, foundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation...") return true } @@ -1362,7 +1362,7 @@ func isUserHaveAccessToCLAGroupProjects(ctx context.Context, authUser *auth.User projectSFIDs := getProjectIDsFromModels(f, foundationSFID, projectCLAGroupModels) f["projectIDs"] = strings.Join(projectSFIDs, ",") log.WithFields(f).Debug("testing if user has access to any projects") - if utils.IsUserAuthorizedForAnyProjects(authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to at least of of the projects...") return true } @@ -1382,7 +1382,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj } log.WithFields(f).Debug("testing if user has access to project SFID") - if utils.IsUserAuthorizedForProject(authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { return true } @@ -1399,11 +1399,11 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj f["foundationSFID"] = projectCLAGroupModel.FoundationSFID log.WithFields(f).Debug("testing if user has access to parent foundation...") - if utils.IsUserAuthorizedForProjectTree(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation tree...") return true } - if utils.IsUserAuthorizedForProject(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation...") return true } @@ -1420,7 +1420,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) f["projectIDs"] = strings.Join(projectSFIDs, ",") log.WithFields(f).Debug("testing if user has access to any projects") - if utils.IsUserAuthorizedForAnyProjects(authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to at least of of the projects...") return true } @@ -1441,13 +1441,13 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut } log.WithFields(f).Debug("testing if user has access to project SFID...") - if utils.IsUserAuthorizedForProject(authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to project SFID...") return true } log.WithFields(f).Debug("testing if user has access to project SFID tree...") - if utils.IsUserAuthorizedForProjectTree(authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to project SFID tree...") return true } @@ -1487,12 +1487,12 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // Check the foundation permissions f["foundationSFID"] = projectCLAGroupModel.FoundationSFID log.WithFields(f).Debug("testing if user has access to parent foundation...") - if utils.IsUserAuthorizedForProject(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation...") return true } log.WithFields(f).Debug("testing if user has access to parent foundation tree...") - if utils.IsUserAuthorizedForProjectTree(authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to parent foundation tree...") return true } From 05d3d7dc9853c33b50fd4053febf6222c276bc4e Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Sun, 24 Jan 2021 23:00:50 +0300 Subject: [PATCH 0005/1276] [#2515] Feature/Company Sign status (#2517) - Refactored GET for company, project CLAs by leveraging signing entity name - Updated response with multiple companies Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/swagger/cla.v2.yaml | 36 +++++++++- cla-backend-go/v2/company/service.go | 103 +++++++++++++++++---------- 3 files changed, 101 insertions(+), 39 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index ad6e7314b..0574d172f 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -66,6 +66,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/communitybridge/easycla v1.0.99 h1:PkmkMV7cLH2Q2YNSFiGGmlyrHBXVYdsWMwbXNuMAyqw= +github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9udsPZk8oLoQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index def1edb62..480d7009d 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3486,6 +3486,12 @@ parameters: description: the Salesforce ID of the Foundation in: query type: string + signingEntityName: + name: signingEntityName + description: The signing entity name of a Company (Salesforce and EasyCLA) + in: query + type: string + required: true path-claGroupID: name: claGroupID description: ID of the CLA Group @@ -4428,8 +4434,16 @@ definitions: description: CLA Manager email name: type: string - + company-project-cla-list: + type: object + properties: + list: + type: array + items: + $ref: '#/definitions/company-project-cla' + + company-project-cla: type: object properties: signed_cla_list: @@ -4470,6 +4484,16 @@ definitions: title: unsigned project description: details of unsigned project properties: + company_name: + type: string + description: The company name + x-omitempty: false + example: "The Linux Foundation" + signing_entity_name: + type: string + description: The company signing entity name + x-omitempty: false + example: "The Linux Foundation Subsidiary 1" cla_group_id: type: string x-omitempty: false @@ -4505,6 +4529,16 @@ definitions: title: Active CLA of the company description: Details of the active CLA Group properties: + company_name: + type: string + description: The company name + x-omitempty: false + example: "The Linux Foundation" + signing_entity_name: + type: string + description: The company signing entity name + x-omitempty: false + example: "The Linux Foundation Subsidiary 1" signed_on: type: string x-omitempty: false diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 498db2319..09026f158 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -309,7 +309,7 @@ func (s *service) GetCompanyProjectActiveCLAs(ctx context.Context, companyID str activeCla := &models.ActiveCla{} out.List = append(out.List, activeCla) go func(swg *sync.WaitGroup, signature *v1Models.Signature, acla *models.ActiveCla) { - s.fillActiveCLA(swg, signature, acla, claGroups) + s.fillActiveCLA(ctx, swg, signature, acla, claGroups, companyID) }(&wg, sig, activeCla) } wg.Wait() @@ -756,12 +756,11 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, // Attempt to locate the company model in our database log.WithFields(f).Debug("locating company by SF ID") var companyModel *v1Models.Company - companyModel, companyErr := s.companyRepo.GetCompanyByExternalID(ctx, companySFID) + companies, companyErr := s.companyRepo.GetCompaniesByExternalID(ctx, companySFID) if companyErr != nil { // If we were unable to find the company/org in our local database, try to auto-create based // on the existing SF record if companyErr == company.ErrCompanyDoesNotExist { - log.WithFields(f).Debug("company not found in EasyCLA database - attempting to auto-create from platform organization service record") var createCompanyErr error companyModel, createCompanyErr = s.autoCreateCompany(ctx, companySFID) @@ -786,40 +785,51 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, return nil, err } - activeCLAList, err := s.GetCompanyProjectActiveCLAs(ctx, companyModel.CompanyID, projectSFID) - if err != nil { - log.WithFields(f).Warnf("problem fetching company project active CLAs, error: %+v", err) - return nil, err - } - - resp := &models.CompanyProjectClaList{ - SignedClaList: activeCLAList.List, - UnsignedProjectList: make([]*models.UnsignedProject, 0), - } - - for _, activeCLA := range activeCLAList.List { - // remove cla groups for which we have signed cla - log.WithFields(f).Debugf("removing CLA Groups with active CLA, CLA Group: %+v, error: %+v", activeCLA, err) - delete(claGroups, activeCLA.ProjectID) - } - - // fill details for not signed cla - for claGroupID, claGroup := range claGroups { - unsignedProject := &models.UnsignedProject{ - CanSign: canSign, - ClaGroupID: claGroupID, - ClaGroupName: claGroup.ClaGroupName, - ProjectName: claGroup.ProjectName, - ProjectSfid: claGroup.ProjectSFID, - SubProjects: claGroup.SubProjects, - IclaEnabled: claGroup.IclaEnabled, - CclaEnabled: claGroup.CclaEnabled, + var companyProjectClaList = make([]*models.CompanyProjectCla, 0) + for _, company := range companies { + activeCLAList, err := s.GetCompanyProjectActiveCLAs(ctx, company.CompanyID, projectSFID) + if err != nil { + log.WithFields(f).Warnf("problem fetching company project active CLAs, error: %+v", err) + return nil, err + } + var companyProjectCLA = &models.CompanyProjectCla{ + SignedClaList: activeCLAList.List, + UnsignedProjectList: make([]*models.UnsignedProject, 0), + } + for _, activeCLA := range activeCLAList.List { + // remove cla groups for which we have signed cla + log.WithFields(f).Debugf("removing CLA Groups with active CLA, CLA Group: %+v, error: %+v", activeCLA, err) + delete(claGroups, activeCLA.ProjectID) } - log.WithFields(f).Debugf("adding unsigned CLA Group: %+v, error: %+v", unsignedProject, err) - resp.UnsignedProjectList = append(resp.UnsignedProjectList, unsignedProject) + // Get Company details + company, compErr := s.GetCompanyByID(ctx, company.CompanyID) + if compErr != nil { + log.WithFields(f).WithError(compErr).Warnf("unable to fetch company by ID: %s ", company.CompanyID) + return nil, compErr + } + // fill details for not signed cla + for claGroupID, claGroup := range claGroups { + unsignedProject := &models.UnsignedProject{ + CompanyName: company.CompanyName, + SigningEntityName: company.SigningEntityName, + CanSign: canSign, + ClaGroupID: claGroupID, + ClaGroupName: claGroup.ClaGroupName, + ProjectName: claGroup.ProjectName, + ProjectSfid: claGroup.ProjectSFID, + SubProjects: claGroup.SubProjects, + IclaEnabled: claGroup.IclaEnabled, + CclaEnabled: claGroup.CclaEnabled, + } + log.WithFields(f).Debugf("adding unsigned CLA Group: %+v, error: %+v", unsignedProject, err) + companyProjectCLA.UnsignedProjectList = append(companyProjectCLA.UnsignedProjectList, unsignedProject) + } + companyProjectClaList = append(companyProjectClaList, companyProjectCLA) } - return resp, nil + return &models.CompanyProjectClaList{ + List: companyProjectClaList, + }, nil } // GetCompanyCLAGroupManagers when provided the internal company ID and CLA Groups ID, this routine returns the list of @@ -1086,7 +1096,10 @@ func fillProjectInfo(claManagers []*models.CompanyClaManager, claGroups map[stri } } -func (s *service) fillActiveCLA(wg *sync.WaitGroup, sig *v1Models.Signature, activeCla *models.ActiveCla, claGroups map[string]*claGroupModel) { +func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1Models.Signature, activeCla *models.ActiveCla, claGroups map[string]*claGroupModel, companyID string) { + f := logrus.Fields{ + "functionName": "fillActiveCLA", + } defer wg.Done() cg, ok := claGroups[sig.ProjectID] if !ok { @@ -1094,7 +1107,20 @@ func (s *service) fillActiveCLA(wg *sync.WaitGroup, sig *v1Models.Signature, act return } + // Get Company details + company, compErr := s.GetCompanyByID(ctx, companyID) + if compErr != nil { + log.WithFields(f).WithError(compErr).Warnf("unable to fetch company by ID: %s ", companyID) + return + } + // fill details from dynamodb + activeCla.CompanyName = company.CompanyName + if company.SigningEntityName == "" { + activeCla.SigningEntityName = company.CompanyName + } else { + activeCla.SigningEntityName = company.SigningEntityName + } activeCla.ProjectID = sig.ProjectID if sig.SignedOn == "" { activeCla.SignedOn = sig.SignatureCreated @@ -1133,13 +1159,13 @@ func (s *service) fillActiveCLA(wg *sync.WaitGroup, sig *v1Models.Signature, act } usc := v2UserService.GetClient() if len(sig.SignatureACL) == 0 { - log.Warnf("signature : %s have empty signature_acl", sig.SignatureID) + log.WithFields(f).Warnf("signature : %s have empty signature_acl", sig.SignatureID) return } lfUsername := sig.SignatureACL[0].LfUsername user, err := usc.GetUserByUsername(lfUsername) if err != nil { - log.Warnf("unable to get user with lf username : %s", lfUsername) + log.WithFields(f).WithError(err).Warnf("unable to get user with lf username : %s", lfUsername) return } signatoryName = user.Name @@ -1310,7 +1336,8 @@ func (s service) autoCreateCompany(ctx context.Context, companySFID string) (*v1 companyModel, companyCreateErr := s.companyRepo.CreateCompany(ctx, &v1Models.Company{ CompanyExternalID: companySFID, CompanyName: sfOrgModel.Name, - Note: "created on-demand by v4 service based on SF Organization Service record", + + Note: "created on-demand by v4 service based on SF Organization Service record", }) if companyCreateErr != nil || companyModel == nil { From 65311b5ca0ea09f8d6cc154363255f335cb5f38e Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 25 Jan 2021 16:33:44 +0300 Subject: [PATCH 0006/1276] [#2462] Bug/Add org - Resolved bug caused by 404 on company lookup Signed-off-by: wanyaland --- .../v2/organization-service/client.go | 96 ++++++++++--------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index 2d62e74a3..a0276e7c8 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -532,6 +532,8 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName "companyWebsite": companyWebsite, } + var org *models.Organization + tok, tokenErr := token.GetToken() if tokenErr != nil { log.WithFields(f).WithError(tokenErr).Warn("unable to fetch token") @@ -547,45 +549,60 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName lookupOrg, lookupErr := osc.SearchOrgLookup(ctx, nil, &companyWebsite) if lookupErr != nil { log.WithFields(f).WithError(lookupErr).Warn("unable to search for existing company using company website value") - return nil, lookupErr + if _, ok := lookupErr.(*organizations.LookupNotFound); !ok { + return nil, lookupErr + } } - if lookupOrg.Payload.ID != "" { + if lookupOrg != nil && lookupOrg.Payload.ID != "" { // Get org based on ID + var updateErr error existingOrg, existingOrgErr := osc.GetOrganization(ctx, lookupOrg.Payload.ID) if existingOrgErr != nil { log.WithFields(f).WithError(existingOrgErr).Warnf("unable to get organization : %s ", lookupOrg.Payload.ID) return nil, existingOrgErr } - updatedModel, updateErr := osc.UpdateOrg(ctx, existingOrg, signingEntityName) + org, updateErr = osc.UpdateOrg(ctx, existingOrg, signingEntityName) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warn("unable to update for existing company") return nil, updateErr } - return updatedModel, nil - } - // use linux foundation logo as default - linuxFoundation, err := osc.SearchOrganization(ctx, utils.TheLinuxFoundation, "", "") - if err != nil || len(linuxFoundation) == 0 { - log.WithFields(f).WithError(err).Warn("unable to search Linux Foundation organization") - return nil, err - } + } else { + // use linux foundation logo as default + linuxFoundation, err := osc.SearchOrganization(ctx, utils.TheLinuxFoundation, "", "") + if err != nil || len(linuxFoundation) == 0 { + log.WithFields(f).WithError(err).Warn("unable to search Linux Foundation organization") + return nil, err + } - clientAuth := runtimeClient.BearerToken(tok) - description := "No Description" - f["description"] = description - companyType := "Customer" - f["type"] = companyType - f["companyType"] = companyType - companySource := "No Source" - industry := "No Industry" - f["industry"] = industry - logoURL := linuxFoundation[0].LogoURL - f["logoURL"] = logoURL - - params := &organizations.CreateOrgParams{ - Org: &models.CreateOrg{ + clientAuth := runtimeClient.BearerToken(tok) + description := "No Description" + f["description"] = description + companyType := "Customer" + f["type"] = companyType + f["companyType"] = companyType + companySource := "No Source" + industry := "No Industry" + f["industry"] = industry + logoURL := linuxFoundation[0].LogoURL + f["logoURL"] = logoURL + + params := &organizations.CreateOrgParams{ + Org: &models.CreateOrg{ + Description: &description, + Name: &companyName, + Website: &companyWebsite, + Industry: &industry, + Source: &companySource, + Type: &companyType, + LogoURL: &logoURL, + SigningEntityName: []string{signingEntityName}, + }, + Context: ctx, + } + + log.WithFields(f).Debugf("Creating organization with params: %+v", models.CreateOrg{ Description: &description, Name: &companyName, Website: &companyWebsite, @@ -594,28 +611,17 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName Type: &companyType, LogoURL: &logoURL, SigningEntityName: []string{signingEntityName}, - }, - Context: ctx, - } + }) + result, err := osc.cl.Organizations.CreateOrg(params, clientAuth) + if err != nil { + log.WithFields(f).WithError(err).Warnf("Failed to create salesforce Company :%s , err: %+v ", companyName, err) + return nil, err + } + log.WithFields(f).Infof("Company: %s successfuly created ", companyName) - log.WithFields(f).Debugf("Creating organization with params: %+v", models.CreateOrg{ - Description: &description, - Name: &companyName, - Website: &companyWebsite, - Industry: &industry, - Source: &companySource, - Type: &companyType, - LogoURL: &logoURL, - SigningEntityName: []string{signingEntityName}, - }) - result, err := osc.cl.Organizations.CreateOrg(params, clientAuth) - if err != nil { - log.WithFields(f).WithError(err).Warnf("Failed to create salesforce Company :%s , err: %+v ", companyName, err) - return nil, err + org = result.Payload } - log.WithFields(f).Infof("Company: %s successfuly created ", companyName) - - return result.Payload, err + return org, nil } // UpdateOrg updates the company record based on the provided name, signingEntityName, and website From 997dd967a1235818b303d7a6ded40e077df1f981 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 25 Jan 2021 20:11:48 +0200 Subject: [PATCH 0007/1276] adding the # for the frontend to work properly (#2520) Signed-off-by: makkalot --- cla-backend/cla/models/github_models.py | 1 + cla-backend/cla/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index d94d053bf..01c530c4a 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -1033,6 +1033,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep # specified default value per issue #166 context, body = cla.utils.assemble_cla_status(context_name, signed=True) sign_url = cla.conf["CLA_LANDING_PAGE"] # Remove this once signature detail page ready. + sign_url = os.path.join(sign_url, "#/") sign_url = append_project_version_to_url(address=sign_url, project_version=project_version) cla.log.debug(f'Creating new CLA {state} status - {len(signed)} passed, {missing}, signing url: {sign_url}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 78079d689..e12f04edc 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -827,6 +827,7 @@ def get_comment_badge(repository_type, all_signed, sign_url, project_version, mi if all_signed: badge_url = f'{CLA_LOGO_URL}/cla-signed.svg' badge_hyperlink = cla.conf["CLA_LANDING_PAGE"] + badge_hyperlink = os.path.join(badge_hyperlink, "#/") badge_hyperlink = append_project_version_to_url(address=badge_hyperlink, project_version=project_version) alt = "CLA Signed" else: From 8742a10f8aeb6a7ae370447b59c18ce5674ee4e1 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 26 Jan 2021 00:59:30 +0300 Subject: [PATCH 0008/1276] [#2515] Feature/ Company Project CLA (#2521) - Added company_id and signatureACL to response Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/swagger/cla.v2.yaml | 6 +++++ .../swagger/common/signature-acl.yaml | 10 ++++++++ cla-backend-go/v2/company/service.go | 23 +++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 cla-backend-go/swagger/common/signature-acl.yaml diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 0574d172f..4807b6946 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -67,6 +67,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/communitybridge/easycla v1.0.99 h1:PkmkMV7cLH2Q2YNSFiGGmlyrHBXVYdsWMwbXNuMAyqw= github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9udsPZk8oLoQ= +github.com/communitybridge/easycla v1.0.107 h1:dktHAji1yJ1nMEu54z4paPWOM4Q7A9rryc0OCADfAcY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 480d7009d..37b390f4f 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4494,6 +4494,8 @@ definitions: description: The company signing entity name x-omitempty: false example: "The Linux Foundation Subsidiary 1" + signing_entity_id: + $ref: './common/properties/internal-id.yaml' cla_group_id: type: string x-omitempty: false @@ -4539,6 +4541,10 @@ definitions: description: The company signing entity name x-omitempty: false example: "The Linux Foundation Subsidiary 1" + signing_entity_id: + $ref: './common/properties/internal-id.yaml' + signature_acl: + $ref: './common/signature-acl.yaml' signed_on: type: string x-omitempty: false diff --git a/cla-backend-go/swagger/common/signature-acl.yaml b/cla-backend-go/swagger/common/signature-acl.yaml new file mode 100644 index 000000000..4d245e56b --- /dev/null +++ b/cla-backend-go/swagger/common/signature-acl.yaml @@ -0,0 +1,10 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +title: signature acl list representing cla managers access list +properties: + username_list: + type: array + items: + type: string diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 09026f158..4db52ef49 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -812,6 +812,7 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, unsignedProject := &models.UnsignedProject{ CompanyName: company.CompanyName, SigningEntityName: company.SigningEntityName, + SigningEntityID: company.CompanyID, CanSign: canSign, ClaGroupID: claGroupID, ClaGroupName: claGroup.ClaGroupName, @@ -825,6 +826,13 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companyProjectCLA.UnsignedProjectList = append(companyProjectCLA.UnsignedProjectList, unsignedProject) } companyProjectClaList = append(companyProjectClaList, companyProjectCLA) + + // refresh clagroups for next company instance + claGroups, err = s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) + if err != nil { + log.WithFields(f).Warnf("problem fetching CLA Groups under project or foundation, error: %+v", err) + return nil, err + } } return &models.CompanyProjectClaList{ @@ -1114,6 +1122,17 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 return } + // Update acl + var acl = make([]string, 0) + if len(sig.SignatureACL) > 0 { + log.WithFields(f).Debugf("updating signature acl: %+v list for lfusernames...", sig.SignatureACL) + for _, manager := range sig.SignatureACL { + if manager.LfUsername != "" { + acl = append(acl, manager.LfUsername) + } + } + } + // fill details from dynamodb activeCla.CompanyName = company.CompanyName if company.SigningEntityName == "" { @@ -1127,6 +1146,10 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 } else { activeCla.SignedOn = sig.SignedOn } + activeCla.SigningEntityID = companyID + activeCla.SignatureACL = &models.ActiveClaSignatureACL{ + UsernameList: acl, + } activeCla.ClaGroupName = cg.ClaGroupName activeCla.SignatureID = sig.SignatureID.String() From 4ce9b750dc0c5c293594a42a258bc1650a95f7b7 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 26 Jan 2021 03:47:01 +0300 Subject: [PATCH 0009/1276] [#2515] Feature/ Get Events Signing Entity Name (#2522) - Factored in internal companyID based on signing entity name to fetch corresponding events Signed-off-by: wanyaland --- cla-backend-go/events/mockrepo.go | 4 ++-- cla-backend-go/events/repository.go | 32 +++++++++++++++++++--------- cla-backend-go/events/service.go | 12 +++++------ cla-backend-go/swagger/cla.v2.yaml | 4 ++-- cla-backend-go/v2/events/handlers.go | 17 ++++++++++----- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/cla-backend-go/events/mockrepo.go b/cla-backend-go/events/mockrepo.go index f74ae1373..4f82ab047 100644 --- a/cla-backend-go/events/mockrepo.go +++ b/cla-backend-go/events/mockrepo.go @@ -19,11 +19,11 @@ func (repo *mockRepository) AddDataToEvent(eventID, foundationSFID, projectSFID, panic("implement me") } -func (repo *mockRepository) GetCompanyFoundationEvents(companySFID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { +func (repo *mockRepository) GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { panic("implement me") } -func (repo *mockRepository) GetCompanyClaGroupEvents(companySFID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { +func (repo *mockRepository) GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { panic("implement me") } diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index e0204a371..01a825b73 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -59,8 +59,8 @@ type Repository interface { SearchEvents(params *eventOps.SearchEventsParams, pageSize int64) (*models.EventList, error) GetRecentEvents(pageSize int64) (*models.EventList, error) - GetCompanyFoundationEvents(companySFID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) - GetCompanyClaGroupEvents(companySFID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) + GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) + GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) GetCompanyEvents(companyID, eventType string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) GetFoundationEvents(foundationSFID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) GetClaGroupEvents(claGroupID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) @@ -308,7 +308,7 @@ func (repo *repository) SearchEvents(params *eventOps.SearchEventsParams, pageSi } // queryEventsTable queries events table on index -func (repo *repository) queryEventsTable(indexName string, condition expression.KeyConditionBuilder, nextKey *string, pageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { +func (repo *repository) queryEventsTable(indexName string, condition expression.KeyConditionBuilder, filter *expression.ConditionBuilder, nextKey *string, pageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { f := logrus.Fields{ "functionName": "events.queryEventsTable", "indexName": indexName, @@ -320,10 +320,14 @@ func (repo *repository) queryEventsTable(indexName string, condition expression. log.WithFields(f).Debug("querying events table") builder := expression.NewBuilder() // .WithProjection(buildProjection()) + // The table we're interested in tableName := fmt.Sprintf("cla-%s-events", repo.stage) builder = builder.WithKeyCondition(condition) + if filter != nil { + builder = builder.WithFilter(*filter) + } // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { @@ -464,17 +468,25 @@ func buildNextKey(indexName string, event *models.Event) (string, error) { } // GetCompanyFoundationEvents returns the list of events for foundation and company -func (repo *repository) GetCompanyFoundationEvents(companySFID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { +func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { key := fmt.Sprintf("%s#%s", companySFID, foundationSFID) keyCondition := expression.Key("company_sfid_foundation_sfid").Equal(expression.Value(key)) - return repo.queryEventsTable(CompanySFIDFoundationSFIDEpochIndex, keyCondition, nextKey, paramPageSize, all, nil) + var filter expression.ConditionBuilder + if companyID != "" { + filter = expression.Name("company_id").Equal(expression.Value(companyID)) + } + return repo.queryEventsTable(CompanySFIDFoundationSFIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } // GetCompanyClaGroupEvents returns the list of events for cla group and the company -func (repo *repository) GetCompanyClaGroupEvents(companySFID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { +func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { key := fmt.Sprintf("%s#%s", companySFID, claGroupID) keyCondition := expression.Key("company_sfid_project_id").Equal(expression.Value(key)) - return repo.queryEventsTable(CompanySFIDProjectIDEpochIndex, keyCondition, nextKey, paramPageSize, all, nil) + var filter expression.ConditionBuilder + if companyID != "" { + filter = expression.Name("company_id").Equal(expression.Value(companyID)) + } + return repo.queryEventsTable(CompanySFIDProjectIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } // GetCompanyEvents returns the list of events for given company id and event types @@ -482,19 +494,19 @@ func (repo *repository) GetCompanyEvents(companyID, eventType string, nextKey *s keyCondition := expression.Key("company_id").Equal(expression.Value(companyID)).And( expression.Key("event_type").Equal(expression.Value(eventType))) - return repo.queryEventsTable(CompanyIDEventTypeIndex, keyCondition, nextKey, paramPageSize, all, nil) + return repo.queryEventsTable(CompanyIDEventTypeIndex, keyCondition, nil, nextKey, paramPageSize, all, nil) } // GetFoundationEvents returns the list of foundation events func (repo *repository) GetFoundationEvents(foundationSFID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { keyCondition := expression.Key("event_foundation_sfid").Equal(expression.Value(foundationSFID)) - return repo.queryEventsTable(EventFoundationSFIDEpochIndex, keyCondition, nextKey, paramPageSize, all, searchTerm) + return repo.queryEventsTable(EventFoundationSFIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } // GetClaGroupEvents returns the list of cla-group events func (repo *repository) GetClaGroupEvents(claGroupID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { keyCondition := expression.Key("event_project_id").Equal(expression.Value(claGroupID)) - return repo.queryEventsTable(EventProjectIDEpochIndex, keyCondition, nextKey, paramPageSize, all, searchTerm) + return repo.queryEventsTable(EventProjectIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } // toString encodes the map as a string diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 622fdf056..34d0439d7 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -30,8 +30,8 @@ type Service interface { GetFoundationEvents(foundationSFID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) GetClaGroupEvents(claGroupID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) - GetCompanyFoundationEvents(companySFID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) - GetCompanyClaGroupEvents(companySFID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) + GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) + GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) GetCompanyEvents(companyID, eventType string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) } @@ -91,13 +91,13 @@ func (s *service) GetClaGroupEvents(projectSFDC string, nextKey *string, paramPa } // GetCompanyFoundationEvents returns list of events for company and foundation -func (s *service) GetCompanyFoundationEvents(companySFID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { - return s.repo.GetCompanyFoundationEvents(companySFID, foundationSFID, nextKey, paramPageSize, all) +func (s *service) GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { + return s.repo.GetCompanyFoundationEvents(companySFID, companyID, foundationSFID, nextKey, paramPageSize, all) } // GetCompanyClaGroupEvents returns list of events for company and cla group -func (s *service) GetCompanyClaGroupEvents(companySFID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { - return s.repo.GetCompanyClaGroupEvents(companySFID, claGroupID, nextKey, paramPageSize, all) +func (s *service) GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { + return s.repo.GetCompanyClaGroupEvents(companySFID, companyID, claGroupID, nextKey, paramPageSize, all) } func (s *service) GetCompanyEvents(companyID, eventType string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 37b390f4f..51f1cf4d9 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1068,7 +1068,7 @@ paths: tags: - events - /company/{companySFID}/project/{projectSFID}/events: + /company/{companyID}/project/{projectSFID}/events: get: summary: Get recent events of company and project description: Returns list of events of company and project @@ -1080,7 +1080,7 @@ paths: - $ref: "#/parameters/x-email" - $ref: '#/parameters/pageSize' - $ref: '#/parameters/path-projectSFID' - - $ref: '#/parameters/path-companySFID' + - $ref: '#/parameters/path-companyID' - $ref: '#/parameters/nextKey' - $ref: '#/parameters/returnAllEvents' produces: diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index c210a045c..365bad29f 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -279,13 +279,20 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe "authUserName": authUser.UserName, "authUserEmail": authUser.Email, "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, } - if !utils.IsUserAuthorizedForOrganization(authUser, params.CompanySFID, utils.ALLOW_ADMIN_SCOPE) { + + v1Company, compErr := v1CompanyRepo.GetCompany(ctx, params.CompanyID) + if compErr != nil { + log.WithFields(f).Warnf("unable to fetch company by ID:%s ", params.CompanyID) + return events.NewGetCompanyProjectEventsBadRequest().WithPayload(errorResponse(reqID, compErr)) + } + + if !utils.IsUserAuthorizedForOrganization(authUser, v1Company.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { return events.NewGetCompanyProjectEventsForbidden().WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to GetCompanyProject Events with Organization scope of %s", - authUser.UserName, params.CompanySFID), + authUser.UserName, v1Company.CompanyExternalID), XRequestID: reqID, }) } @@ -300,7 +307,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe var result *v1Models.EventList if projectDetails.ProjectType == utils.ProjectTypeProjectGroup { - result, err = service.GetCompanyFoundationEvents(params.CompanySFID, params.ProjectSFID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) + result, err = service.GetCompanyFoundationEvents(v1Company.CompanyExternalID, params.CompanyID, params.ProjectSFID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) } else { pm, perr := projectsClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) if perr != nil { @@ -314,7 +321,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe log.WithFields(f).WithError(perr).Warnf("problem determining CLA Group for project SFID: %s", params.ProjectSFID) return events.NewGetCompanyProjectEventsInternalServerError().WithPayload(errorResponse(reqID, perr)) } - result, err = service.GetCompanyClaGroupEvents(params.CompanySFID, pm.ClaGroupID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) + result, err = service.GetCompanyClaGroupEvents(v1Company.CompanyExternalID, params.CompanyID, pm.ClaGroupID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) } if err != nil { log.WithFields(f).WithError(err).Warn("problem loading events") From 4f585d252bbaf7a376219dbe4aeb517d89f513ab Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 25 Jan 2021 20:52:28 -0500 Subject: [PATCH 0010/1276] Updated v2 Corporate Console APIs - Changed SFID to Internal ID (#2524) - Updated many API calls to switch from SFID to internal ID due to our support of signing entity names. As a result of this introduction, we now can have zero, one or more than 1 internal company records matching a single SF parent record. As a result, the APIs were updated to force the client to provide the interal ID. - Updated logging and permission checks to lookup SFID to check for platform permissions Signed-off-by: David Deal --- cla-backend-go/cmd/server.go | 66 ++--- cla-backend-go/swagger/cla.v2.yaml | 149 +++++++---- .../company-signing-entity-name.yaml | 2 +- cla-backend-go/utils/constants.go | 3 + cla-backend-go/utils/responses.go | 18 ++ cla-backend-go/v2/cla_manager/errors.go | 39 +++ cla-backend-go/v2/cla_manager/handlers.go | 249 +++++++++--------- cla-backend-go/v2/cla_manager/service.go | 202 +++++++++----- cla-backend-go/v2/company/handlers.go | 150 ++++++----- cla-backend-go/v2/company/service.go | 112 +++++--- 10 files changed, 611 insertions(+), 379 deletions(-) create mode 100644 cla-backend-go/v2/cla_manager/errors.go diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 6ac014d60..9242288c8 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -68,7 +68,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/auth" - "github.com/communitybridge/easycla/cla-backend-go/company" + v1Company "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/docraptor" "github.com/communitybridge/easycla/cla-backend-go/gen/models" "github.com/communitybridge/easycla/cla-backend-go/gen/restapi" @@ -120,7 +120,7 @@ func init() { type combinedRepo struct { users.UserRepository - company.IRepository + v1Company.IRepository project.ProjectRepository } @@ -232,8 +232,8 @@ func server(localMode bool) http.Handler { gerritRepo := gerrits.NewRepository(awsSession, stage) templateRepo := template.NewRepository(awsSession, stage) approvalListRepo := approval_list.NewRepository(awsSession, stage) - companyRepo := company.NewRepository(awsSession, stage) - signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo) + v1CompanyRepo := v1Company.NewRepository(awsSession, stage) + signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo) projectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) projectRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, projectClaGroupRepo) eventsRepo := events.NewRepository(awsSession, stage) @@ -244,7 +244,7 @@ func server(localMode bool) http.Handler { // Our service layer handlers eventsService := events.NewService(eventsRepo, combinedRepo{ usersRepo, - companyRepo, + v1CompanyRepo, projectRepo, }) @@ -259,23 +259,23 @@ func server(localMode bool) http.Handler { usersService := users.NewService(usersRepo, eventsService) healthService := health.New(Version, Commit, Branch, BuildDate) templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) - projectService := project.NewService(projectRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) - v2ProjectService := v2Project.NewService(projectService, projectRepo, projectClaGroupRepo) - companyService := company.NewService(companyRepo, configFile.CorporateConsoleURL, userRepo, usersService) - v2CompanyService := v2Company.NewService(companyService, signaturesRepo, projectRepo, usersRepo, companyRepo, projectClaGroupRepo, eventsService) - v2SignService := sign.NewService(configFile.ClaV1ApiURL, companyRepo, projectRepo, projectClaGroupRepo, companyService) - signaturesService := signatures.NewService(signaturesRepo, companyService, usersService, eventsService, githubOrgValidation) - v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, projectService, companyService, signaturesService, projectClaGroupRepo) - v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, companyService, projectService, usersService, signaturesService, eventsService, configFile.CorporateConsoleURL) - repositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) + v1ProjectService := project.NewService(projectRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) + v2ProjectService := v2Project.NewService(v1ProjectService, projectRepo, projectClaGroupRepo) + v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleURL, userRepo, usersService) + v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, projectRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) + v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, projectRepo, projectClaGroupRepo, v1CompanyService) + v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) + v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo) + v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) + v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) - v2ClaManagerService := v2ClaManager.NewService(companyService, projectService, v1ClaManagerService, usersService, repositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) - approvalListService := approval_list.NewService(approvalListRepo, usersRepo, companyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleURL, http.DefaultClient) + v2ClaManagerService := v2ClaManager.NewService(v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) + v1ApprovalListService := approval_list.NewService(approvalListRepo, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleURL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) - autoEnableService := dynamo_events.NewAutoEnableService(repositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, projectService) + autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, eventsService, autoEnableService) gerritService := gerrits.NewService(gerritRepo, &gerrits.LFGroup{ LfBaseURL: configFile.LFGroup.ClientURL, @@ -283,7 +283,7 @@ func server(localMode bool) http.Handler { ClientSecret: configFile.LFGroup.ClientSecret, RefreshToken: configFile.LFGroup.RefreshToken, }) - v2ClaGroupService := cla_groups.NewService(projectService, templateService, projectClaGroupRepo, v1ClaManagerService, signaturesService, metricsRepo, gerritService, repositoriesService, eventsService) + v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, projectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) sessionStore, err := dynastore.New(dynastore.Path("/"), dynastore.HTTPOnly(), dynastore.TableName(configFile.SessionStoreTableName), dynastore.DynamoDB(dynamodb.New(awsSession))) if err != nil { @@ -298,35 +298,35 @@ func server(localMode bool) http.Handler { // Setup our API handlers users.Configure(api, usersService, eventsService) - project.Configure(api, projectService, eventsService, gerritService, repositoriesService, signaturesService) - v2Project.Configure(v2API, projectService, v2ProjectService, eventsService) + project.Configure(api, v1ProjectService, eventsService, gerritService, v1RepositoriesService, v1SignaturesService) + v2Project.Configure(v2API, v1ProjectService, v2ProjectService, eventsService) health.Configure(api, healthService) v2Health.Configure(v2API, healthService) template.Configure(api, templateService, eventsService) v2Template.Configure(v2API, templateService, eventsService) github.Configure(api, configFile.Github.ClientID, configFile.Github.ClientSecret, configFile.Github.AccessToken, sessionStore) - signatures.Configure(api, signaturesService, sessionStore, eventsService) - v2Signatures.Configure(v2API, projectService, projectRepo, companyService, signaturesService, sessionStore, eventsService, v2SignatureService, projectClaGroupRepo) - approval_list.Configure(api, approvalListService, sessionStore, signaturesService, eventsService) - company.Configure(api, companyService, usersService, companyUserValidation, eventsService) + signatures.Configure(api, v1SignaturesService, sessionStore, eventsService) + v2Signatures.Configure(v2API, v1ProjectService, projectRepo, v1CompanyService, v1SignaturesService, sessionStore, eventsService, v2SignatureService, projectClaGroupRepo) + approval_list.Configure(api, v1ApprovalListService, sessionStore, v1SignaturesService, eventsService) + v1Company.Configure(api, v1CompanyService, usersService, companyUserValidation, eventsService) docs.Configure(api) v2Docs.Configure(v2API) version.Configure(api, Version, Commit, Branch, BuildDate) v2Version.Configure(v2API, Version, Commit, Branch, BuildDate) events.Configure(api, eventsService) - v2Events.Configure(v2API, eventsService, companyRepo, projectClaGroupRepo) - v2Metrics.Configure(v2API, v2MetricsService, companyRepo) + v2Events.Configure(v2API, eventsService, v1CompanyRepo, projectClaGroupRepo) + v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) - repositories.Configure(api, repositoriesService, eventsService) + repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) - gerrits.Configure(api, gerritService, projectService, eventsService) - v2Gerrits.Configure(v2API, gerritService, projectService, eventsService, projectClaGroupRepo) - v2Company.Configure(v2API, v2CompanyService, companyRepo, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleURL) - cla_manager.Configure(api, v1ClaManagerService, companyService, projectService, usersService, signaturesService, eventsService, configFile.CorporateConsoleURL) - v2ClaManager.Configure(v2API, v2ClaManagerService, configFile.LFXPortalURL, projectClaGroupRepo, userRepo) + gerrits.Configure(api, gerritService, v1ProjectService, eventsService) + v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, projectClaGroupRepo) + v2Company.Configure(v2API, v2CompanyService, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleURL) + cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) + v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, projectClaGroupRepo, userRepo) sign.Configure(v2API, v2SignService) - cla_groups.Configure(v2API, v2ClaGroupService, projectService, projectClaGroupRepo, eventsService) + cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, projectClaGroupRepo, eventsService) v2GithubActivity.Configure(v2API, v2GithubActivityService) userCreaterMiddleware := func(next http.Handler) http.Handler { diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 51f1cf4d9..819e63cc9 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -2211,7 +2211,7 @@ paths: $ref: '#/responses/internal-server-error' tags: - signatures - + /company/{companySFID}/user/{userLFID}/claGroupID/{claGroupID}/is-cla-manager-designee: get: summary: Checks cla-manager-designee role @@ -2405,7 +2405,7 @@ paths: tags: - company - /company/{companySFID}/project/{projectSFID}/cla-manager/requests: + /company/{companyID}/project/{projectSFID}/cla-manager/requests: post: summary: Adds a CLA Manager Designee to the specified Company and Project description: User proposes a CLA Manager making the proposed user CLA Manager Designee @@ -2415,7 +2415,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-projectSFID" - name: body in: body @@ -2467,7 +2467,7 @@ paths: - cla-manager - /company/{companySFID}/project/{projectSFID}/cla-manager: + /company/{companyID}/project/{projectSFID}/cla-manager: post: summary: Adds a new CLA Manager to the specified Company and Project description: Allows an existing CLA Manager to add another CLA Manager to the specified Company and Project. @@ -2478,7 +2478,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-projectSFID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - name: body in: body schema: @@ -2542,9 +2542,9 @@ paths: tags: - company - /company/{companySFID}/project/{projectSFID}/cla-manager/{userLFID}: + /company/{companyID}/project/{projectSFID}/cla-manager/{userLFID}: delete: - summary: Removes the CLA Manager from ACL for specified Company and Project + summary: Deletes the CLA Manager from CLA Manager list for specified Company and Project description: Allows an existing CLA Manager to remove another CLA Manager from the specified Company and Project. operationId: deleteCLAManager parameters: @@ -2553,7 +2553,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-projectSFID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-userLFID" responses: '204': @@ -2573,9 +2573,7 @@ paths: tags: - cla-manager - - - /company/{companySFID}/claGroup/{claGroupID}/cla-manager-designee: + /company/{companyID}/claGroup/{claGroupID}/cla-manager-designee: post: summary: Assigns CLA Manager designee description: Assigns CLA Manager designee to a given user @@ -2586,7 +2584,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-claGroupID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - name: body in: body schema: @@ -2620,7 +2618,7 @@ paths: tags: - cla-manager - /company/{companySFID}/project/{projectSFID}/cla-manager-designee: + /company/{companyID}/project/{projectSFID}/cla-manager-designee: post: summary: Assigns CLA Manager designee description: Assigns CLA Manager designee to a given user @@ -2631,7 +2629,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-projectSFID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - name: body in: body schema: @@ -2940,6 +2938,41 @@ paths: tags: - company + /company/entityname/{signingEntityName}: + get: + summary: Gets the company by name + description: Returns the matching company by name + operationId: getCompanyBySigningEntityName + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: '#/parameters/path-signingEntityName' + produces: + - application/json + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/company' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + '500': + $ref: '#/responses/internal-server-error' + tags: + - company + /company/id/{companyID}: delete: summary: Deletes the company by ID @@ -2972,7 +3005,7 @@ paths: $ref: '#/responses/internal-server-error' tags: - company - + /company/lookup: get: summary: Search companies from organization service @@ -3047,7 +3080,7 @@ paths: tags: - company - /company/{companySFID}/project/{projectSFID}/cla-managers: + /company/{companyID}/project/{projectSFID}/cla-managers: get: summary: Get CLA manager of company for particular project/foundation description: Returns list CLA managers of the company for project/foundation @@ -3057,7 +3090,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-projectSFID" responses: '200': @@ -3109,7 +3142,7 @@ paths: tags: - company - /company/{companySFID}/project/{projectSFID}/active-cla-list: + /company/{companyID}/project/{projectSFID}/active-cla-list: get: summary: Get active CLA list of company for particular project/foundation description: Returns list active CLA of the company under particular project/foundation @@ -3119,7 +3152,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-projectSFID" responses: '200': @@ -3140,6 +3173,7 @@ paths: $ref: '#/responses/not-found' tags: - company + /company/{companySFID}/project/{projectSFID}/contributors: get: summary: Get corporate contributors for project @@ -3565,6 +3599,16 @@ parameters: type: string required: true pattern: '^([\w\d\s\-\,\./]+){2,255}$' + path-signingEntityName: + name: signingEntityName + type: string + description: Signing Entity Name of the Company + # Pattern aligns with UI and other platform services including Org Service + pattern: '[^<>]*' # allow everything except greater than and less than symbols + minLength: 2 + maxLength: 100 + in: path + required: true path-signatureID: name: signatureID description: id of the CLA signature @@ -3973,19 +4017,19 @@ definitions: Source: type: string description: >- - The company account source, such as "Google Natural Search", "Event-Promo", "Direct Mail", or "Tradeshow". - If the information was found in clearbit, this value will be "clearbit". + The company account source, such as "Google Natural Search", "Event-Promo", "Direct Mail", or "Tradeshow". + If the information was found in clearbit, this value will be "clearbit". example: "clearbit" Industry: type: string description: >- - The company industry, such as "Banking" or "Communications" + The company industry, such as "Banking" or "Communications" example: "Education" pattern: '^([\w\d\s\-\,\.]+){2,40}$' Sector: type: string description: >- - The company industry sector, such as "Information Technology" + The company industry sector, such as "Information Technology" example: "Information Technology" Employees: type: string @@ -4127,8 +4171,8 @@ definitions: items: $ref: '#/definitions/cla-manager-designee' - - + + user-role-status: type: object title: User Role status @@ -4249,20 +4293,20 @@ definitions: assigned_on: type: string x-omitempty: false + company_id: + $ref: './common/properties/internal-id.yaml' + description: 'the Company/Organization internal ID' + x-omitempty: false company_sfid: - type: string - description: 'the Organization SalesForce ID' + $ref: './common/properties/external-id.yaml' + description: 'the Company/Organization SalesForce ID' x-omitempty: false - example: 'abc134234adsdf43' project_sfid: - type: string + $ref: './common/properties/external-id.yaml' description: 'the project SalesForce ID' x-omitempty: false - example: 'a2g17000000hyxNAAA' project_name: - type: string - description: 'name of the salesforce project' - example: 'Appium' + $ref: './common/properties/project-name.yaml' x-omitempty: false company-cla-manager: @@ -4294,31 +4338,32 @@ definitions: type: string x-omitempty: false project_id: - type: string + $ref: './common/properties/internal-id.yaml' description: "The Project ID" x-omitempty: false - example: "e1e30240-a722-4c82-a648-121681d959c7" project_sfid: - type: string + $ref: './common/properties/external-id.yaml' description: "The Project SalesForce ID" x-omitempty: false - example: "a2g17000000hyxNAAA" project_name: - type: string - description: "The name of the SalesForce project" - example: "Appium" + $ref: './common/properties/cla-group-name.yaml' x-omitempty: false cla_group_name: $ref: './common/properties/cla-group-name.yaml' organization_name: - type: string - description: "The name of Salesforce organization" + $ref: './common/properties/company-name.yaml' + x-omitempty: false + signing_entity_name: + $ref: './common/properties/company-signing-entity-name.yaml' + x-omitempty: false + organization_id: + $ref: './common/properties/internal-id.yaml' + description: "The internal organization ID" x-omitempty: false - example: "Intel Corporation" organization_sfid: - type: string + $ref: './common/properties/external-id.yaml' + description: "The Salesforce organization ID" x-omitempty: false - example: "00117000015vpjXAAQ" cla-manager-user: type: object @@ -4412,7 +4457,7 @@ definitions: notify-cla-manager-list: type: object - title: Cla Manager list and contributor userID for given company and Project + title: CLA Manager list and contributor userID for given company and Project description: list of CLA Manager emails and contributor userID properties: list: @@ -4423,6 +4468,8 @@ definitions: type: string companyName: $ref: './common/properties/company-name.yaml' + signingEntityName: + $ref: './common/properties/company-signing-entity-name.yaml' claGroupName: $ref: './common/properties/cla-group-name.yaml' @@ -4434,7 +4481,7 @@ definitions: description: CLA Manager email name: type: string - + company-project-cla-list: type: object properties: @@ -4536,6 +4583,14 @@ definitions: description: The company name x-omitempty: false example: "The Linux Foundation" + company_id: + $ref: './common/properties/internal-id.yaml' + description: 'the Company ID' + x-omitempty: false + company_sfid: + $ref: './common/properties/external-id.yaml' + description: 'the Company/Organization SalesForce ID' + x-omitempty: false signing_entity_name: type: string description: The company signing entity name diff --git a/cla-backend-go/swagger/common/properties/company-signing-entity-name.yaml b/cla-backend-go/swagger/common/properties/company-signing-entity-name.yaml index 1448d6981..6d3497e3f 100644 --- a/cla-backend-go/swagger/common/properties/company-signing-entity-name.yaml +++ b/cla-backend-go/swagger/common/properties/company-signing-entity-name.yaml @@ -3,7 +3,7 @@ example: "Linux Foundation" type: string -description: Name of the company +description: Signing Entity Name of the Company # Pattern aligns with UI and other platform services including Org Service pattern: '[^<>]*' # allow everything except greater than and less than symbols minLength: 2 diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 37b3b9caf..3d2fd9d53 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -27,6 +27,9 @@ const EasyCLA403Forbidden = "EasyCLA - 403 Forbidden" // EasyCLA404NotFound common string for handler not found error messages const EasyCLA404NotFound = "EasyCLA - 404 Not Found" +// EasyCLA409Conflict common string for handler conflict error messages +const EasyCLA409Conflict = "EasyCLA - 409 Conflict" + // EasyCLA500InternalServerError common string for handler internal server error messages const EasyCLA500InternalServerError = "EasyCLA - 500 Internal Server Error" diff --git a/cla-backend-go/utils/responses.go b/cla-backend-go/utils/responses.go index df251d996..fd8ac3add 100644 --- a/cla-backend-go/utils/responses.go +++ b/cla-backend-go/utils/responses.go @@ -73,6 +73,24 @@ func ErrorResponseNotFoundWithError(reqID, msg string, err error) *models.ErrorR } } +// ErrorResponseConflict Helper function to generate a conflict error response +func ErrorResponseConflict(reqID, msg string) *models.ErrorResponse { + return &models.ErrorResponse{ + Code: String409, + Message: fmt.Sprintf("%s - %s", EasyCLA409Conflict, msg), + XRequestID: reqID, + } +} + +// ErrorResponseConflictWithError Helper function to generate a conflict error message +func ErrorResponseConflictWithError(reqID, msg string, err error) *models.ErrorResponse { + return &models.ErrorResponse{ + Code: String409, + Message: fmt.Sprintf("%s - %s - error: %+v", EasyCLA409Conflict, msg, err), + XRequestID: reqID, + } +} + // ErrorResponseInternalServerError Helper function to generate an internal server error response func ErrorResponseInternalServerError(reqID, msg string) *models.ErrorResponse { return &models.ErrorResponse{ diff --git a/cla-backend-go/v2/cla_manager/errors.go b/cla-backend-go/v2/cla_manager/errors.go new file mode 100644 index 000000000..13bf369d6 --- /dev/null +++ b/cla-backend-go/v2/cla_manager/errors.go @@ -0,0 +1,39 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package cla_manager + +import ( + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/cla_manager" +) + +// buildErrorMessageCreate helper function to build an error message +func buildErrorMessageCreate(params cla_manager.CreateCLAManagerParams, err error) string { + return fmt.Sprintf("problem creating new CLA Manager using company ID: %s, project SFID: %s, firstName: %s, lastName: %s, user email: %s, error: %+v", + params.CompanyID, params.ProjectSFID, *params.Body.FirstName, *params.Body.LastName, *params.Body.UserEmail, err) +} + +// buildErrorMessage helper function to build an error message +func buildErrorMessageDelete(params cla_manager.DeleteCLAManagerParams, err error) string { + return fmt.Sprintf("problem deleting new CLA Manager Request using company ID: %s, project SFID: %s, user ID: %s, error: %+v", + params.CompanyID, params.ProjectSFID, params.UserLFID, err) +} + +// buildErrorStatusCode helper function to build an error statusCodes +func buildErrorStatusCode(err error) string { + if err == ErrNoOrgAdmins || err == ErrCLACompanyNotFound || err == ErrClaGroupNotFound || err == ErrCLAUserNotFound { + return NotFound + } + // Check if user is already assigned scope/role + if err == ErrRoleScopeConflict { + return Conflict + } + // Check if user does exists + if err == ErrNoLFID { + return Accepted + } + // Return Bad Request + return BadRequest +} diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 8eb699b1e..0a67e154c 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -15,6 +15,7 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" + v1Company "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/cla_manager" @@ -36,34 +37,47 @@ const ( ) // Configure is the API handler routine for CLA Manager routes -func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, projectClaGroupRepo projects_cla_groups.Repository, easyCLAUserRepo v1User.RepositoryService) { +func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1Company.IService, LfxPortalURL string, projectClaGroupRepo projects_cla_groups.Repository, easyCLAUserRepo v1User.RepositoryService) { // nolint api.ClaManagerCreateCLAManagerHandler = cla_manager.CreateCLAManagerHandlerFunc(func(params cla_manager.CreateCLAManagerParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, params.CompanySFID, utils.DISALLOW_ADMIN_SCOPE) { - return cla_manager.NewCreateCLAManagerForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to CreateCLAManager with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID), - XRequestID: reqID, - }) + + f := logrus.Fields{ + "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "CompanyID": params.CompanyID, + "ProjectSFID": params.ProjectSFID, + "authUser": *params.XUSERNAME, + } + + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := v1CompanyService.GetCompany(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return cla_manager.NewCreateCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + log.WithFields(f).Debug("checking permissions...") + if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to DeleteCLAManager with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, params.CompanyID) + log.WithFields(f).Warn(msg) + return cla_manager.NewCreateCLAManagerForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } + + log.WithFields(f).Debug("looking up CLA Group for projectSFID...") cginfo, err := projectClaGroupRepo.GetClaGroupIDForProject(params.ProjectSFID) if err != nil { if err == projects_cla_groups.ErrProjectNotAssociatedWithClaGroup { - return cla_manager.NewCreateCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: BadRequest, - Message: fmt.Sprintf("EasyCLA - 400 Bad Request - No cla group associated with this project: %s", params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("no CLA Group associated with this project: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return cla_manager.NewCreateCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - return cla_manager.NewCreateCLAManagerInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "500", - Message: fmt.Sprintf("EasyCLA - 500 Internal server error. error = %s", err.Error()), - XRequestID: reqID, - }) + return cla_manager.NewCreateCLAManagerInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, err.Error(), err)) } + compCLAManager, errorResponse := service.CreateCLAManager(ctx, cginfo.ClaGroupID, params, authUser.UserName) if errorResponse != nil { if errorResponse.Code == BadRequest { @@ -80,21 +94,36 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, params.CompanySFID, utils.DISALLOW_ADMIN_SCOPE) { - return cla_manager.NewDeleteCLAManagerForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to DeleteCLAManager with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID), - XRequestID: reqID, - }) + f := logrus.Fields{ + "functionName": "cla_manager.handlers.ClaManagerDeleteCLAManagerHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "CompanyID": params.CompanyID, + "ProjectSFID": params.ProjectSFID, + "userLFID": params.UserLFID, + "authUser": *params.XUSERNAME, + } + + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := v1CompanyService.GetCompany(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + log.WithFields(f).Debug("checking permissions...") + if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to DeleteCLAManager with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, params.CompanyID) + log.WithFields(f).Warn(msg) + return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } + cginfo, err := projectClaGroupRepo.GetClaGroupIDForProject(params.ProjectSFID) if err != nil { - return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: BadRequest, - Message: fmt.Sprintf("EasyCLA - Bad Request. No Cla Group associated with ProjectSFID: %s ", params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("no CLA Group associated with this project: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } errResponse := service.DeleteCLAManager(ctx, cginfo.ClaGroupID, params) @@ -108,35 +137,35 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, api.ClaManagerCreateCLAManagerDesigneeHandler = cla_manager.CreateCLAManagerDesigneeHandlerFunc(func(params cla_manager.CreateCLAManagerDesigneeParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaManagerCreateCLAManagerDesigneeHandler", + "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerDesigneeHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "CompanySFID": params.CompanySFID, + "CompanyID": params.CompanyID, "ProjectSFID": params.ProjectSFID, "authUser": *params.XUSERNAME, } + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := v1CompanyService.GetCompany(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return cla_manager.NewCreateCLAManagerDesigneeBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + // Note: anyone create assign a CLA manager designee...no permissions checks - log.WithFields(f).Debugf("processing CLA Manager Desginee request") + log.WithFields(f).Debugf("processing create CLA Manager Desginee request") utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - claManagerDesignee, err := service.CreateCLAManagerDesignee(ctx, params.CompanySFID, params.ProjectSFID, params.Body.UserEmail.String()) + claManagerDesignee, err := service.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyExternalID, params.ProjectSFID, params.Body.UserEmail.String()) if err != nil { if err == ErrCLAManagerDesigneeConflict { msg := fmt.Sprintf("Conflict assigning cla manager role for Project SFID: %s ", params.ProjectSFID) - return cla_manager.NewCreateCLAManagerDesigneeByGroupConflict().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Message: msg, - Code: Conflict, - XRequestID: reqID, - }) + return cla_manager.NewCreateCLAManagerDesigneeByGroupConflict().WithXRequestID(reqID).WithPayload(utils.ErrorResponseConflictWithError(reqID, msg, err)) } msg := fmt.Sprintf("user :%s, error: %+v ", authUser.Email, err) - return cla_manager.NewCreateCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Message: msg, - Code: BadRequest, - XRequestID: reqID, - }) + return cla_manager.NewCreateCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } log.Debugf("CLA Manager designee created : %+v", claManagerDesignee) @@ -147,13 +176,14 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, func(params cla_manager.CreateCLAManagerDesigneeByGroupParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaManagerCreateCLAManagerDesigneeByGroupHandler", + "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerDesigneeByGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "CompanySFID": params.CompanySFID, + "CompanySFID": params.CompanyID, "ClaGroupID": params.ClaGroupID, "Email": params.Body.UserEmail.String(), - "authUser": *params.XUSERNAME, + "authUser": utils.StringValue(params.XUSERNAME), } // Note: anyone create assign a CLA manager designee...no permissions checks @@ -171,41 +201,28 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, XRequestID: reqID, }) } + log.WithFields(f).Debugf("found %d project IDs for CLA group", len(projectCLAGroups)) if len(projectCLAGroups) == 0 { msg := fmt.Sprintf("no projects associated with CLA Group: %s", params.ClaGroupID) log.WithFields(f).Warn(msg) - return cla_manager.NewCreateCLAManagerDesigneeByGroupNotFound().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Message: msg, - Code: BadRequest, - XRequestID: reqID, - }) - + return cla_manager.NewCreateCLAManagerDesigneeByGroupNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - designeeScopes, msg, err := service.CreateCLAManagerDesigneeByGroup(ctx, params, projectCLAGroups, f) + designeeScopes, msg, err := service.CreateCLAManagerDesigneeByGroup(ctx, params, projectCLAGroups) if err != nil { if err == ErrCLAManagerDesigneeConflict { - return cla_manager.NewCreateCLAManagerDesigneeByGroupConflict().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Message: msg, - Code: Conflict, - XRequestID: reqID, - }) + return cla_manager.NewCreateCLAManagerDesigneeByGroupConflict().WithXRequestID(reqID).WithPayload(utils.ErrorResponseConflictWithError(reqID, msg, err)) } log.WithFields(f).Warn(msg) - return cla_manager.NewCreateCLAManagerDesigneeByGroupBadRequest().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Message: msg, - Code: BadRequest, - XRequestID: reqID, - }) + return cla_manager.NewCreateCLAManagerDesigneeByGroupBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } + return cla_manager.NewCreateCLAManagerDesigneeByGroupOK().WithXRequestID(reqID).WithPayload(&models.ClaManagerDesignees{ List: designeeScopes, }) }) + api.ClaManagerIsCLAManagerDesigneeHandler = cla_manager.IsCLAManagerDesigneeHandlerFunc(func(params cla_manager.IsCLAManagerDesigneeParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint @@ -227,16 +244,12 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, api.ClaManagerInviteCompanyAdminHandler = cla_manager.InviteCompanyAdminHandlerFunc(func(params cla_manager.InviteCompanyAdminParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + // Get Contributor details user, userErr := easyCLAUserRepo.GetUser(params.UserID) if userErr != nil { msg := fmt.Sprintf("Problem getting user by ID : %s, error: %+v ", params.UserID, userErr) - return cla_manager.NewInviteCompanyAdminBadRequest().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Code: BadRequest, - Message: msg, - XRequestID: reqID, - }) + return cla_manager.NewInviteCompanyAdminBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, userErr)) } claManagerDesignees, err := service.InviteCompanyAdmin(ctx, params.Body.ContactAdmin, params.Body.CompanyID, *params.Body.ClaGroupID, params.Body.UserEmail.String(), params.Body.Name, &user, LfxPortalURL) @@ -288,25 +301,36 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, api.ClaManagerCreateCLAManagerRequestHandler = cla_manager.CreateCLAManagerRequestHandlerFunc(func(params cla_manager.CreateCLAManagerRequestParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaManagerCreateCLAManagerRequestHandler", + "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerRequestHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "CompanySFID": params.CompanySFID, + "CompanyID": params.CompanyID, "ProjectSFID": params.ProjectSFID, + "contactAdmin": params.Body.ContactAdmin, + "userFullName": utils.StringValue(params.Body.FullName), + "userEmail": params.Body.UserEmail.String(), "authUser": *params.XUSERNAME, } - log.WithFields(f).Debugf("processing CLA Manager request") - utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForOrganization(authUser, params.CompanySFID, utils.ALLOW_ADMIN_SCOPE) { - return cla_manager.NewCreateCLAManagerRequestForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to CreateCLAManagerRequest with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID), - XRequestID: reqID, - }) + + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := v1CompanyService.GetCompany(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return cla_manager.NewCreateCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - claManagerDesignee, err := service.CreateCLAManagerRequest(ctx, params.Body.ContactAdmin, params.CompanySFID, params.ProjectSFID, params.Body.UserEmail.String(), + // Check perms... + if !utils.IsUserAuthorizedForOrganization(authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to CreateCLAManagerRequest with Project|Organization scope of %s | %s", + authUser.UserName, params.ProjectSFID, v1CompanyModel.CompanyExternalID) + log.WithFields(f).Warn(msg) + return cla_manager.NewCreateCLAManagerRequestForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + claManagerDesignee, err := service.CreateCLAManagerRequest(ctx, params.Body.ContactAdmin, v1CompanyModel.CompanyExternalID, params.ProjectSFID, params.Body.UserEmail.String(), *params.Body.FullName, authUser, LfxPortalURL) if err != nil { @@ -347,48 +371,29 @@ func Configure(api *operations.EasyclaAPI, service Service, LfxPortalURL string, func(params cla_manager.NotifyCLAManagersParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "cla_manager.handlers.ClaManagerNotifyCLAManagersHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyName": params.Body.CompanyName, + "signingEntityName": params.Body.SigningEntityName, + "userID": params.Body.UserID, + "claGroupName": params.Body.ClaGroupName, + } + log.WithFields(f).Debug("notifying CLA managers...") err := service.NotifyCLAManagers(ctx, params.Body, LfxPortalURL) if err != nil { if err == ErrCLAUserNotFound { + msg := fmt.Sprintf("unable to notify cla managers - user not found: %s", params.Body.UserID) + log.WithFields(f).WithError(err).Warn(err) return cla_manager.NewNotifyCLAManagersNotFound().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFound( - reqID, - fmt.Sprintf("unable to notify cla managers - user not found: %s", params.Body.UserID))) + utils.ErrorResponseNotFound(reqID, msg)) } - return cla_manager.NewNotifyCLAManagersBadRequest().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to notify cla managers - cla group: %s, company: %s", params.Body.ClaGroupName, params.Body.CompanyName), err)) + + msg := fmt.Sprintf("unable to notify cla managers - cla group: %s, company: %s", params.Body.ClaGroupName, params.Body.CompanyName) + log.WithFields(f).WithError(err).Warn(err) + return cla_manager.NewNotifyCLAManagersBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } return cla_manager.NewNotifyCLAManagersNoContent().WithXRequestID(reqID) }) - -} - -// buildErrorMessageCreate helper function to build an error message -func buildErrorMessageCreate(params cla_manager.CreateCLAManagerParams, err error) string { - return fmt.Sprintf("problem creating new CLA Manager using company SFID: %s, project SFID: %s, firstName: %s, lastName: %s, user email: %s, error: %+v", - params.CompanySFID, params.ProjectSFID, *params.Body.FirstName, *params.Body.LastName, *params.Body.UserEmail, err) -} - -// buildErrorMessage helper function to build an error message -func buildErrorMessageDelete(params cla_manager.DeleteCLAManagerParams, err error) string { - return fmt.Sprintf("problem deleting new CLA Manager Request using company SFID: %s, project SFID: %s, user ID: %s, error: %+v", - params.CompanySFID, params.ProjectSFID, params.UserLFID, err) -} - -// buildErrorStatusCode helper function to build an error statusCodes -func buildErrorStatusCode(err error) string { - if err == ErrNoOrgAdmins || err == ErrCLACompanyNotFound || err == ErrClaGroupNotFound || err == ErrCLAUserNotFound { - return NotFound - } - // Check if user is already assigned scope/role - if err == ErrRoleScopeConflict { - return Conflict - } - // Check if user does not exiss - if err == ErrNoLFID { - return Accepted - } - // Return Bad Request - return BadRequest } diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index d22ed03fb..9f9dff0bd 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -95,9 +95,9 @@ type Service interface { DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL string) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) - CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) + CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companySFID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, LfxPortalURL string) error - CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, f logrus.Fields) ([]*models.ClaManagerDesignee, string, error) + CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) } @@ -120,20 +120,20 @@ func NewService(compService company.IService, projService project.Service, mgrSe // CreateCLAManager creates Cla Manager func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) { f := logrus.Fields{ - "functionName": "CreateCLAManager", + "functionName": "cla_manager.service.CreateCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, "authUsername": authUsername, "xUserName": params.XUSERNAME, "xEmail": params.XEMAIL, } - // Search for salesForce Company aka external Company - log.WithFields(f).Debugf("Getting company by external ID : %s", params.CompanySFID) - companyModel, companyErr := s.companyService.GetCompanyByExternalID(ctx, params.CompanySFID) - if companyErr != nil || companyModel == nil { + // Search for Company by internal ID + log.WithFields(f).Debugf("Getting company by ID: %s", params.CompanyID) + v1CompanyModel, companyErr := s.companyService.GetCompany(ctx, params.CompanyID) + if companyErr != nil || v1CompanyModel == nil { msg := buildErrorMessage("company lookup error", claGroupID, params, companyErr) log.WithFields(f).Warn(msg) return nil, &models.ErrorResponse{ @@ -141,6 +141,8 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param Code: "400", } } + f["companySFID"] = v1CompanyModel.CompanyExternalID + f["companyName"] = v1CompanyModel.CompanyName claGroup, err := s.projectService.GetCLAGroupByID(ctx, claGroupID) if err != nil || claGroup == nil { @@ -151,6 +153,7 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param Code: "400", } } + // Get user by email userServiceClient := v2UserService.GetClient() // Get Manager lf account by username. Used for email content @@ -180,7 +183,7 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param } // Check if user exists in easyCLA DB, if not add User - log.WithFields(f).Debugf("Checking user: %+v in easyCLA records", user) + log.WithFields(f).Debugf("Checking user: %+v in EasyCLA database...", user) claUser, claUserErr := s.easyCLAUserService.GetUserByLFUserName(user.Username) if claUserErr != nil { msg := fmt.Sprintf("Problem getting claUser by :%s, error: %+v ", user.Username, claUserErr) @@ -197,7 +200,8 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param userName := fmt.Sprintf("%s %s", *params.Body.FirstName, *params.Body.LastName) _, currentTimeString := utils.CurrentTime() claUserModel := &v1Models.User{ - UserExternalID: params.CompanySFID, + CompanyID: v1CompanyModel.CompanyID, + UserExternalID: v1CompanyModel.CompanyExternalID, LfEmail: *user.Emails[0].EmailAddress, Admin: true, LfUsername: user.Username, @@ -231,7 +235,7 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param } // Add CLA Manager to Database - signature, addErr := s.managerService.AddClaManager(ctx, companyModel.CompanyID, claGroupID, user.Username) + signature, addErr := s.managerService.AddClaManager(ctx, v1CompanyModel.CompanyID, claGroupID, user.Username) if addErr != nil { msg := buildErrorMessageCreate(params, addErr) log.WithFields(f).Warn(msg) @@ -241,7 +245,7 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param } } if signature == nil { - sigMsg := fmt.Sprintf("Signature not found for project: %s and company: %s ", claGroupID, companyModel.CompanyID) + sigMsg := fmt.Sprintf("Signature not found for project: %s and company: %s ", claGroupID, v1CompanyModel.CompanyID) log.WithFields(f).Warn(sigMsg) return nil, &models.ErrorResponse{ Message: sigMsg, @@ -258,35 +262,26 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param ClaGroupName: claGroup.ProjectName, ProjectID: claGroupID, ProjectName: projectSF.Name, - OrganizationName: companyModel.CompanyName, - OrganizationSfid: params.CompanySFID, + OrganizationName: v1CompanyModel.CompanyName, + OrganizationSfid: v1CompanyModel.CompanyExternalID, + OrganizationID: v1CompanyModel.CompanyID, Name: fmt.Sprintf("%s %s", user.FirstName, user.LastName), } + return claCompanyManager, nil } func (s *service) DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse { f := logrus.Fields{ - "functionName": "DeleteCLAManager", + "functionName": "cla_manager.service.DeleteCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, "xUserName": params.XUSERNAME, "xEmail": params.XEMAIL, } - // Search for salesForce Company aka external Company - companyModel, companyErr := s.companyService.GetCompanyByExternalID(ctx, params.CompanySFID) - if companyErr != nil || companyModel == nil { - msg := buildErrorMessageDelete(params, companyErr) - log.WithFields(f).Warn(msg) - return &models.ErrorResponse{ - Message: msg, - Code: "400", - } - } - - signature, deleteErr := s.managerService.RemoveClaManager(ctx, companyModel.CompanyID, claGroupID, params.UserLFID) + signature, deleteErr := s.managerService.RemoveClaManager(ctx, params.CompanyID, claGroupID, params.UserLFID) if deleteErr != nil { msg := buildErrorMessageDelete(params, deleteErr) @@ -296,8 +291,9 @@ func (s *service) DeleteCLAManager(ctx context.Context, claGroupID string, param Code: "400", } } + if signature == nil { - msg := fmt.Sprintf("Not found signature for project: %s and company: %s ", claGroupID, companyModel.CompanyID) + msg := fmt.Sprintf("CCLA signature not found for project: %s and company: %s ", claGroupID, params.CompanyID) log.WithFields(f).Warn(msg) return &models.ErrorResponse{ Message: msg, @@ -309,11 +305,11 @@ func (s *service) DeleteCLAManager(ctx context.Context, claGroupID string, param } //CreateCLAManagerDesignee creates designee for cla manager prospect -func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID string, projectSFID string, userEmail string) (*models.ClaManagerDesignee, error) { +func (s *service) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectSFID string, userEmail string) (*models.ClaManagerDesignee, error) { f := logrus.Fields{ - "functionName": "CreateCLAManagerDesignee", + "functionName": "cla_manager.service.CreateCLAManagerDesignee", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companySFID": companySFID, + "companyID": companyID, "projectSFID": projectSFID, "userEmail": userEmail, } @@ -323,12 +319,14 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID stri orgClient := v2OrgService.GetClient() projectClient := v2ProjectService.GetClient() - log.WithFields(f).Debugf("loading company by external ID...") - v1CompanyModel, companyErr := s.companyService.GetCompanyByExternalID(ctx, companySFID) + log.WithFields(f).Debugf("loading company by ID...") + v1CompanyModel, companyErr := s.companyService.GetCompany(ctx, companyID) if companyErr != nil { log.WithFields(f).Warnf("company not found, error: %+v", companyErr) return nil, companyErr } + f["companySFID"] = v1CompanyModel.CompanyExternalID + f["companyName"] = v1CompanyModel.CompanyName log.WithFields(f).Debugf("checking if company/project is signed with CLA managers...") isSigned, signedErr := s.isSigned(ctx, v1CompanyModel, projectSFID) @@ -357,7 +355,7 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID stri log.WithFields(f).Debugf("checking if user has %s role scope...", utils.CLADesigneeRole) // Check if user is already CLA Manager designee of project|organization scope - hasRoleScope, hasRoleScopeErr := orgClient.IsUserHaveRoleScope(ctx, utils.CLADesigneeRole, lfxUser.ID, companySFID, projectSFID) + hasRoleScope, hasRoleScopeErr := orgClient.IsUserHaveRoleScope(ctx, utils.CLADesigneeRole, lfxUser.ID, v1CompanyModel.CompanyExternalID, projectSFID) if hasRoleScopeErr != nil { // Skip 404 for ListOrgUsrServiceScopes endpoint if _, ok := hasRoleScopeErr.(*organizations.ListOrgUsrServiceScopesNotFound); !ok { @@ -385,18 +383,18 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID stri } log.WithFields(f).Debugf("creating user role organization scope for user: %s, with role: %s with role ID: %s using project|org: %s|%s...", - userEmail, utils.CLADesigneeRole, roleID, projectSFID, companySFID) - scopeErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, userEmail, projectSFID, companySFID, roleID) + userEmail, utils.CLADesigneeRole, roleID, projectSFID, v1CompanyModel.CompanyExternalID) + scopeErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, userEmail, projectSFID, v1CompanyModel.CompanyExternalID, roleID) if scopeErr != nil { // Ignore conflict - role has already been assigned - otherwise, return error if _, ok := scopeErr.(*organizations.CreateOrgUsrRoleScopesConflict); !ok { - log.Warn(fmt.Sprintf("Problem creating projectOrg scope for email: %s , projectSFID: %s, companyID: %s", userEmail, projectSFID, companySFID)) + log.Warn(fmt.Sprintf("problem creating projectOrg scope for email: %s , projectSFID: %s, companySFID: %s", userEmail, projectSFID, v1CompanyModel.CompanyExternalID)) return nil, scopeErr } } log.WithFields(f).Debugf("created user role organization scope for user: %s, with role: %s with role ID: %s using project|org: %s|%s...", - userEmail, utils.CLADesigneeRole, roleID, projectSFID, companySFID) + userEmail, utils.CLADesigneeRole, roleID, projectSFID, v1CompanyModel.CompanyExternalID) // Log Event s.eventService.LogEvent( @@ -409,7 +407,7 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID stri UserModel: &v1Models.User{LfUsername: lfxUser.Username, UserID: lfxUser.ID}, EventData: &events.AssignRoleScopeData{ Role: "cla-manager-designee", - Scope: fmt.Sprintf("%s|%s", projectSFID, companySFID), + Scope: fmt.Sprintf("%s|%s", projectSFID, v1CompanyModel.CompanyExternalID), }, }) @@ -420,7 +418,8 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID stri AssignedOn: time.Now().String(), Email: strfmt.Email(userEmail), ProjectSfid: projectSFID, - CompanySfid: companySFID, + CompanySfid: v1CompanyModel.CompanyExternalID, + CompanyID: v1CompanyModel.CompanyID, ProjectName: projectSF.Name, } @@ -428,11 +427,12 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companySFID stri } func (s *service) IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) { - f := logrus.Fields{ - "functionName": "IsCLAManagerDesignee", - "claGroupID": claGroupID, - "userLFID": userLFID, + "functionName": "cla_manager.service.IsCLAManagerDesignee", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companySFID": companySFID, + "claGroupID": claGroupID, + "userLFID": userLFID, } // Get LF User @@ -545,7 +545,15 @@ func (s *service) IsCLAManagerDesignee(ctx context.Context, companySFID, claGrou } //CreateCLAManagerDesigneeByGroup creates designee by group for cla manager prospect -func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, f logrus.Fields) ([]*models.ClaManagerDesignee, string, error) { +func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) { + f := logrus.Fields{ + "functionName": "cla_manager.service.CreateCLAManagerDesigneeByGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": params.ClaGroupID, + "companyID": params.CompanyID, + "userEmail": params.Body.UserEmail.String(), + } + var designeeScopes []*models.ClaManagerDesignee userEmail := params.Body.UserEmail.String() @@ -556,9 +564,20 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl return nil, msg, signedErr } + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := s.companyService.GetCompany(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return nil, msg, err + } + f["companySFID"] = v1CompanyModel.CompanyExternalID + f["companyName"] = v1CompanyModel.CompanyName + if signedAtFoundationLevel { if foundationSFID != "" { - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, params.CompanySFID, foundationSFID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyExternalID, foundationSFID, userEmail) if err != nil { if err == ErrCLAManagerDesigneeConflict { msg := fmt.Sprintf("Conflict assigning cla manager role for Foundation SFID: %s ", foundationSFID) @@ -589,7 +608,7 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl go func(swg *sync.WaitGroup, pcg *projects_cla_groups.ProjectClaGroup, designeeChannel chan *result) { defer swg.Done() log.WithFields(f).Debugf("creating CLA Manager Designee for Project SFID: %s", pcg.ProjectSFID) - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, params.CompanySFID, pcg.ProjectSFID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyExternalID, pcg.ProjectSFID, userEmail) var output result if err != nil { if err == ErrCLAManagerDesigneeConflict { @@ -631,7 +650,7 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl // CreateCLAManagerRequest service method func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companySFID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) { f := logrus.Fields{ - "functionName": "CreateCLAManagerRequest", + "functionName": "cla_manager.service.CreateCLAManagerRequest", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "contactAdmin": contactAdmin, "companySFID": companySFID, @@ -652,6 +671,8 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.Warn(msg) return nil, companyErr } + f["companyID"] = v1CompanyModel.CompanyID + f["companyName"] = v1CompanyModel.CompanyName // Determine if the CCLA is already signed or not log.WithFields(f).Debugf("checking if company/project is signed with CLA managers...") @@ -813,16 +834,18 @@ func (s *service) ValidateInviteCompanyAdminCheck(ctx context.Context, f logrus. } func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, LfxPortalURL string) ([]*models.ClaManagerDesignee, error) { - orgService := v2OrgService.GetClient() - projectService := v2ProjectService.GetClient() - userService := v2UserService.GetClient() f := logrus.Fields{ - "functionName": "InviteCompanyAdmin", + "functionName": "cla_manager.service.InviteCompanyAdmin", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "claGroupID": projectID, "userEmail": userEmail, - "name": name} + "name": name, + } + + orgService := v2OrgService.GetClient() + projectService := v2ProjectService.GetClient() + userService := v2UserService.GetClient() validateError := s.ValidateInviteCompanyAdminCheck(ctx, f, projectID, contactAdmin, userEmail, name, contributor) if validateError != nil { @@ -1045,24 +1068,42 @@ func validateInviteCompanyAdmin(contactAdmin bool, userEmail string, name string } func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, LfxPortalURL string) error { + f := logrus.Fields{ + "functionName": "cla_manager.service.NotifyCLAManagers", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyName": notifyCLAManagers.CompanyName, + "signingEntityName": notifyCLAManagers.SigningEntityName, + "userID": notifyCLAManagers.UserID, + "claGroupName": notifyCLAManagers.ClaGroupName, + } // Search for Easy CLA User - log.Debugf("Getting user by ID: %s", notifyCLAManagers.UserID) + log.WithFields(f).Debugf("Getting user by ID: %s", notifyCLAManagers.UserID) userModel, userErr := s.easyCLAUserService.GetUser(notifyCLAManagers.UserID) if userErr != nil { msg := fmt.Sprintf("Problem getting user by ID: %s ", notifyCLAManagers.UserID) - log.Warn(msg) + log.WithFields(f).Warn(msg) return ErrCLAUserNotFound } - log.Debugf("Sending notification emails to claManagers: %+v", notifyCLAManagers.List) + log.Debugf("Sending notification emails to CLA Managers: %+v", notifyCLAManagers.List) for _, claManager := range notifyCLAManagers.List { - sendEmailToCLAManager(claManager.Name, claManager.Email.String(), userModel, notifyCLAManagers.CompanyName, notifyCLAManagers.ClaGroupName, LfxPortalURL) + sendEmailToCLAManager(ctx, claManager.Name, claManager.Email.String(), userModel, notifyCLAManagers.CompanyName, notifyCLAManagers.SigningEntityName, notifyCLAManagers.ClaGroupName, LfxPortalURL) } return nil } -func sendEmailToCLAManager(manager string, managerEmail string, userModel *v1Models.User, company string, claGroupName string, lfxPortalURL string) { +func sendEmailToCLAManager(ctx context.Context, manager string, managerEmail string, userModel *v1Models.User, company, signingEntityName, claGroupName, lfxPortalURL string) { + f := logrus.Fields{ + "functionName": "cla_manager.service.sendEmailToCLAManager", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "manager": manager, + "managerEmail": managerEmail, + "user": userModel.Username, + "companyName": company, + "signingEntityName": signingEntityName, + "claGroupName": claGroupName, + } subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(userModel)) recipients := []string{managerEmail} body := fmt.Sprintf(` @@ -1070,18 +1111,20 @@ func sendEmailToCLAManager(manager string, managerEmail string, userModel *v1Mod

This is a notification email from EasyCLA regarding the organization %s.

The following contributor would like to submit a contribution to the %s CLA Group and is requesting to be approved as a contributor for your organization:

-

%s

+

%s - Signing Entity Name: %s

Approval can be done at %s

Please notify the contributor once they are added so that they may complete the contribution process.

%s %s`, - manager, company, claGroupName, getFormattedUserDetails(userModel), lfxPortalURL, + manager, company, signingEntityName, claGroupName, getFormattedUserDetails(userModel), lfxPortalURL, utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) + + log.WithFields(f).Debugf("sending email with subject: %s to recipients: %+v...", subject, recipients) err := utils.SendEmail(subject, body, recipients) if err != nil { - log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { - log.Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) } } @@ -1160,7 +1203,7 @@ func getFormattedUserDetails(model *v1Models.User) string { // isSigned is a helper function to check if project/claGroup is signed func (s *service) isSigned(ctx context.Context, companyModel *v1Models.Company, projectID string) (bool, error) { f := logrus.Fields{ - "functionName": "isSigned", + "functionName": "cla_manager.service.isSigned", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyModel.CompanyID, "companyName": companyModel.CompanyName, @@ -1171,7 +1214,7 @@ func (s *service) isSigned(ctx context.Context, companyModel *v1Models.Company, f["companyID"] = companyModel.CompanyID f["companyName"] = companyModel.CompanyName log.WithFields(f).Debug("loading CLA Managers for company/project") - claManagers, err := s.v2CompanyService.GetCompanyProjectCLAManagers(ctx, companyModel.CompanyID, companyModel.CompanyExternalID, projectID) + claManagers, err := s.v2CompanyService.GetCompanyProjectCLAManagers(ctx, companyV1toV2(companyModel), projectID) if err != nil { msg := fmt.Sprintf("EasyCLA - 400 Bad Request : %v", err) log.WithFields(f).Warn(msg) @@ -1248,7 +1291,7 @@ func contributorEmailToOrgAdmin(adminEmail string, admin string, company string, func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, corporateConsole string, companyName string, projectNames []string, designeeEmail string, designeeName string, senderEmail string, senderName string) { f := logrus.Fields{ - "functionName": "sendEmailToCLAManagerDesigneeCorporate", + "functionName": "cla_manager.service.sendEmailToCLAManagerDesigneeCorporate", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "corporateConsole": corporateConsole, "companyName": companyName, @@ -1286,7 +1329,7 @@ Either you or someone whom to designate from your company can login to this port func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, companyName string, projectNames []string, designeeEmail string, designeeName string, contributorID string, contributorName string) { f := logrus.Fields{ - "functionName": "sendEmailToCLAManagerDesignee", + "functionName": "cla_manager.service.sendEmailToCLAManagerDesignee", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "corporateConsole": corporateConsole, "companyName": companyName, @@ -1323,7 +1366,7 @@ func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string) error { f := logrus.Fields{ - "functionName": "sendDesigneeEmailToUserWithNoLFID", + "functionName": "cla_manager.service.sendDesigneeEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "userWithNoLFIDName": userWithNoLFIDName, "userWithNoLFIDEmail": userWithNoLFIDEmail, @@ -1356,7 +1399,7 @@ func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, requesterUsername, r // sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID func sendEmailToUserWithNoLFID(ctx context.Context, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error { f := logrus.Fields{ - "functionName": "sendEmailToUserWithNoLFID", + "functionName": "cla_manager.service.sendEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectName": projectName, "requesterUsername": requesterUsername, @@ -1393,13 +1436,13 @@ Once complete, notify the user %s and they will be able to add you as a CLA Mana // buildErrorMessage helper function to build an error message func buildErrorMessage(errPrefix string, claGroupID string, params cla_manager.CreateCLAManagerParams, err error) string { - return fmt.Sprintf("%s - problem creating new CLA Manager Request using company SFID: %s, project ID: %s, first name: %s, last name: %s, user email: %s, error: %+v", - errPrefix, params.CompanySFID, claGroupID, *params.Body.FirstName, *params.Body.LastName, *params.Body.UserEmail, err) + return fmt.Sprintf("%s - problem creating new CLA Manager Request using company ID: %s, project ID: %s, first name: %s, last name: %s, user email: %s, error: %+v", + errPrefix, params.CompanyID, claGroupID, *params.Body.FirstName, *params.Body.LastName, *params.Body.UserEmail, err) } func (s *service) convertGHUserToContact(ctx context.Context, contributor *v1User.User) error { f := logrus.Fields{ - "functionName": "convertGHUserToContact", + "functionName": "cla_manager.service.convertGHUserToContact", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } @@ -1437,3 +1480,18 @@ func (s *service) convertGHUserToContact(ctx context.Context, contributor *v1Use } return nil } + +func companyV1toV2(v1CompanyModel *v1Models.Company) *models.Company { + return &models.Company{ + CompanyACL: v1CompanyModel.CompanyACL, + CompanyID: v1CompanyModel.CompanyID, + CompanyExternalID: v1CompanyModel.CompanyExternalID, + CompanyName: v1CompanyModel.CompanyName, + SigningEntityName: v1CompanyModel.SigningEntityName, + CompanyManagerID: v1CompanyModel.CompanyManagerID, + Note: v1CompanyModel.Note, + Created: v1CompanyModel.Created, + Updated: v1CompanyModel.Updated, + Version: v1CompanyModel.Version, + } +} diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 393a92f85..04c450fa7 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -26,48 +26,38 @@ import ( ) // Configure sets up the middleware handlers -func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Company.IRepository, projectClaGroupRepo projects_cla_groups.Repository, LFXPortalURL, v1CorporateConsole string) { // nolint +func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo projects_cla_groups.Repository, LFXPortalURL, v1CorporateConsole string) { // nolint - const msgUnableToLoadCompany = "unable to load company external ID" api.CompanyGetCompanyProjectClaManagersHandler = company.GetCompanyProjectClaManagersHandlerFunc( func(params company.GetCompanyProjectClaManagersParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "CompanyGetCompanyProjectClaManagersHandler", + "functionName": "company.handlers.CompanyGetCompanyProjectClaManagersHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, } - log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, params.CompanySFID, utils.ALLOW_ADMIN_SCOPE) { - return company.NewGetCompanyProjectClaManagersForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Company Project CLA Managers with Organization scope of %s", - authUser.UserName, params.CompanySFID), - XRequestID: reqID, - }) - } - comp, err := v1CompanyRepo.GetCompanyByExternalID(ctx, params.CompanySFID) - if err != nil { - msg := "unable to load company by SFID" + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) log.WithFields(f).WithError(err).Warn(msg) - if err == v1Company.ErrCompanyDoesNotExist { - return company.NewGetCompanyProjectClaManagersNotFound().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFoundWithError(reqID, msg, err)) - } - return company.NewGetCompanyProjectClaManagersNotFound().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + return company.NewGetCompanyProjectClaManagersBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - if comp == nil { - log.WithFields(f).WithError(err).Warn(msgUnableToLoadCompany) - return company.NewGetCompanyProjectClaManagersNotFound().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFound(reqID, msgUnableToLoadCompany)) + + log.WithFields(f).Debug("checking permissions") + if !utils.IsUserAuthorizedForOrganization(authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to GetCompanyProjectClaManagers with Project|Organization scope of %s | %s", + authUser.UserName, params.ProjectSFID, v1CompanyModel.CompanyExternalID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectClaManagersForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.GetCompanyProjectCLAManagers(ctx, comp.CompanyID, params.CompanySFID, params.ProjectSFID) + result, err := service.GetCompanyProjectCLAManagers(ctx, v1CompanyModel, params.ProjectSFID) if err != nil { msg := "unable to load company project CLA managers" log.WithFields(f).WithError(err).Warn(msg) @@ -84,7 +74,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "CompanyGetCompanyCLAGroupManagersHandler", + "functionName": "company.handlers.CompanyGetCompanyCLAGroupManagersHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "companyID": params.CompanyID, @@ -111,49 +101,43 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "CompanyGetCompanyProjectActiveClaHandler", + "functionName": "company.handlers.CompanyGetCompanyProjectActiveClaHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, } - log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, params.CompanySFID, utils.ALLOW_ADMIN_SCOPE) { - return company.NewGetCompanyProjectActiveClaForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to CreateCLAManager with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID), - XRequestID: reqID, - }) + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - comp, err := v1CompanyRepo.GetCompanyByExternalID(ctx, params.CompanySFID) - if err != nil { - if err == v1Company.ErrCompanyDoesNotExist { - return company.NewGetCompanyProjectActiveClaNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("Company not found with given ID. [%s]", params.CompanySFID), - XRequestID: reqID, - }) - } - } - if comp == nil { - log.WithFields(f).WithError(err).Warn(msgUnableToLoadCompany) - return company.NewGetCompanyProjectActiveClaNotFound().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFound(reqID, msgUnableToLoadCompany)) + log.WithFields(f).Debug("checking permissions") + if !utils.IsUserAuthorizedForOrganization(authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to GetCompanyProjectActiveCla with Project|Organization scope of %s | %s", + authUser.UserName, params.ProjectSFID, v1CompanyModel.CompanyExternalID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectActiveClaForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.GetCompanyProjectActiveCLAs(ctx, comp.CompanyID, params.ProjectSFID) + log.WithFields(f).Debug("getting company project active CLAs...") + result, err := service.GetCompanyProjectActiveCLAs(ctx, v1CompanyModel.CompanyID, params.ProjectSFID) if err != nil { if strings.ContainsAny(err.Error(), "getProjectNotFound") { - return company.NewGetCompanyProjectActiveClaNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("clagroup not found with given ID. [%s]", params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("CLA Group not found with given project SFID: %s", params.ProjectSFID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectActiveClaNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbiddenWithError(reqID, msg, err)) } - return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + + msg := fmt.Sprintf("error looking up active project CLAs by internal company ID: %s and project SFID: %s", v1CompanyModel.CompanyID, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } + return company.NewGetCompanyProjectActiveClaOK().WithXRequestID(reqID).WithPayload(result) }) @@ -163,7 +147,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "CompanyGetCompanyProjectContributorsHandler", + "functionName": "company.handlers.CompanyGetCompanyProjectContributorsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companySFID": params.CompanySFID, @@ -197,7 +181,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "CompanyGetCompanyProjectClaHandler", + "functionName": "company.handlers.CompanyGetCompanyProjectClaHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companySFID": params.CompanySFID, @@ -235,7 +219,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "CompanyCreateCompanyHandler", + "functionName": "company.handlers.CompanyCreateCompanyHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "userID": params.UserID, "companyName": aws.StringValue(params.Input.CompanyName), @@ -274,7 +258,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "CompanyGetCompanyByNameHandler", + "functionName": "company.handlers.CompanyGetCompanyByNameHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": params.CompanyName, } @@ -297,13 +281,43 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp return company.NewGetCompanyByNameOK().WithXRequestID(reqID).WithPayload(companyModel) }) + api.CompanyGetCompanyBySigningEntityNameHandler = company.GetCompanyBySigningEntityNameHandlerFunc( + func(params company.GetCompanyBySigningEntityNameParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "company.handlers.CompanyGetCompanyByNameHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "signingEntityName": params.SigningEntityName, + } + + // Anyone can query for a company by signing entity name + + log.WithFields(f).Debug("loading company by name") + companyModel, err := service.GetCompanyBySigningEntityName(ctx, params.SigningEntityName) + if err != nil { + msg := fmt.Sprintf("unable to locate company by signing entity name: %s", params.SigningEntityName) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyBySigningEntityNameBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + if companyModel == nil { + msg := fmt.Sprintf("unable to locate company by signing entity name: %s", params.SigningEntityName) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyBySigningEntityNameNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + return company.NewGetCompanyBySigningEntityNameOK().WithXRequestID(reqID).WithPayload(companyModel) + }) + api.CompanyDeleteCompanyByIDHandler = company.DeleteCompanyByIDHandlerFunc( func(params company.DeleteCompanyByIDParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "CompanyDeleteCompanyByIDHandler", + "functionName": "company.handlers.CompanyDeleteCompanyByIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": params.CompanyID, } @@ -355,7 +369,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "CompanyDeleteCompanyBySFIDHandler", + "functionName": "company.handlers.CompanyDeleteCompanyBySFIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, } @@ -410,7 +424,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "CompanyContributorAssociationHandler", + "functionName": "company.handlers.CompanyContributorAssociationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, "userEmail": params.Body.UserEmail.String(), @@ -436,7 +450,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "CompanyContributorAssociationHandler", + "functionName": "company.handlers.CompanyContributorAssociationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, } @@ -536,7 +550,7 @@ func errorResponse(reqID string, err error) *models.ErrorResponse { // isUserHaveAccessToCLAProjectOrganization is a helper function to determine if the user has access to the specified project and organization func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *auth.User, projectSFID, organizationSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { f := logrus.Fields{ - "functionName": "isUserHaveAccessToCLAProjectOrganization", + "functionName": "company.handlers.isUserHaveAccessToCLAProjectOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "organizationSFID": organizationSFID, diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 4db52ef49..375708015 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -85,12 +85,13 @@ const ( // Service functions for company type Service interface { - GetCompanyProjectCLAManagers(ctx context.Context, companyID, companySFID, projectSFID string) (*models.CompanyClaManagers, error) + GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyModel *models.Company, projectSFID string) (*models.CompanyClaManagers, error) GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string) (*models.CompanyProjectClaList, error) CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string) (*models.CompanyOutput, error) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) + GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) GetCompanyByID(ctx context.Context, companyID string) (*models.Company, error) GetCompanyBySFID(ctx context.Context, companySFID string) (*models.Company, error) DeleteCompanyByID(ctx context.Context, companyID string) error @@ -125,13 +126,17 @@ func NewService(v1CompanyService v1Company.IService, sigRepo signatures.Signatur } } -func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, companyID, companySFID, projectSFID string) (*models.CompanyClaManagers, error) { +func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyModel *models.Company, projectSFID string) (*models.CompanyClaManagers, error) { f := logrus.Fields{ - "functionName": "GetCompanyProjectCLAManagers", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "companyID": companyID, + "functionName": "GetCompanyProjectCLAManagers", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "companyID": v1CompanyModel.CompanyID, + "companySFID": v1CompanyModel.CompanyExternalID, + "companyName": v1CompanyModel.CompanyName, + "signingEntityName": v1CompanyModel.SigningEntityName, } + log.WithFields(f).Debugf("locating CLA Group(s) under project or foundation...") var err error claGroups, err := s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) @@ -142,9 +147,9 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, companyID, c // get the org client for org info filling orgClient := orgService.GetClient() - orgModel, err := orgClient.GetOrganization(ctx, companySFID) + orgModel, err := orgClient.GetOrganization(ctx, v1CompanyModel.CompanyExternalID) if err != nil { - return nil, fmt.Errorf("fetching org model failed for companySFID : %s : %w", companySFID, err) + return nil, fmt.Errorf("fetching org model failed for companySFID : %s : %w", v1CompanyModel.CompanyExternalID, err) } signed, approved := true, true @@ -157,7 +162,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, companyID, c log.WithFields(f).Debugf("claGroupID missing for project : %s ", claGroup.ProjectSFID) continue } - sig, sigErr := s.signatureRepo.GetProjectCompanySignature(ctx, companyID, claGroup.ClaGroupID, &signed, &approved, nil, &maxLoad) + sig, sigErr := s.signatureRepo.GetProjectCompanySignature(ctx, v1CompanyModel.CompanyID, claGroup.ClaGroupID, &signed, &approved, nil, &maxLoad) if sigErr != nil { log.WithFields(f).Warnf("problem fetching CLA signatures, error: %+v", sigErr) return nil, sigErr @@ -178,11 +183,13 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, companyID, c for _, user := range sig.SignatureACL { claManagers = append(claManagers, &models.CompanyClaManager{ // DB doesn't have approved_on value - ApprovedOn: sig.SignatureCreated, - LfUsername: user.LfUsername, - ProjectID: sig.ProjectID, - OrganizationSfid: companySFID, - OrganizationName: orgModel.Name, + ApprovedOn: sig.SignatureCreated, + LfUsername: user.LfUsername, + ProjectID: sig.ProjectID, + OrganizationSfid: v1CompanyModel.CompanyExternalID, + OrganizationID: v1CompanyModel.CompanyID, + OrganizationName: orgModel.Name, + SigningEntityName: v1CompanyModel.SigningEntityName, }) lfUsernames.Add(user.LfUsername) } @@ -205,9 +212,9 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, companyID, c // fill project info fillProjectInfo(claManagers, claGroups) // fetch the cla_manager.added events so can fill the addedOn field - claManagerAddedEvents, err := s.eventService.GetCompanyEvents(companyID, events.ClaManagerCreated, nil, aws.Int64(100), true) + claManagerAddedEvents, err := s.eventService.GetCompanyEvents(v1CompanyModel.CompanyID, events.ClaManagerCreated, nil, aws.Int64(100), true) if err != nil { - log.WithFields(f).Warnf("fetching events for companyID failed : %s : %v", companyID, err) + log.WithFields(f).Warnf("fetching events for companyID failed : %s : %v", v1CompanyModel.CompanyID, err) return nil, err } // fill events info @@ -472,10 +479,39 @@ func (s *service) GetCompanyByName(ctx context.Context, companyName string) (*mo return &v2CompanyModel, nil } +// GetCompanyBySigningEntityName retrieves the company by signing entity name +func (s *service) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) { + f := logrus.Fields{ + "functionName": "company.service.GetCompanyBySigningEntityName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "signingEntityName": signingEntityName, + } + + companyModel, err := s.companyRepo.GetCompanyBySigningEntityName(ctx, signingEntityName) + if err != nil { + return nil, err + } + + if companyModel == nil { + log.WithFields(f).Debugf("search by company signing entity name: %s didn't locate the record", signingEntityName) + return nil, nil + } + + // Convert from v1 to v2 model - use helper: Copy(toValue interface{}, fromValue interface{}) + var v2CompanyModel v2Models.Company + copyErr := copier.Copy(&v2CompanyModel, &companyModel) + if copyErr != nil { + log.WithFields(f).Warnf("problem converting v1 company model to a v2 company model, error: %+v", copyErr) + return nil, copyErr + } + + return &v2CompanyModel, nil +} + // GetCompanyByID retrieves the company by internal ID func (s *service) GetCompanyByID(ctx context.Context, companyID string) (*models.Company, error) { f := logrus.Fields{ - "functionName": "GetCompanyByID", + "functionName": "company.service.GetCompanyByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, } @@ -502,7 +538,7 @@ func (s *service) GetCompanyByID(ctx context.Context, companyID string) (*models func (s *service) AssociateContributor(ctx context.Context, companySFID string, userEmail string) (*models.Contributor, error) { f := logrus.Fields{ - "functionName": "AssociateContributor", + "functionName": "company.service.AssociateContributor", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, "userEmail": userEmail, @@ -549,7 +585,7 @@ func (s *service) AssociateContributor(ctx context.Context, companySFID string, //CreateContributor creates contributor for contributor prospect func (s *service) CreateContributor(ctx context.Context, companyID string, projectID string, userEmail string, ClaGroupID string) (*models.Contributor, error) { f := logrus.Fields{ - "functionName": "CreateContributor", + "functionName": "company.service.CreateContributor", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectID": projectID, @@ -639,7 +675,7 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje //AssociateContributorByGroup creates contributor by group for contributor prospect func (s *service) AssociateContributorByGroup(ctx context.Context, companySFID, userEmail string, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, ClaGroupID string) ([]*models.Contributor, string, error) { f := logrus.Fields{ - "functionName": "AssociateContributorByGroup", + "functionName": "company.service.AssociateContributorByGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, "ClaGroupID": ClaGroupID, @@ -682,7 +718,7 @@ func (s *service) AssociateContributorByGroup(ctx context.Context, companySFID, // GetCompanyBySFID retrieves the company by external SFID func (s *service) GetCompanyBySFID(ctx context.Context, companySFID string) (*models.Company, error) { f := logrus.Fields{ - "functionName": "GetCompanyBySFID", + "functionName": "company.service.GetCompanyBySFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, } @@ -736,7 +772,7 @@ func (s *service) DeleteCompanyBySFID(ctx context.Context, companyID string) err func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string) (*models.CompanyProjectClaList, error) { f := logrus.Fields{ - "functionName": "GetCompanyProjectCLA", + "functionName": "company.service.GetCompanyProjectCLA", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, @@ -844,7 +880,7 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, // corresponding CLA managers func (s *service) GetCompanyCLAGroupManagers(ctx context.Context, companyID, claGroupID string) (*models.CompanyClaManagers, error) { f := logrus.Fields{ - "functionName": "GetCompanyCLAGroupManagers", + "functionName": "company.service.GetCompanyCLAGroupManagers", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "claGroupID": claGroupID, @@ -1023,7 +1059,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, id s func (s *service) getAllCCLASignatures(ctx context.Context, companyID string) ([]*v1Models.Signature, error) { f := logrus.Fields{ - "functionName": "getAllCCLASignatures", + "functionName": "company.service.getAllCCLASignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, } @@ -1066,7 +1102,7 @@ func getUsersInfo(lfUsernames []string) (map[string]*v2UserServiceModels.User, e func fillUsersInfo(claManagers []*models.CompanyClaManager, usermap map[string]*v2UserServiceModels.User) { f := logrus.Fields{ - "functionName": "fillUsersInfo", + "functionName": "company.service.fillUsersInfo", } log.WithFields(f).Debug("filling users info...") @@ -1090,7 +1126,7 @@ func fillUsersInfo(claManagers []*models.CompanyClaManager, usermap map[string]* func fillProjectInfo(claManagers []*models.CompanyClaManager, claGroups map[string]*claGroupModel) { f := logrus.Fields{ - "functionName": "fillProjectInfo", + "functionName": "company.service.fillProjectInfo", } log.WithFields(f).Debug("filling project info...") for _, claManager := range claManagers { @@ -1106,7 +1142,9 @@ func fillProjectInfo(claManagers []*models.CompanyClaManager, claGroups map[stri func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1Models.Signature, activeCla *models.ActiveCla, claGroups map[string]*claGroupModel, companyID string) { f := logrus.Fields{ - "functionName": "fillActiveCLA", + "functionName": "v1CompanyModel.service.fillActiveCLA", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyID": companyID, } defer wg.Done() cg, ok := claGroups[sig.ProjectID] @@ -1116,9 +1154,9 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 } // Get Company details - company, compErr := s.GetCompanyByID(ctx, companyID) + v1CompanyModel, compErr := s.GetCompanyByID(ctx, companyID) if compErr != nil { - log.WithFields(f).WithError(compErr).Warnf("unable to fetch company by ID: %s ", companyID) + log.WithFields(f).WithError(compErr).Warnf("unable to fetch v1CompanyModel by ID: %s ", companyID) return } @@ -1134,11 +1172,11 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 } // fill details from dynamodb - activeCla.CompanyName = company.CompanyName - if company.SigningEntityName == "" { - activeCla.SigningEntityName = company.CompanyName + activeCla.CompanyName = v1CompanyModel.CompanyName + if v1CompanyModel.SigningEntityName == "" { + activeCla.SigningEntityName = v1CompanyModel.CompanyName } else { - activeCla.SigningEntityName = company.SigningEntityName + activeCla.SigningEntityName = v1CompanyModel.SigningEntityName } activeCla.ProjectID = sig.ProjectID if sig.SignedOn == "" { @@ -1151,6 +1189,8 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 UsernameList: acl, } activeCla.ClaGroupName = cg.ClaGroupName + activeCla.CompanyID = companyID + activeCla.CompanySfid = v1CompanyModel.CompanyExternalID activeCla.SignatureID = sig.SignatureID.String() // fill details from project service @@ -1264,7 +1304,7 @@ func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepos func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, companySFID string, projectSFID string) ([]*v1Models.Signature, error) { f := logrus.Fields{ - "functionName": "getAllCompanyProjectEmployeeSignatures", + "functionName": "company.service.getAllCompanyProjectEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, "projectSFID": projectSFID, @@ -1290,7 +1330,7 @@ func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, co // get company and project in parallel func (s *service) getCompanyAndClaGroup(ctx context.Context, companySFID, projectSFID string) (*v1Models.Company, *v1Models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "getCompanyAndClaGroup", + "functionName": "company.service.getCompanyAndClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, "projectSFID": projectSFID, @@ -1333,7 +1373,7 @@ func (s *service) getCompanyAndClaGroup(ctx context.Context, companySFID, projec // autoCreateCompany helper function to create a new company record based on the SF ID and underlying record in SF func (s service) autoCreateCompany(ctx context.Context, companySFID string) (*v1Models.Company, error) { f := logrus.Fields{ - "functionName": "autoCreateCompany", + "functionName": "company.service.autoCreateCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, } From 276a005849955225ba96b29136640adf303a3369 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 26 Jan 2021 18:44:30 +0300 Subject: [PATCH 0011/1276] [#2515] Feature/Query for Company ID (#2525) - Added query param for company ID for company project cla endpoint Signed-off-by: wanyaland --- cla-backend-go/swagger/cla.v2.yaml | 6 ++++++ cla-backend-go/v2/company/handlers.go | 2 +- cla-backend-go/v2/company/service.go | 27 ++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 819e63cc9..dd9422c30 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3240,6 +3240,7 @@ paths: - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-companySFID" - $ref: "#/parameters/path-projectSFID" + - $ref: "#/parameters/companyID" responses: '200': description: 'Success' @@ -3526,6 +3527,11 @@ parameters: in: query type: string required: true + companyID: + name: companyID + description: The internal company ID representing signing entity name instance (EasyCLA) + in: query + type: string path-claGroupID: name: claGroupID description: ID of the CLA Group diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 04c450fa7..30953b775 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -195,7 +195,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("loading project company CLAs") - result, err := service.GetCompanyProjectCLA(ctx, authUser, params.CompanySFID, params.ProjectSFID) + result, err := service.GetCompanyProjectCLA(ctx, authUser, params.CompanySFID, params.ProjectSFID, params.CompanyID) if err != nil { msg := "unable to load project company CLAs" log.WithFields(f).WithError(err).Warn(msg) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 375708015..a3f2d9e8a 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -88,7 +88,7 @@ type Service interface { GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyModel *models.Company, projectSFID string) (*models.CompanyClaManagers, error) GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) - GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string) (*models.CompanyProjectClaList, error) + GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string) (*models.CompanyOutput, error) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) @@ -770,7 +770,7 @@ func (s *service) DeleteCompanyBySFID(ctx context.Context, companyID string) err return s.companyRepo.DeleteCompanyBySFID(ctx, companyID) } -func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string) (*models.CompanyProjectClaList, error) { +func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) { f := logrus.Fields{ "functionName": "company.service.GetCompanyProjectCLA", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -792,7 +792,9 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, // Attempt to locate the company model in our database log.WithFields(f).Debug("locating company by SF ID") var companyModel *v1Models.Company - companies, companyErr := s.companyRepo.GetCompaniesByExternalID(ctx, companySFID) + var companies []*v1Models.Company + var companyErr error + companies, companyErr = s.companyRepo.GetCompaniesByExternalID(ctx, companySFID) if companyErr != nil { // If we were unable to find the company/org in our local database, try to auto-create based // on the existing SF record @@ -822,6 +824,16 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, } var companyProjectClaList = make([]*models.CompanyProjectCla, 0) + if companyID != nil { + log.WithFields(f).Debugf("Filtering company for ID: %s ", *companyID) + index, found := findCompany(companies, *companyID) + if found { + log.WithFields(f).Debugf("Found company: %v ", companies[index]) + companies = []*v1Models.Company{companies[index]} + } else { + companies = []*v1Models.Company{} + } + } for _, company := range companies { activeCLAList, err := s.GetCompanyProjectActiveCLAs(ctx, company.CompanyID, projectSFID) if err != nil { @@ -1567,3 +1579,12 @@ func validateRequestCompanyAdmin(userID string, claManagerName string, contribut return nil } + +func findCompany(companies []*v1Models.Company, companyID string) (int, bool) { + for index, company := range companies { + if company.CompanyID == companyID { + return index, true + } + } + return -1, false +} From 3ffa347f08727b121122fa103b5d612159e9e382 Mon Sep 17 00:00:00 2001 From: makkalot Date: Tue, 26 Jan 2021 18:15:01 +0200 Subject: [PATCH 0012/1276] companySFID -> companyID change for metrics endpoints Signed-off-by: makkalot --- cla-backend-go/swagger/cla.v2.yaml | 10 +++------- cla-backend-go/v2/metrics/handlers.go | 28 ++++++++++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 819e63cc9..7fce303d2 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -164,11 +164,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - name: companyID - description: the company ID - in: path - type: string - required: true + - $ref: '#/parameters/path-companyID' responses: '200': description: 'Success' @@ -312,7 +308,7 @@ paths: tags: - metrics - /metrics/company/{companySFID}/project/{projectSFID}: + /metrics/company/{companyID}/project/{projectSFID}: get: summary: List project metrics for a company description: Returns list of project metrics for company @@ -322,7 +318,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-projectSFID" responses: '200': diff --git a/cla-backend-go/v2/metrics/handlers.go b/cla-backend-go/v2/metrics/handlers.go index 05edfaaa1..60106acc4 100644 --- a/cla-backend-go/v2/metrics/handlers.go +++ b/cla-backend-go/v2/metrics/handlers.go @@ -7,11 +7,14 @@ import ( "context" "fmt" + "github.com/sirupsen/logrus" + "github.com/LF-Engineering/lfx-kit/auth" v1Company "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/metrics" + log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime/middleware" ) @@ -95,22 +98,29 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp func(params metrics.ListCompanyProjectMetricsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "MetricsListCompanyProjectMetricsHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyID": params.CompanyID, + } + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + company, compErr := v1CompanyRepo.GetCompany(ctx, params.CompanyID) + if compErr != nil { + log.WithFields(f).Warnf("unable to fetch company by ID:%s ", params.CompanyID) + return metrics.NewListCompanyProjectMetricsBadRequest().WithPayload(errorResponse(reqID, compErr)) + } utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForOrganization(authUser, params.CompanySFID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(authUser, company.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { return metrics.NewListCompanyProjectMetricsForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to List Company Project Metrics with Organization scope of %s", - authUser.UserName, params.CompanySFID), + authUser.UserName, company.CompanyExternalID), XRequestID: reqID, }) } - comp, err := v1CompanyRepo.GetCompanyByExternalID(ctx, params.CompanySFID) - if err != nil { - if err == v1Company.ErrCompanyDoesNotExist { - return metrics.NewListCompanyProjectMetricsNotFound().WithXRequestID(reqID) - } - } - result, err := service.ListCompanyProjectMetrics(comp.CompanyID, params.ProjectSFID) + + result, err := service.ListCompanyProjectMetrics(params.CompanyID, params.ProjectSFID) if err != nil { return metrics.NewListCompanyProjectMetricsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } From 7f04c3bfc6a1bbbacc34aef4eec03be11639a515 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 26 Jan 2021 14:41:48 -0500 Subject: [PATCH 0013/1276] Updated GitHub Organization v2 API Logging (#2528) Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/handlers.go | 14 +-- .../v2/github_organizations/handlers.go | 96 +++++++++---------- 2 files changed, 52 insertions(+), 58 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 3ce030747..f19ce4284 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -39,7 +39,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaGroupCreateClaGroupHandler", + "functionName": "cla_groups.handlers.ClaGroupCreateClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupName": utils.StringValue(params.ClaGroupInput.ClaGroupName), "foundationSFID": utils.StringValue(params.ClaGroupInput.FoundationSfid), @@ -85,7 +85,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaGroupUpdateClaGroupHandler", + "functionName": "cla_groups.handlers.ClaGroupUpdateClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -160,7 +160,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaGroupDeleteClaGroupHandler", + "functionName": "cla_groups.handlers.ClaGroupDeleteClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -226,7 +226,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaGroupEnrollProjectsHandler", + "functionName": "cla_groups.handlers.ClaGroupEnrollProjectsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ClaGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -323,7 +323,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaGroupUnenrollProjectsHandler", + "functionName": "cla_groups.handlers.ClaGroupUnenrollProjectsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ClaGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -395,7 +395,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "ClaGroupListClaGroupsUnderFoundationHandler", + "functionName": "cla_groups.handlers.ClaGroupListClaGroupsUnderFoundationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "authUsername": params.XUSERNAME, @@ -504,7 +504,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // isUserHaveAccessToCLAProject is a helper function to determine if the user has access to the specified project func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, parentProjectSFID string, projectSFIDs []string, projectClaGroupsRepo projects_cla_groups.Repository) bool { // nolint f := logrus.Fields{ - "functionName": "isUserHaveAccessToCLAProject", + "functionName": "cla_groups.handlers.isUserHaveAccessToCLAProject", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "projectSFIDs": strings.Join(projectSFIDs, ","), diff --git a/cla-backend-go/v2/github_organizations/handlers.go b/cla-backend-go/v2/github_organizations/handlers.go index 514f224a0..ac4f893c5 100644 --- a/cla-backend-go/v2/github_organizations/handlers.go +++ b/cla-backend-go/v2/github_organizations/handlers.go @@ -13,7 +13,6 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/github_organizations" "github.com/communitybridge/easycla/cla-backend-go/github" @@ -23,6 +22,7 @@ import ( // Configure setups handlers on api with service func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service) { + api.GithubOrganizationsGetProjectGithubOrganizationsHandler = github_organizations.GetProjectGithubOrganizationsHandlerFunc( func(params github_organizations.GetProjectGithubOrganizationsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) @@ -30,7 +30,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubOrganizationsGetProjectGithubOrganizationsHandler", + "functionName": "github_organizations.handlers.GitHubOrganizationsGetProjectGithubOrganizationsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -70,7 +70,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubOrganizationsAddProjectGithubOrganizationHandler", + "functionName": "github_organization.handlers.GitHubOrganizationsAddProjectGithubOrganizationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -145,26 +145,32 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "github_organization.handlers.GithubOrganizationsDeleteProjectGithubOrganizationHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSFID, + "orgName": params.OrgName, + "authUser": authUser.UserName, + "authEmail": authUser.Email, + } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - return github_organizations.NewDeleteProjectGithubOrganizationForbidden().WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Delete Project GitHub Organizations with Project scope of %s", - authUser.UserName, params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("user %s does not have access to Delete Project GitHub Organizations with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return github_organizations.NewDeleteProjectGithubOrganizationForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } err := service.DeleteGithubOrganization(ctx, params.ProjectSFID, params.OrgName) if err != nil { if strings.Contains(err.Error(), "getProjectNotFound") { - return github_organizations.NewDeleteProjectGithubOrganizationNotFound().WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("project not found with given ID. [%s]", params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("project not found with given SFID: %s", params.ProjectSFID) + log.WithFields(f).Debug(msg) + return github_organizations.NewDeleteProjectGithubOrganizationNotFound().WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) } - return github_organizations.NewDeleteProjectGithubOrganizationBadRequest().WithPayload(errorResponse(reqID, err)) + msg := fmt.Sprintf("problem deleting GitHub Organization with project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) + log.WithFields(f).Debug(msg) + return github_organizations.NewDeleteProjectGithubOrganizationBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } eventService.LogEvent(&events.LogEventArgs{ @@ -185,64 +191,52 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "github_organization.handlers.GithubOrganizationsUpdateProjectGithubOrganizationConfigHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSFID, + "orgName": params.OrgName, + "authUser": authUser.UserName, + "authEmail": authUser.Email, + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - return github_organizations.NewUpdateProjectGithubOrganizationConfigForbidden().WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Update Project GitHub Organizations with Project scope of %s", - authUser.UserName, params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("user %s does not have access to Update Project GitHub Organizations with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return github_organizations.NewUpdateProjectGithubOrganizationConfigForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } if params.Body.AutoEnabled == nil { - return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "EasyCLA - 400 Bad Request - missing auto enable value in body", - XRequestID: reqID, - }) + msg := fmt.Sprintf("missing auto enable value in request body for project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) + log.WithFields(f).Debug(msg) + return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { - return github_organizations.NewAddProjectGithubOrganizationBadRequest().WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "EasyCLA - 400 Bad Request - AutoEnabledClaGroupID can't be empty when AutoEnabled", - }) + msg := fmt.Sprintf("AutoEnabledClaGroupID can't be empty when AutoEnabled flag is set to true - issue in request body for project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) + log.WithFields(f).Debug(msg) + return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } err := service.UpdateGithubOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) if err != nil { - return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(errorResponse(reqID, err)) + msg := fmt.Sprintf("problem updating GitHub Organization for project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) + log.WithFields(f).Debug(msg) + return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } + // Log the event eventService.LogEvent(&events.LogEventArgs{ LfUsername: authUser.UserName, EventType: events.GitHubOrganizationUpdated, ExternalProjectID: params.ProjectSFID, EventData: &events.GitHubOrganizationUpdatedEventData{ GitHubOrganizationName: params.OrgName, - AutoEnabled: *params.Body.AutoEnabled, + AutoEnabled: utils.BoolValue(params.Body.AutoEnabled), }, }) return github_organizations.NewUpdateProjectGithubOrganizationConfigOK() }) } - -type codedResponse interface { - Code() string -} - -func errorResponse(reqID string, err error) *models.ErrorResponse { - code := "" - if e, ok := err.(codedResponse); ok { - code = e.Code() - } - - e := models.ErrorResponse{ - Code: code, - Message: err.Error(), - XRequestID: reqID, - } - - return &e -} From 105cc399badf2819537b8610bf9dc3fdee352dec Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 27 Jan 2021 17:55:25 +0200 Subject: [PATCH 0014/1276] signatures endpoints companySFID -> companyID migration (#2530) Signed-off-by: makkalot --- cla-backend-go/swagger/cla.v2.yaml | 22 +-- cla-backend-go/v2/signatures/handlers.go | 192 ++++++++++++++--------- cla-backend-go/v2/signatures/service.go | 43 ++--- 3 files changed, 139 insertions(+), 118 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 18b17c913..01647e697 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1641,7 +1641,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-claGroupID" - - $ref: "#/parameters/companySFID" + - $ref: "#/parameters/companyID" - $ref: '#/parameters/searchTerm' responses: '200': @@ -1987,7 +1987,7 @@ paths: # -------------------------------------------------------- # Employee CLA Endpoints - CSV Report Download # -------------------------------------------------------- - /signatures/project/{claGroupID}/company/{companySFID}/employee/csv: + /signatures/project/{claGroupID}/company/{companyID}/employee/csv: get: summary: Downloads all employee CLA information as a CSV document for this project description: Downloads the employee CLA information as a CSV document for this project @@ -1998,7 +1998,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-claGroupID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" produces: - text/json - text/csv @@ -2022,7 +2022,7 @@ paths: tags: - signatures - /signatures/project/{projectSFID}/company/{companySFID}: + /signatures/project/{projectSFID}/company/{companyID}: get: summary: Get project company ccla signatures description: Returns a list of ccla signature models when provided the project ID and company ID @@ -2033,7 +2033,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-projectSFID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: '#/parameters/sortOrder' responses: '200': @@ -2055,7 +2055,7 @@ paths: tags: - signatures - /signatures/company/{companySFID}: + /signatures/company/{companyID}: get: summary: Get company signatures description: Returns a list of company signatures when provided the company ID @@ -2065,7 +2065,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - $ref: '#/parameters/path-companySFID' + - $ref: '#/parameters/path-companyID' - $ref: '#/parameters/signatureType' - $ref: '#/parameters/pageSize' - $ref: '#/parameters/nextKey' @@ -2130,7 +2130,7 @@ paths: tags: - signatures - /signatures/project/{projectSFID}/company/{companySFID}/employee: + /signatures/project/{projectSFID}/company/{companyID}/employee: get: summary: Get project company signatures for the employees description: Returns a list of employee project signature models when provided the project ID and company ID @@ -2141,7 +2141,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-projectSFID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: '#/parameters/pageSize' - $ref: '#/parameters/nextKey' - $ref: '#/parameters/sortOrder' @@ -2165,7 +2165,7 @@ paths: tags: - signatures - /signatures/project/{projectSFID}/company/{companySFID}/clagroup/{claGroupID}/approval-list: + /signatures/project/{projectSFID}/company/{companyID}/clagroup/{claGroupID}/approval-list: put: summary: Updates the Project / Organization/Company Approval list description: API to update the project and organization/company approval list. @@ -2176,7 +2176,7 @@ paths: - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - $ref: "#/parameters/path-projectSFID" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - name: claGroupID in: path type: string diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index fd9973597..54548d646 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -99,13 +99,29 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, + } + + companyModel, err := companyService.GetCompany(ctx, params.CompanyID) + if err != nil { + msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) + log.Warn(msg) + if errors.Is(err, company.ErrCompanyDoesNotExist) { + return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 404 Not Found - error getting company - " + msg, + Code: "404", + }) + } + return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, + Code: "400", + }) } // Must be in the Project|Organization Scope to see this - signature ACL is double-checked in the service level when the signature is loaded - if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, params.CompanySFID, utils.DISALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, companyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to update Project Company Approval List with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID) + authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) return signatures.NewUpdateApprovalListForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -118,15 +134,6 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje return validationError } - // Lookup the internal company ID when provided the external ID via the v1SignatureGService call - log.WithFields(f).Debug("loading company by company SFID") - companyModel, compErr := companyService.GetCompanyByExternalID(ctx, params.CompanySFID) - if compErr != nil || companyModel == nil { - msg := fmt.Sprintf("unable to locate company by external company ID: %s", params.CompanySFID) - log.WithFields(f).Warn(msg) - return signatures.NewUpdateApprovalListNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) - } - log.WithFields(f).Debug("loading CLA groups by projectSFID") projectModels, projsErr := projectService.GetCLAGroupsByExternalSFID(ctx, params.ProjectSFID) if projsErr != nil || projectModels == nil { @@ -145,7 +152,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje // Convert the v2 input parameters to a v1 model v1ApprovalList := v1Models.ApprovalList{} - err := copier.Copy(&v1ApprovalList, params.Body) + err = copier.Copy(&v1ApprovalList, params.Body) if err != nil { msg := "unable to convert v1 to v2 approval list" log.WithFields(f).Warn(msg) @@ -435,25 +442,41 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje "functionName": "SignaturesGetProjectCompanySignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, + } + + companyModel, err := companyService.GetCompany(ctx, params.CompanyID) + if err != nil { + msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) + log.Warn(msg) + if errors.Is(err, company.ErrCompanyDoesNotExist) { + return signatures.NewGetProjectCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 404 Not Found - error getting company - " + msg, + Code: "404", + }) + } + return signatures.NewGetProjectCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, + Code: "400", + }) } // Must be in the one of the above scopes to see this // - if project scope (like a PM) // - if project|organization scope (like CLA Manager, CLA Signatory) // - if organization scope (like company admin) - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, params.CompanySFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s is not authorized to view project company signatures any scope of project: %s, organization %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID) + authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) return signatures.NewGetProjectCompanySignaturesForbidden().WithXRequestID(reqID).WithPayload( utils.ErrorResponseForbidden(reqID, msg)) } log.WithFields(f).Debug("loading project company signatures...") - projectSignatures, err := v2service.GetProjectCompanySignatures(ctx, params.CompanySFID, params.ProjectSFID) + projectSignatures, err := v2service.GetProjectCompanySignatures(ctx, params.CompanyID, companyModel.CompanyExternalID, params.ProjectSFID) if err != nil { - msg := fmt.Sprintf("error retrieving project signatures for project: %s, company: %s", params.ProjectSFID, params.CompanySFID) + msg := fmt.Sprintf("error retrieving project signatures for project: %s, company: %s", params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) return signatures.NewGetProjectCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } @@ -470,55 +493,37 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje "functionName": "SignaturesGetProjectCompanyEmployeeSignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, "nextKey": aws.StringValue(params.NextKey), "pageSize": aws.Int64Value(params.PageSize), } - // Try to load the company model - use both approaches - internal and external - var companyModel *v1Models.Company - var err error - // Internal IDs are UUIDv4 - external are not - if utils.IsUUIDv4(params.CompanySFID) { - // Oops - not provided a SFID - but an internal ID - that'iclaNotSupported ok, we'll lookup via the internal ID - log.WithFields(f).Debug("companySFID provided as internal ID - looking up record by internal ID") - // Lookup the company model by internal ID - companyModel, err = companyService.GetCompany(ctx, params.CompanySFID) - if companyModel != nil && companyModel.CompanyExternalID == "" { - msg := fmt.Sprintf("problem loading company - company external ID not defined - comapny ID: %s", params.CompanySFID) - log.WithFields(f).WithError(err).Warn(msg) - return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound( - reqID, msg)) - } - } else { - // Lookup the company model by external ID - log.WithFields(f).Debug("companySFID provided as external ID - looking up record by external ID") - companyModel, err = companyService.GetCompanyByExternalID(ctx, params.CompanySFID) - } + companyModel, err := companyService.GetCompany(ctx, params.CompanyID) if err != nil { - var companyDoesNotExistErr utils.CompanyDoesNotExist - if errors.Is(err, &companyDoesNotExistErr) { - msg := "problem loading company by ID" - log.WithFields(f).WithError(err).Warn(msg) - return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) + log.Warn(msg) + if errors.Is(err, company.ErrCompanyDoesNotExist) { + return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 404 Not Found - error getting company - " + msg, + Code: "404", + }) } - - log.WithFields(f).WithError(err).Warnf("problem loading company by ID") - return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("problem loading company by ID: %s", params.CompanySFID), err)) + return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, + Code: "400", + }) } if companyModel == nil { - msg := fmt.Sprintf("problem loading company by ID: %s", params.CompanySFID) + msg := fmt.Sprintf("problem loading company by ID: %s", params.CompanyID) log.WithFields(f).WithError(err).Warn(msg) return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload( utils.ErrorResponseNotFound(reqID, msg)) } log.WithFields(f).Debug("checking access control permissions...") - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, params.CompanySFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s is not authorized to view project company signatures any scope of project: %s, organization %s", - authUser.UserName, params.ProjectSFID, params.CompanySFID) + authUser.UserName, params.ProjectSFID, params.CompanyID) log.Warn(msg) return signatures.NewGetProjectCompanyEmployeeSignaturesForbidden().WithXRequestID(reqID).WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -548,9 +553,9 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje }) if err != nil { log.WithFields(f).WithError(err).Warnf("error retrieving employee project signatures for project: %s, company: %s, error: %+v", - params.ProjectSFID, params.CompanySFID, err) + params.ProjectSFID, params.CompanyID, err) return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError( - reqID, fmt.Sprintf("unable to fetch employee signatures for project ID: %s and company: %s", params.ProjectSFID, params.CompanySFID), err)) + reqID, fmt.Sprintf("unable to fetch employee signatures for project ID: %s and company: %s", params.ProjectSFID, params.CompanyID), err)) } resp, err := v2Signatures(projectSignatures) @@ -573,24 +578,29 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje f := logrus.Fields{ "functionName": "SignaturesGetCompanySignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, "companyName": aws.StringValue(params.CompanyName), "signatureType": aws.StringValue(params.SignatureType), "nextKey": aws.StringValue(params.NextKey), "pageSize": aws.Int64Value(params.PageSize), } - // Lookup the internal company ID - companyModel, err := companyService.GetCompanyByExternalID(ctx, params.CompanySFID) + companyModel, err := companyService.GetCompany(ctx, params.CompanyID) if err != nil { - log.WithFields(f).WithError(err).Warnf("problem loading company by SFID - returning empty response") - // Not sure this is the correct response as the LFX UI/Admin console wants 200 empty lists instead of non-200 status back - return signatures.NewGetCompanySignaturesOK().WithXRequestID(reqID).WithPayload(&models.Signatures{ - Signatures: []*models.Signature{}, - ResultCount: 0, - TotalCount: 0, + msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) + log.Warn(msg) + if errors.Is(err, company.ErrCompanyDoesNotExist) { + return signatures.NewGetCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 404 Not Found - error getting company - " + msg, + Code: "404", + }) + } + return signatures.NewGetCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, + Code: "400", }) } + if companyModel == nil { log.WithFields(f).WithError(err).Warnf("problem loading company model by ID - returning empty response") // Not sure this is the correct response as the LFX UI/Admin console wants 200 empty lists instead of non-200 status back @@ -696,7 +706,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje "functionName": "SignaturesDownloadProjectSignatureEmployeeAsCSVHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, } log.WithFields(f).Debug("processing request...") @@ -731,8 +741,24 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje // utils.ErrorResponseBadRequest(reqID, cclaNotSupportedForCLAGroup)) } + companyModel, err := companyService.GetCompany(ctx, params.CompanyID) + if err != nil { + msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) + log.Warn(msg) + if errors.Is(err, company.ErrCompanyDoesNotExist) { + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 404 Not Found - error getting company - " + msg, + Code: "404", + }) + } + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, + Code: "400", + }) + } + log.WithFields(f).Debug("checking access control permissions for user...") - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, claGroupModel.FoundationSFID, params.CompanySFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, claGroupModel.FoundationSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { msg := fmt.Sprintf(" user %s is not authorized to view project employee signatures any scope of project", authUser.UserName) log.Warn(msg) @@ -741,9 +767,9 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("searching for corporate contributor signatures...") - result, err := v2service.GetClaGroupCorporateContributorsCsv(ctx, params.ClaGroupID, params.CompanySFID) + result, err := v2service.GetClaGroupCorporateContributorsCsv(ctx, params.ClaGroupID, params.CompanyID) if err != nil { - msg := fmt.Sprintf("problem getting corporate contributors CSV for CLA Group: %s with company: %s", params.ClaGroupID, params.CompanySFID) + msg := fmt.Sprintf("problem getting corporate contributors CSV for CLA Group: %s with company: %s", params.ClaGroupID, companyModel.CompanyExternalID) if _, ok := err.(*organizations.GetOrgNotFound); ok { formatErr := errors.New("error retrieving company using companySFID") return signatures.NewDownloadProjectSignatureEmployeeAsCSVNotFound().WithXRequestID(reqID).WithPayload( @@ -830,17 +856,33 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje "functionName": "SignaturesListClaGroupCorporateContributorsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, } - // Make sure the user has provided the companySFID - if params.CompanySFID == nil { - msg := "missing companySFID as input" + // Make sure the user has provided the companyID + if params.CompanyID == nil { + msg := "missing companyID as input" log.WithFields(f).Warn(msg) return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload( utils.ErrorResponseBadRequest(reqID, msg)) } + companyModel, err := companyService.GetCompany(ctx, *params.CompanyID) + if err != nil { + msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", *params.CompanyID, err) + log.Warn(msg) + if errors.Is(err, company.ErrCompanyDoesNotExist) { + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 404 Not Found - error getting company - " + msg, + Code: "404", + }) + } + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ + Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, + Code: "400", + }) + } + // Lookup the CLA Group by ID - make sure it's valid claGroupModel, err := projectRepo.GetCLAGroupByID(ctx, params.ClaGroupID, project.DontLoadRepoDetails) if err != nil { @@ -868,18 +910,18 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje f["foundationSFID"] = claGroupModel.FoundationSFID log.WithFields(f).Debug("checking access control permissions for user...") - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, claGroupModel.FoundationSFID, *params.CompanySFID, projectClaGroupsRepo) { + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, claGroupModel.FoundationSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s is not authorized to view project CCLA signatures any scope of project or project|organization scope with company ID: %s", - authUser.UserName, aws.StringValue(params.CompanySFID)) + authUser.UserName, companyModel.CompanyID) log.Warn(msg) return signatures.NewListClaGroupCorporateContributorsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("searching for CCLA signatures...") - result, err := v2service.GetClaGroupCorporateContributors(ctx, params.ClaGroupID, params.CompanySFID, params.SearchTerm) + result, err := v2service.GetClaGroupCorporateContributors(ctx, params.ClaGroupID, *params.CompanyID, params.SearchTerm) if err != nil { - msg := fmt.Sprintf("problem getting corporate contributors for CLA Group: %s with company: %s", params.ClaGroupID, *params.CompanySFID) + msg := fmt.Sprintf("problem getting corporate contributors for CLA Group: %s with company: %s", params.ClaGroupID, *params.CompanyID) if _, ok := err.(*organizations.GetOrgNotFound); ok { formatErr := errors.New("error retrieving company using companySFID") return signatures.NewListClaGroupCorporateContributorsNotFound().WithXRequestID(reqID).WithPayload( diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 16bb8a8d2..d67b7a219 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -53,12 +53,12 @@ type service struct { // Service contains method of v2 signature service type Service interface { - GetProjectCompanySignatures(ctx context.Context, companySFID string, projectSFID string) (*models.Signatures, error) + GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) - GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companySFID string) ([]byte, error) - GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companySFID *string, searchTerm *string) (*models.CorporateContributorList, error) + GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companyID string) ([]byte, error) + GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companySFID string, searchTerm *string) (*models.CorporateContributorList, error) GetSignedDocument(ctx context.Context, signatureID string) (*models.SignedDocument, error) GetSignedIclaZipPdf(claGroupID string) (*models.URLObject, error) GetSignedCclaZipPdf(claGroupID string) (*models.URLObject, error) @@ -79,18 +79,14 @@ func NewService(awsSession *session.Session, signaturesBucketName string, v1Proj } } -func (s *service) GetProjectCompanySignatures(ctx context.Context, companySFID string, projectSFID string) (*models.Signatures, error) { - companyModel, err := s.v1CompanyService.GetCompanyByExternalID(ctx, companySFID) - if err != nil { - return nil, err - } +func (s *service) GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) { pm, err := s.projectsClaGroupsRepo.GetClaGroupIDForProject(projectSFID) if err != nil { return nil, err } signed := true approved := true - sig, err := s.v1SignatureService.GetProjectCompanySignature(ctx, companyModel.CompanyID, pm.ClaGroupID, &signed, &approved, nil, aws.Int64(HugePageSize)) + sig, err := s.v1SignatureService.GetProjectCompanySignature(ctx, companyID, pm.ClaGroupID, &signed, &approved, nil, aws.Int64(HugePageSize)) if err != nil { return nil, err } @@ -101,7 +97,7 @@ func (s *service) GetProjectCompanySignatures(ctx context.Context, companySFID s resp.ResultCount = 1 resp.Signatures = append(resp.Signatures, sig) } - return v2SignaturesReplaceCompanyID(resp, companyModel.CompanyID, companySFID) + return v2SignaturesReplaceCompanyID(resp, companyID, companySFID) } func eclaSigCsvLine(sig *v1Models.CorporateContributor) string { @@ -116,14 +112,9 @@ func eclaSigCsvLine(sig *v1Models.CorporateContributor) string { return fmt.Sprintf("\n%s,%s,%s,%s,\"%s\"", sig.GithubID, sig.LinuxFoundationID, sig.Name, sig.Email, dateTime) } -func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companySFID string) ([]byte, error) { +func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companyID string) ([]byte, error) { var b bytes.Buffer - comp, companyErr := s.v1CompanyService.GetCompanyByExternalID(ctx, companySFID) - if companyErr != nil { - return nil, companyErr - } - - result, err := s.v1SignatureService.GetClaGroupCorporateContributors(ctx, claGroupID, &comp.CompanyID, nil) + result, err := s.v1SignatureService.GetClaGroupCorporateContributors(ctx, claGroupID, &companyID, nil) if err != nil { return nil, err } @@ -265,30 +256,18 @@ func (s service) IsZipPresentOnS3(zipFilePath string) (bool, error) { return true, nil } -func (s service) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companySFID *string, searchTerm *string) (*models.CorporateContributorList, error) { +func (s service) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID string, searchTerm *string) (*models.CorporateContributorList, error) { f := logrus.Fields{ "functionName": "GetClaGroupCorporateContributors", "claGroupID": claGroupID, - } - if companySFID != nil { - f["companySFID"] = *companySFID + "companyID": companyID, } if searchTerm != nil { f["searchTerm"] = *searchTerm } - var companyID *string - if companySFID != nil { - log.WithFields(f).Debug("loading company by companySFID...") - companyModel, err := s.v1CompanyService.GetCompanyByExternalID(ctx, *companySFID) - if err != nil { - return nil, err - } - companyID = &companyModel.CompanyID - } - log.WithFields(f).Debug("querying CLA corporate contributors...") - result, err := s.v1SignatureService.GetClaGroupCorporateContributors(ctx, claGroupID, companyID, searchTerm) + result, err := s.v1SignatureService.GetClaGroupCorporateContributors(ctx, claGroupID, &companyID, searchTerm) if err != nil { return nil, err } From 480a5856040bcab469bef95df1eca903e41d67da Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 27 Jan 2021 17:56:35 +0200 Subject: [PATCH 0015/1276] the # sign in the url was confusing the underlying urllib (#2531) Signed-off-by: makkalot --- cla-backend/cla/tests/unit/test_utils.py | 21 +++++++++++++++++ cla-backend/cla/utils.py | 30 +++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/tests/unit/test_utils.py b/cla-backend/cla/tests/unit/test_utils.py index bfda0d026..cd1eaf928 100644 --- a/cla-backend/cla/tests/unit/test_utils.py +++ b/cla-backend/cla/tests/unit/test_utils.py @@ -225,6 +225,27 @@ def test_append_project_version_to_url(): assert "version=1" in url assert "something=else" in url + # try the weird case with # in url + url = "https://dev.lfcla.com/#/" + url = append_project_version_to_url(address=url, project_version="v2") + assert "version=2" in url + assert "version=1" not in url + + url = "https://dev.lfcla.com/#/" + url = append_project_version_to_url(address=url, project_version="") + assert "version=1" in url + assert "version=2" not in url + + url = "https://dev.lfcla.com/#/" + url = append_project_version_to_url(address=url, project_version=None) + assert "version=1" in url + assert "version=2" not in url + + url = "https://dev.lfcla.com/#/?something=else" + url = append_project_version_to_url(address=url, project_version="") + assert "version=1" in url + assert "something=else" in url + assert "version=2" not in url if __name__ == '__main__': unittest.main() diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index e12f04edc..fe019c0b7 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -779,9 +779,9 @@ def get_full_sign_url(repository_service, installation_id, github_repository_id, """ base_url = '{}/v2/repository-provider/{}/sign/{}/{}/{}/#/'.format(cla.conf['API_BASE_URL'], repository_service, - str(installation_id), - str(github_repository_id), - str(change_request_id)) + str(installation_id), + str(github_repository_id), + str(change_request_id)) return append_project_version_to_url(address=base_url, project_version=project_version) @@ -801,6 +801,30 @@ def append_project_version_to_url(address: str, project_version: str) -> str: if "version" in f.args: return address f.args["version"] = version + + # seem if the url has # in it (https://dev.lfcla.com/#/) the underlying urllib is being confused + # In[7]: url_parts = list(urlparse.urlparse(address)) + # In[8]: url_parts + # Out[8]: ['https', 'dev.lfcla.com', '/', '', '', '/'] + # In [9]: query = dict(urlparse.parse_qsl(url_parts[4])) + # In[12]: query["version"] = "1" + # In[13]: query + # Out[13]: {'version': '1'} + # + # In[14]: url_parts[4] = urlencode(query) + # + # In[15]: print(urlparse.urlunparse(url_parts)) + # https://dev.lfcla.com/?version = 1#/ + + final_url = f.url + final_url = final_url.rstrip("/") + if final_url.endswith("#"): + final_url = final_url.rstrip("#") + parts = final_url.split("?") + if len(parts) > 2: + return f.url + return "#/?".join(parts) + return f.url From 8a3203848b896435d794562948b07c035880e7f5 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Wed, 27 Jan 2021 18:48:22 +0300 Subject: [PATCH 0016/1276] [#2527,#2529] Bug/Invite Company Admin - Resolved assigning company designee bug that was caused by wrong companyID Signed-off-by: wanyaland --- cla-backend-go/v2/cla_manager/handlers.go | 2 +- cla-backend-go/v2/cla_manager/service.go | 50 +---------------------- 2 files changed, 3 insertions(+), 49 deletions(-) diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 0a67e154c..7d7d7c60a 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -158,7 +158,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C // Note: anyone create assign a CLA manager designee...no permissions checks log.WithFields(f).Debugf("processing create CLA Manager Desginee request") utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - claManagerDesignee, err := service.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyExternalID, params.ProjectSFID, params.Body.UserEmail.String()) + claManagerDesignee, err := service.CreateCLAManagerDesignee(ctx, params.CompanyID, params.ProjectSFID, params.Body.UserEmail.String()) if err != nil { if err == ErrCLAManagerDesigneeConflict { msg := fmt.Sprintf("Conflict assigning cla manager role for Project SFID: %s ", params.ProjectSFID) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 9f9dff0bd..39467d46d 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -37,7 +37,6 @@ import ( v2OrgService "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" v2UserService "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" - v2UserModels "github.com/communitybridge/easycla/cla-backend-go/v2/user-service/models" ) var ( @@ -979,7 +978,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com // check if claGroup is signed at foundation level foundationSFID := projectCLAGroups[0].FoundationSFID log.WithFields(f).Debugf("Create cla manager designee for foundation : %s ", foundationSFID) - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, organization.ID, foundationSFID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, companyID, foundationSFID, userEmail) if err != nil { msg := fmt.Sprintf("Problem creating cla Manager Designee for user : %s, error: %+v ", userEmail, err) log.WithFields(f).Warn(msg) @@ -989,7 +988,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com } else { for _, pcg := range projectCLAGroups { log.WithFields(f).Debugf("Create cla manager designee for Project SFID: %s", pcg.ProjectSFID) - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, organization.ID, pcg.ProjectSFID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, companyID, pcg.ProjectSFID, userEmail) if err != nil { msg := fmt.Sprintf("Problem creating cla Manager Designee for user : %s, error: %+v ", userEmail, err) log.WithFields(f).Warn(msg) @@ -998,10 +997,6 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com designeeScopes = append(designeeScopes, claManagerDesignee) } } - conversionErr := s.convertGHUserToContact(ctx, contributor) - if conversionErr != nil { - return nil, conversionErr - } log.Debugf("Sending Email to CLA Manager Designee email: %s ", userEmail) @@ -1440,47 +1435,6 @@ func buildErrorMessage(errPrefix string, claGroupID string, params cla_manager.C errPrefix, params.CompanyID, claGroupID, *params.Body.FirstName, *params.Body.LastName, *params.Body.UserEmail, err) } -func (s *service) convertGHUserToContact(ctx context.Context, contributor *v1User.User) error { - f := logrus.Fields{ - "functionName": "cla_manager.service.convertGHUserToContact", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - } - - userService := v2UserService.GetClient() - log.Infof("Checking if GH User: %s, GH ID: %s has LFID for contact conversion ", contributor.UserGithubUsername, contributor.UserGithubID) - var GHUserLF *v2UserModels.User - var GHUserErr error - if contributor.LFEmail != "" { - GHUserLF, GHUserErr = userService.SearchUserByEmail(contributor.LFEmail) - if GHUserErr != nil { - msg := fmt.Sprintf("GH UserEmail: %s has no LF Login ", contributor.LFEmail) - log.Warn(msg) - } - - } else if contributor.LFUsername != "" { - GHUserLF, GHUserErr = userService.GetUserByUsername(contributor.LFUsername) - if GHUserErr != nil { - msg := fmt.Sprintf("GH Username: %s has no LF Login ", contributor.LFUsername) - log.Warn(msg) - } - } - - if GHUserLF != nil { - // Convert user to contact - if GHUserLF.Type == utils.Lead { - // convert user to contact - log.WithFields(f).Debug("converting lead to contact") - err := userService.ConvertToContact(GHUserLF.ID) - if err != nil { - msg := fmt.Sprintf("converting lead to contact failed: %v", err) - log.WithFields(f).Warn(msg) - return err - } - } - } - return nil -} - func companyV1toV2(v1CompanyModel *v1Models.Company) *models.Company { return &models.Company{ CompanyACL: v1CompanyModel.CompanyACL, From d66ddb4a4f9fc584ce01dfcfb122943e4324f6ed Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 27 Jan 2021 19:44:43 +0200 Subject: [PATCH 0017/1276] cover more cases for urls that include # and remove the furl library (#2532) since we needed more low level access to the url parsing Signed-off-by: makkalot --- cla-backend/cla/tests/unit/test_utils.py | 77 +++++++++++++++++------- cla-backend/cla/utils.py | 48 ++++++--------- cla-backend/requirements.txt | 1 - 3 files changed, 74 insertions(+), 52 deletions(-) diff --git a/cla-backend/cla/tests/unit/test_utils.py b/cla-backend/cla/tests/unit/test_utils.py index cd1eaf928..5b246ebf0 100644 --- a/cla-backend/cla/tests/unit/test_utils.py +++ b/cla-backend/cla/tests/unit/test_utils.py @@ -192,60 +192,93 @@ def test_get_full_sign_url(): def test_append_project_version_to_url(): - url = "http://localhost:5000/v1/sign" - url = append_project_version_to_url(address=url, project_version="v1") + original_url = "http://localhost:5000/v1/sign" + url = append_project_version_to_url(address=original_url, project_version="v1") + print(url) assert "?version=1" in url + assert original_url in url - url = "http://localhost:5000/v1/sign" - url = append_project_version_to_url(address=url, project_version="v2") + original_url = "http://localhost:5000/v1/sign" + url = append_project_version_to_url(address=original_url, project_version="v2") + print(url) assert "?version=2" in url assert "http://localhost:5000/v1/sign?version=2" == url + assert original_url in url - url = "http://localhost:5000/v1/sign" - url = append_project_version_to_url(address=url, project_version=None) + original_url = "http://localhost:5000/v1/sign" + url = append_project_version_to_url(address=original_url, project_version=None) + print(url) assert "?version=1" in url + assert original_url in url - url = "http://localhost:5000/v1/sign" - url = append_project_version_to_url(address=url, project_version="invalid") + original_url = "http://localhost:5000/v1/sign" + url = append_project_version_to_url(address=original_url, project_version="invalid") + print(url) assert "?version=1" in url + assert original_url in url - url = "http://localhost:5000/v1/sign?something=else" - url = append_project_version_to_url(address=url, project_version="v2") + original_url = "http://localhost:5000/v1/sign?something=else" + url = append_project_version_to_url(address=original_url, project_version="v2") + print(url) assert "version=2" in url assert "something=else" in url + assert original_url in url - url = "http://localhost:5000/v1/sign?version=1" - url = append_project_version_to_url(address=url, project_version="v2") + original_url = "http://localhost:5000/v1/sign?version=1" + url = append_project_version_to_url(address=original_url, project_version="v2") + print(url) assert "version=2" not in url assert "version=1" in url + assert original_url in url - url = "http://localhost:5000/v1/sign?something=else&version=1" - url = append_project_version_to_url(address=url, project_version="v2") + original_url = "http://localhost:5000/v1/sign?something=else&version=1" + url = append_project_version_to_url(address=original_url, project_version="v2") + print(url) assert "version=2" not in url assert "version=1" in url assert "something=else" in url + assert original_url in url # try the weird case with # in url - url = "https://dev.lfcla.com/#/" - url = append_project_version_to_url(address=url, project_version="v2") + original_url = "https://dev.lfcla.com/#/" + url = append_project_version_to_url(address=original_url, project_version="v2") + print(url) assert "version=2" in url assert "version=1" not in url + assert original_url in url - url = "https://dev.lfcla.com/#/" - url = append_project_version_to_url(address=url, project_version="") + original_url = "https://dev.lfcla.com/#/" + url = append_project_version_to_url(address=original_url, project_version="") + print(url) assert "version=1" in url assert "version=2" not in url + assert original_url in url - url = "https://dev.lfcla.com/#/" - url = append_project_version_to_url(address=url, project_version=None) + original_url = "https://dev.lfcla.com/#/" + url = append_project_version_to_url(address=original_url, project_version=None) + print(url) assert "version=1" in url assert "version=2" not in url + assert original_url in url - url = "https://dev.lfcla.com/#/?something=else" - url = append_project_version_to_url(address=url, project_version="") + original_url= "https://dev.lfcla.com/#/#/?something=else" + url = append_project_version_to_url(address=original_url, project_version="") + print(url) assert "version=1" in url assert "something=else" in url assert "version=2" not in url + assert original_url in url + + # check for crazier example ... + original_url = "https://dev.lfcla.com/1/#/2/#/3/#/?something=else&this=that" + url = append_project_version_to_url(address=original_url, project_version="") + print(url) + assert "version=1" in url + assert "something=else" in url + assert "this=that" in url + assert "version=2" not in url + assert original_url in url + if __name__ == '__main__': unittest.main() diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index fe019c0b7..e9d097d90 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -9,6 +9,8 @@ import json import os import urllib.parse +import urllib.parse as urlparse +from urllib.parse import urlencode from datetime import datetime from typing import List, Optional @@ -16,7 +18,6 @@ import requests from hug.middleware import SessionMiddleware from requests_oauthlib import OAuth2Session -from furl import furl import cla from cla.models import DoesNotExist @@ -797,35 +798,24 @@ def append_project_version_to_url(address: str, project_version: str) -> str: if project_version and project_version == 'v2': version = "2" - f = furl(address) - if "version" in f.args: + # seem if the url has # in it (https://dev.lfcla.com/#/version=1) the underlying urllib is being confused + # In[21]: list(urlparse.urlparse(address)) + # Out[21]: ['https', 'dev.lfcla.com', '/', '', '', '/#/?version=1'] + + query = {} + if "?" in address: + query = dict(urlparse.parse_qsl(address.split("?")[1])) + + # we don't alter for now + if "version" in query: return address - f.args["version"] = version - - # seem if the url has # in it (https://dev.lfcla.com/#/) the underlying urllib is being confused - # In[7]: url_parts = list(urlparse.urlparse(address)) - # In[8]: url_parts - # Out[8]: ['https', 'dev.lfcla.com', '/', '', '', '/'] - # In [9]: query = dict(urlparse.parse_qsl(url_parts[4])) - # In[12]: query["version"] = "1" - # In[13]: query - # Out[13]: {'version': '1'} - # - # In[14]: url_parts[4] = urlencode(query) - # - # In[15]: print(urlparse.urlunparse(url_parts)) - # https://dev.lfcla.com/?version = 1#/ - - final_url = f.url - final_url = final_url.rstrip("/") - if final_url.endswith("#"): - final_url = final_url.rstrip("#") - parts = final_url.split("?") - if len(parts) > 2: - return f.url - return "#/?".join(parts) - - return f.url + + query["version"] = version + query_params_str = urlencode(query) + + if "?" in address: + return "?".join([address.split("?")[0], query_params_str]) + return "?".join([address, query_params_str]) def get_comment_badge(repository_type, all_signed, sign_url, project_version, missing_user_id=False, diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 342206003..64247bdbb 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -16,7 +16,6 @@ docutils==0.15.2 ecdsa==0.14.1 falcon==2.0.0 future==0.18.2 -furl==2.1.0 gossip==2.3.1 gunicorn==19.9.0 hug==2.6.0 From dbf2b374d4515a3c5eeed3568b4c79fe8ba1437e Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 28 Jan 2021 06:57:58 +0300 Subject: [PATCH 0018/1276] [#2515] Bug/Events and Contributors for Company and Project (#2534) - Resolved events endpoint caused by incorrect filter attribute changed to event_company_id - Updated company project contributors endpoint with internal companyID as path parameter Signed-off-by: wanyaland --- cla-backend-go/events/repository.go | 4 ++-- cla-backend-go/swagger/cla.v2.yaml | 4 ++-- cla-backend-go/v2/company/handlers.go | 17 +++++++++++++---- cla-backend-go/v2/company/service.go | 19 +++++++++---------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 01a825b73..89853230f 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -473,7 +473,7 @@ func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, found keyCondition := expression.Key("company_sfid_foundation_sfid").Equal(expression.Value(key)) var filter expression.ConditionBuilder if companyID != "" { - filter = expression.Name("company_id").Equal(expression.Value(companyID)) + filter = expression.Name("event_company_id").Equal(expression.Value(companyID)) } return repo.queryEventsTable(CompanySFIDFoundationSFIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } @@ -484,7 +484,7 @@ func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGrou keyCondition := expression.Key("company_sfid_project_id").Equal(expression.Value(key)) var filter expression.ConditionBuilder if companyID != "" { - filter = expression.Name("company_id").Equal(expression.Value(companyID)) + filter = expression.Name("event_company_id").Equal(expression.Value(companyID)) } return repo.queryEventsTable(CompanySFIDProjectIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 01647e697..3207f8f7e 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3170,7 +3170,7 @@ paths: tags: - company - /company/{companySFID}/project/{projectSFID}/contributors: + /company/{companyID}/project/{projectSFID}/contributors: get: summary: Get corporate contributors for project description: Returns list of corporate contributors for project @@ -3180,7 +3180,7 @@ paths: - $ref: "#/parameters/x-acl" - $ref: "#/parameters/x-username" - $ref: "#/parameters/x-email" - - $ref: "#/parameters/path-companySFID" + - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-projectSFID" - $ref: "#/parameters/searchTerm" responses: diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 30953b775..48fb2fddf 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -150,22 +150,31 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo "functionName": "company.handlers.CompanyGetCompanyProjectContributorsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, - "companySFID": params.CompanySFID, + "companyID": params.CompanyID, + } + + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v1CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) + if err != nil || v1CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } // PM - check if authorized by project scope - allow if PM has project ID scope that matches // Contact,Community Program Manager,CLA Manager,CLA Manager Designee,Company Admin - check if authorized by organization scope - allow if {Contact,Community Program Manager,CLA Manager,CLA Manager Designee,Company Admin} has organization ID scope that matches // CLA Manager - check if authorized by project|organization scope - allow if CLA Manager (for example) has project ID + org DI scope that matches log.WithFields(f).Debug("checking permissions") - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, params.CompanySFID, projectClaGroupRepo) { + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, projectClaGroupRepo) { return company.NewGetCompanyProjectContributorsForbidden().WithXRequestID(reqID).WithPayload( utils.ErrorResponseForbidden( reqID, fmt.Sprintf("user %s does not have access to get contributors with Project scope of %s or Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.ProjectSFID, params.CompanySFID))) + authUser.UserName, params.ProjectSFID, params.ProjectSFID, params.CompanyID))) } - result, err := service.GetCompanyProjectContributors(ctx, params.ProjectSFID, params.CompanySFID, utils.StringValue(params.SearchTerm)) + result, err := service.GetCompanyProjectContributors(ctx, params.ProjectSFID, params.CompanyID, utils.StringValue(params.SearchTerm)) if err != nil { if err == v1Company.ErrCompanyDoesNotExist { return company.NewGetCompanyProjectContributorsNotFound().WithXRequestID(reqID) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index a3f2d9e8a..5a7f09f6f 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -323,16 +323,16 @@ func (s *service) GetCompanyProjectActiveCLAs(ctx context.Context, companyID str return &out, nil } -func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) { +func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companyID string, searchTerm string) (*models.CorporateContributorList, error) { f := logrus.Fields{ "functionName": "GetCompanyProjectContributors", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "companySFID": companySFID, + "companyID": companyID, "searchTerm": searchTerm, } list := make([]*models.CorporateContributor, 0) - sigs, err := s.getAllCompanyProjectEmployeeSignatures(ctx, companySFID, projectSFID) + sigs, err := s.getAllCompanyProjectEmployeeSignatures(ctx, companyID, projectSFID) if err != nil { log.WithFields(f).Warnf("problem fetching all company project employee signatures, error: %+v", err) return nil, err @@ -1314,19 +1314,18 @@ func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepos result <- &contributor } -func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, companySFID string, projectSFID string) ([]*v1Models.Signature, error) { +func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, companyID string, projectSFID string) ([]*v1Models.Signature, error) { f := logrus.Fields{ "functionName": "company.service.getAllCompanyProjectEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companySFID": companySFID, + "companyID": companyID, "projectSFID": projectSFID, } log.WithFields(f).Debug("getAllCompanyProjectEmployeeSignatures") - comp, claGroup, err := s.getCompanyAndClaGroup(ctx, companySFID, projectSFID) + _, claGroup, err := s.getCompanyAndClaGroup(ctx, companyID, projectSFID) if err != nil { return nil, err } - companyID := comp.CompanyID params := v1SignatureParams.GetProjectCompanyEmployeeSignaturesParams{ HTTPRequest: nil, CompanyID: companyID, @@ -1340,11 +1339,11 @@ func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, co } // get company and project in parallel -func (s *service) getCompanyAndClaGroup(ctx context.Context, companySFID, projectSFID string) (*v1Models.Company, *v1Models.ClaGroup, error) { +func (s *service) getCompanyAndClaGroup(ctx context.Context, companyID, projectSFID string) (*v1Models.Company, *v1Models.ClaGroup, error) { f := logrus.Fields{ "functionName": "company.service.getCompanyAndClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companySFID": companySFID, + "companyID": companyID, "projectSFID": projectSFID, } var comp *v1Models.Company @@ -1355,7 +1354,7 @@ func (s *service) getCompanyAndClaGroup(ctx context.Context, companySFID, projec cp.Add(2) go func() { defer cp.Done() - comp, companyErr = s.companyRepo.GetCompanyByExternalID(ctx, companySFID) + comp, companyErr = s.companyRepo.GetCompany(ctx, companyID) }() go func() { defer cp.Done() From 0d4adcab2d7224b7bb0d6c1fdd9ced541f74817c Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Thu, 28 Jan 2021 18:14:07 +0200 Subject: [PATCH 0019/1276] createClaManagerDesignee companySFID -> companyID (#2540) Signed-off-by: makkalot --- cla-backend-go/v2/cla_manager/handlers.go | 2 +- cla-backend-go/v2/cla_manager/service.go | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 7d7d7c60a..696e308d0 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -330,7 +330,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C return cla_manager.NewCreateCLAManagerRequestForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - claManagerDesignee, err := service.CreateCLAManagerRequest(ctx, params.Body.ContactAdmin, v1CompanyModel.CompanyExternalID, params.ProjectSFID, params.Body.UserEmail.String(), + claManagerDesignee, err := service.CreateCLAManagerRequest(ctx, params.Body.ContactAdmin, v1CompanyModel.CompanyID, params.ProjectSFID, params.Body.UserEmail.String(), *params.Body.FullName, authUser, LfxPortalURL) if err != nil { diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 39467d46d..580bf3cb8 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -94,7 +94,7 @@ type Service interface { DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL string) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) - CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companySFID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) + CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, LfxPortalURL string) error CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) @@ -576,7 +576,7 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl if signedAtFoundationLevel { if foundationSFID != "" { - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyExternalID, foundationSFID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyID, foundationSFID, userEmail) if err != nil { if err == ErrCLAManagerDesigneeConflict { msg := fmt.Sprintf("Conflict assigning cla manager role for Foundation SFID: %s ", foundationSFID) @@ -607,7 +607,7 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl go func(swg *sync.WaitGroup, pcg *projects_cla_groups.ProjectClaGroup, designeeChannel chan *result) { defer swg.Done() log.WithFields(f).Debugf("creating CLA Manager Designee for Project SFID: %s", pcg.ProjectSFID) - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyExternalID, pcg.ProjectSFID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, v1CompanyModel.CompanyID, pcg.ProjectSFID, userEmail) var output result if err != nil { if err == ErrCLAManagerDesigneeConflict { @@ -647,12 +647,12 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl } // CreateCLAManagerRequest service method -func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companySFID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) { +func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) { f := logrus.Fields{ "functionName": "cla_manager.service.CreateCLAManagerRequest", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "contactAdmin": contactAdmin, - "companySFID": companySFID, + "companyID": companyID, "projectID": projectID, "userEmail": userEmail, "fullName": fullName, @@ -664,7 +664,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debugf("loading company by external ID...") // Search for salesForce Company aka external Company - v1CompanyModel, companyErr := s.companyService.GetCompanyByExternalID(ctx, companySFID) + v1CompanyModel, companyErr := s.companyService.GetCompany(ctx, companyID) if companyErr != nil { msg := fmt.Sprintf("EasyCLA - 400 Bad Request - %s", companyErr) log.Warn(msg) @@ -703,17 +703,17 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool if contactAdmin { log.WithFields(f).Debug("sending email to company Admin") log.WithFields(f).Debug("querying user admin scopes...") - scopes, listScopeErr := orgService.ListOrgUserAdminScopes(ctx, companySFID, nil) + scopes, listScopeErr := orgService.ListOrgUserAdminScopes(ctx, v1CompanyModel.CompanyExternalID, nil) if listScopeErr != nil { msg := fmt.Sprintf("EasyCLA - 400 Bad Request - Admin lookup error for organisation SFID: %s, error: %+v ", - companySFID, listScopeErr) + v1CompanyModel.CompanyExternalID, listScopeErr) log.WithFields(f).Warn(msg) return nil, listScopeErr } if len(scopes.Userroles) == 0 { msg := fmt.Sprintf("EasyCLA - 404 NotFound - No admins for organization SFID: %s", - companySFID) + v1CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) return nil, ErrNoOrgAdmins } @@ -748,7 +748,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool msg := fmt.Sprintf("User: %s does not have an LF Login", userEmail) log.WithFields(f).Warn(msg) // Send email - sendEmailErr := sendEmailToUserWithNoLFID(ctx, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, companySFID, &projectSF.ID, utils.CLADesigneeRole) + sendEmailErr := sendEmailToUserWithNoLFID(ctx, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole) if sendEmailErr != nil { log.WithFields(f).Warnf("Error sending email: %+v", sendEmailErr) return nil, sendEmailErr @@ -761,7 +761,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool } log.WithFields(f).Debug("sending CLA manager designee request...") - claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, companySFID, projectID, userEmail) + claManagerDesignee, err := s.CreateCLAManagerDesignee(ctx, companyID, projectID, userEmail) if err != nil { // Check conflict for role scope if _, ok := err.(*organizations.CreateOrgUsrRoleScopesConflict); ok { From 9bc0f88e37167ccdfcba8b3b2be5975bd7d09fa0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 28 Jan 2021 17:21:09 -0500 Subject: [PATCH 0020/1276] Added Get Company by ID/SFID + Added GitHub v4 GraphQL Lib (#2541) - Added v2 Get Company by ID/SFID swagger/API endpoints - Updated company not found error/404 logic - Added 404 error to v2 endponts that were missing it - Added initial release of GitHub v4 GraphQL API library and init logic Signed-off-by: makkalot Signed-off-by: David Deal Co-authored-by: Denis Kyorov --- .circleci/config.yml | 13 + cla-backend-go/Makefile | 3 +- .../cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/company/repository.go | 37 +- cla-backend-go/company/service.go | 4 +- cla-backend-go/config/config.go | 22 +- cla-backend-go/config/ssm.go | 22 +- cla-backend-go/github/.graphqlconfig | 16 + cla-backend-go/github/client.go | 12 + cla-backend-go/github/github-query.graphql | 25 + cla-backend-go/github/github-schema.graphql | 39977 ++++++++++++++++ cla-backend-go/github/protected_branch.go | 186 + cla-backend-go/go.mod | 2 + cla-backend-go/go.sum | 4 + cla-backend-go/init/init.go | 2 +- cla-backend-go/repositories/service.go | 2 +- cla-backend-go/swagger/cla.v1.yaml | 50 +- cla-backend-go/swagger/cla.v2.yaml | 73 +- cla-backend-go/tests/github_v4_test.go | 45 + cla-backend-go/users/handlers.go | 2 +- cla-backend-go/utils/conversion.go | 5 + cla-backend-go/utils/errors.go | 76 +- cla-backend-go/v2/cla_manager/handlers.go | 3 +- cla-backend-go/v2/company/handlers.go | 140 +- cla-backend-go/v2/company/service.go | 55 +- cla-backend-go/v2/dynamo_events/autoenable.go | 2 +- .../v2/dynamo_events/github_organization.go | 44 +- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/signatures/handlers.go | 12 +- cla-backend-go/v2/signatures/service.go | 4 +- 31 files changed, 40719 insertions(+), 127 deletions(-) create mode 100644 cla-backend-go/github/.graphqlconfig create mode 100644 cla-backend-go/github/github-query.graphql create mode 100644 cla-backend-go/github/github-schema.graphql create mode 100644 cla-backend-go/tests/github_v4_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e4d47925..b13471042 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -205,6 +205,7 @@ jobs: - add_ssh_keys: fingerprints: - "e9:13:85:f1:b1:a1:25:bf:f5:44:34:66:82:1e:31:59" + - *setup_aws - run: echo 'export NVM_DIR=${HOME}/.nvm' >> $BASH_ENV - *install-node-12 - run: echo 'export GO111MODULE=on' >> $BASH_ENV @@ -287,16 +288,28 @@ jobs: <<: *buildGoBackendAnchor environment: STAGE: dev + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 buildGoBackendStaging: <<: *buildGoBackendAnchor environment: STAGE: staging + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 buildGoBackendProd: <<: *buildGoBackendAnchor environment: STAGE: prod + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 # Deploys deployBackend: &deployBackendAnchor diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 10414aa88..25805ac3e 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -23,7 +23,6 @@ LINT_VERSION=v1.29.0 SWAGGER_TOOL_VERSION=v0.24.0 GO_PKGS=$(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') -TEST_ENV=AWS_REGION=us-east-1 DYNAMODB_AWS_REGION=us-east-1 AWS_PROFILE=bar AWS_ACCESS_KEY_ID=foo AWS_SECRET_ACCESS_KEY=bar .PHONY: generate setup tool-setup setup-dev setup-deploy clean-all clean swagger up fmt test run deps build build-mac build-aws-lambda user-subscribe-lambda qc lint @@ -170,7 +169,7 @@ fmt: test: @echo "Running unit tests..." - @ $(TEST_ENV) go test -v $(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) -coverprofile=cover.out + @go test -v $(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) -coverprofile=cover.out mock: @echo "Re-Generating mocks" diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index f9dba32de..2e9c30e74 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -91,7 +91,7 @@ func init() { githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) - github.Init(configFile.Github.AppID, configFile.Github.AppPrivateKey, configFile.Github.AccessToken) + github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) user_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) project_service.InitClient(configFile.APIGatewayURL) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 9242288c8..f62cba994 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -223,7 +223,7 @@ func server(localMode bool) http.Handler { if err != nil { logrus.Panic(err) } - github.Init(configFile.Github.AppID, configFile.Github.AppPrivateKey, configFile.Github.AccessToken) + github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) @@ -304,7 +304,7 @@ func server(localMode bool) http.Handler { v2Health.Configure(v2API, healthService) template.Configure(api, templateService, eventsService) v2Template.Configure(v2API, templateService, eventsService) - github.Configure(api, configFile.Github.ClientID, configFile.Github.ClientSecret, configFile.Github.AccessToken, sessionStore) + github.Configure(api, configFile.GitHub.ClientID, configFile.GitHub.ClientSecret, configFile.GitHub.AccessToken, sessionStore) signatures.Configure(api, v1SignaturesService, sessionStore, eventsService) v2Signatures.Configure(v2API, v1ProjectService, projectRepo, v1CompanyService, v1SignaturesService, sessionStore, eventsService, v2SignatureService, projectClaGroupRepo) approval_list.Configure(api, v1ApprovalListService, sessionStore, v1SignaturesService, eventsService) diff --git a/cla-backend-go/company/repository.go b/cla-backend-go/company/repository.go index 2708c608d..9dae3e5a7 100644 --- a/cla-backend-go/company/repository.go +++ b/cla-backend-go/company/repository.go @@ -5,7 +5,6 @@ package company import ( "context" - "errors" "fmt" "strings" @@ -28,11 +27,6 @@ import ( "github.com/gofrs/uuid" ) -// errors -var ( - ErrCompanyDoesNotExist = errors.New("company does not exist") -) - // IRepository interface methods type IRepository interface { //nolint CreateCompany(ctx context.Context, in *models.Company) (*models.Company, error) @@ -176,8 +170,12 @@ func (repo repository) GetCompanyByExternalID(ctx context.Context, companySFID s } if len(companyRecords) == 0 { log.WithFields(f).Debug("no records found") - return nil, ErrCompanyDoesNotExist + return nil, &utils.CompanyNotFound{ + Message: "no company records found for SFID", + CompanyID: companySFID, + } } + log.WithFields(f).Debugf("loaded %d records", len(companyRecords)) // For debug when problems occur f["companyName"] = companyRecords[0].CompanyName @@ -195,7 +193,10 @@ func (repo repository) GetCompanyByExternalID(ctx context.Context, companySFID s } f["signingEntityNames"] = strings.Join(signingEntityNames, ";") log.WithFields(f).Warning("unable to match company name with existing signing entity names") - return nil, ErrCompanyDoesNotExist + return nil, &utils.CompanyNotFound{ + Message: "company record not found - unable to match company name with existing signing entity names", + CompanyID: companySFID, + } } // GetCompaniesByExternalID returns a list of companies based on the company external ID. A company will have more than one if/when the SF record has multiple entity names - for which we create separate EasyCLA company records @@ -232,9 +233,13 @@ func (repo repository) GetCompaniesByExternalID(ctx context.Context, companySFID } if len(results.Items) == 0 { - log.WithFields(f).Debug("no records found") - return nil, ErrCompanyDoesNotExist + log.WithFields(f).Debug("no company records found") + return nil, &utils.CompanyNotFound{ + Message: "no company records found with matching external SFID", + CompanySFID: companySFID, + } } + var dbCompanyModels []DBModel err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbCompanyModels) if err != nil { @@ -279,8 +284,13 @@ func (repo repository) GetCompanyBySigningEntityName(ctx context.Context, signin } if len(results.Items) == 0 { - return nil, ErrCompanyDoesNotExist + return nil, &utils.CompanyNotFound{ + Message: "no company with signing entity name found", + CompanySigningEntityName: signingEntityName, + Err: nil, + } } + dbCompanyModel := DBModel{} err = dynamodbattribute.UnmarshalMap(results.Items[0], &dbCompanyModel) if err != nil { @@ -366,7 +376,10 @@ func (repo repository) GetCompany(ctx context.Context, companyID string) (*model } if len(companyTableData.Item) == 0 { - return nil, ErrCompanyDoesNotExist + return nil, &utils.CompanyNotFound{ + Message: "no company matching company record", + CompanyID: companyID, + } } dbCompanyModel := DBModel{} diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index dabfaa1f9..7fc942d04 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -634,7 +634,7 @@ func (s service) GetCompanyByExternalID(ctx context.Context, companySFID string) return comp, nil } - if err == ErrCompanyDoesNotExist { + if _, ok := err.(*utils.CompanyNotFound); ok { comp, err = s.CreateOrgFromExternalID(ctx, "", companySFID) if err != nil { return comp, err @@ -675,7 +675,7 @@ func (s service) GetCompanyBySigningEntityName(ctx context.Context, signingEntit return comp, nil } - if err == ErrCompanyDoesNotExist { + if _, ok := err.(*utils.CompanyNotFound); ok { log.WithFields(f).Debugf("Company with signing entity name %s does not exist", signingEntityName) comp, err = s.CreateOrgFromExternalID(ctx, signingEntityName, companySFID) if err != nil { diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index c562cdeed..537f7ab3f 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -38,8 +38,8 @@ type Config struct { // AWS AWS AWS `json:"aws"` - // Github Application - Github Github `json:"github"` + // GitHub Application + GitHub GitHub `json:"github"` // Dynamo Session Store SessionStoreTableName string `json:"sessionStoreTableName"` @@ -110,13 +110,17 @@ type AWS struct { Region string `json:"region"` } -// Github model -type Github struct { - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` - AccessToken string `json:"accessToken"` - AppID int `json:"app_id"` - AppPrivateKey string `json:"app_private_key"` +// GitHub model +type GitHub struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + AccessToken string `json:"accessToken"` + AppID int `json:"app_id"` + AppPrivateKey string `json:"app_private_key"` + TestOrganization string `json:"test_organization"` + TestOrganizationInstallationID string `json:"test_organization_installation_id"` + TestRepository string `json:"test_repository"` + TestRepositoryID string `json:"test_repository_id"` } // MetricsReport keeps the config needed to send the metrics data report diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 8138989e7..b6f0ba61c 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -65,6 +65,10 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-gh-access-token-%s", stage), fmt.Sprintf("cla-gh-app-id-%s", stage), fmt.Sprintf("cla-gh-app-private-key-%s", stage), + fmt.Sprintf("cla-gh-test-organization-%s", stage), + fmt.Sprintf("cla-gh-test-organization-installation-id-%s", stage), + fmt.Sprintf("cla-gh-test-repository-%s", stage), + fmt.Sprintf("cla-gh-test-repository-id-%s", stage), fmt.Sprintf("cla-corporate-base-%s", stage), fmt.Sprintf("cla-corporate-v2-base-%s", stage), fmt.Sprintf("cla-doc-raptor-api-key-%s", stage), @@ -118,20 +122,28 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint case fmt.Sprintf("cla-auth0-algorithm-%s", stage): config.Auth0.Algorithm = resp.value case fmt.Sprintf("cla-gh-oauth-client-id-go-backend-%s", stage): - config.Github.ClientID = resp.value + config.GitHub.ClientID = resp.value case fmt.Sprintf("cla-gh-oauth-secret-go-backend-%s", stage): - config.Github.ClientSecret = resp.value + config.GitHub.ClientSecret = resp.value case fmt.Sprintf("cla-gh-access-token-%s", stage): - config.Github.AccessToken = resp.value + config.GitHub.AccessToken = resp.value case fmt.Sprintf("cla-gh-app-id-%s", stage): githubAppID, err := strconv.Atoi(resp.value) if err != nil { errMsg := fmt.Sprintf("invalid value of key: %s", fmt.Sprintf("cla-gh-app-id-%s", stage)) log.WithFields(f).WithError(err).Fatal(errMsg) } - config.Github.AppID = githubAppID + config.GitHub.AppID = githubAppID case fmt.Sprintf("cla-gh-app-private-key-%s", stage): - config.Github.AppPrivateKey = resp.value + config.GitHub.AppPrivateKey = resp.value + case fmt.Sprintf("cla-gh-test-organization-%s", stage): + config.GitHub.TestOrganization = resp.value + case fmt.Sprintf("cla-gh-test-organization-installation-id-%s", stage): + config.GitHub.TestOrganizationInstallationID = resp.value + case fmt.Sprintf("cla-gh-test-repository-%s", stage): + config.GitHub.TestRepository = resp.value + case fmt.Sprintf("cla-gh-test-repository-id-%s", stage): + config.GitHub.TestRepositoryID = resp.value case fmt.Sprintf("cla-corporate-base-%s", stage): corporateConsoleURLValue := resp.value diff --git a/cla-backend-go/github/.graphqlconfig b/cla-backend-go/github/.graphqlconfig new file mode 100644 index 000000000..fc565542a --- /dev/null +++ b/cla-backend-go/github/.graphqlconfig @@ -0,0 +1,16 @@ +{ + "name": "GitHub API V4 GraphQL Schema", + "schemaPath": "github-schema.graphql", + "extensions": { + "endpoints": { + "GitHub API V4 GraphQL Endpoint": { + "url": "https://api.github.com/graphql", + "headers": { + "Authorization": "Bearer ${env:GITHUB-ACCESS-TOKEN}", + "user-agent": "JS GraphQL" + }, + "introspect": false + } + } + } +} diff --git a/cla-backend-go/github/client.go b/cla-backend-go/github/client.go index e6efd8591..7f526b7a7 100644 --- a/cla-backend-go/github/client.go +++ b/cla-backend-go/github/client.go @@ -8,6 +8,9 @@ import ( "errors" "fmt" "net/http" + "time" + + "github.com/shurcooL/githubv4" "github.com/bradleyfalzon/ghinstallation" "github.com/google/go-github/v33/github" @@ -77,6 +80,15 @@ func NewGithubAppClient(installationID int64) (*github.Client, error) { return github.NewClient(&http.Client{Transport: itr}), nil } +// NewGithubV4AppClient creates a new github v4 client from the supplied installationID +func NewGithubV4AppClient(installationID int64) (*githubv4.Client, error) { + authTransport, err := ghinstallation.New(http.DefaultTransport, int64(getGithubAppID()), installationID, []byte(getGithubAppPrivateKey())) + if err != nil { + return nil, err + } + return githubv4.NewClient(&http.Client{Transport: authTransport, Timeout: 5 * time.Second}), nil +} + // NewGithubOauthClient creates github client from global accessToken func NewGithubOauthClient() *github.Client { return NewGithubOauthClientWithAccessToken(getSecretAccessToken()) diff --git a/cla-backend-go/github/github-query.graphql b/cla-backend-go/github/github-query.graphql new file mode 100644 index 000000000..4cf4ccb40 --- /dev/null +++ b/cla-backend-go/github/github-query.graphql @@ -0,0 +1,25 @@ +query BranchProtectionRule($organizationOwner: String!, $repositoryName: String!) { + repository(owner: $organizationOwner, name: $repositoryName) { + id + createdAt + branchProtectionRules(first: 100) { + totalCount + nodes { + pattern + id + allowsDeletions + requiredApprovingReviewCount + requiredStatusCheckContexts + } + edges { + node { + allowsDeletions + id + pattern + } + } + } + diskUsage + hasIssuesEnabled + } +} diff --git a/cla-backend-go/github/github-schema.graphql b/cla-backend-go/github/github-schema.graphql new file mode 100644 index 000000000..3ae15a1a7 --- /dev/null +++ b/cla-backend-go/github/github-schema.graphql @@ -0,0 +1,39977 @@ +""" +Defines what type of global IDs are accepted for a mutation argument of type ID. +""" +directive @possibleTypes( + """ + Abstract type of accepted global ID + """ + abstractType: String + + """ + Accepted types of global IDs. + """ + concreteTypes: [String!]! +) on INPUT_FIELD_DEFINITION + +""" +Marks an element of a GraphQL schema as only available via a preview header +""" +directive @preview( + """ + The identifier of the API preview that toggles this field. + """ + toggledBy: String! +) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +""" +Autogenerated input type of AcceptEnterpriseAdministratorInvitation +""" +input AcceptEnterpriseAdministratorInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the invitation being accepted + """ + invitationId: ID! @possibleTypes(concreteTypes: ["EnterpriseAdministratorInvitation"]) +} + +""" +Autogenerated return type of AcceptEnterpriseAdministratorInvitation +""" +type AcceptEnterpriseAdministratorInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was accepted. + """ + invitation: EnterpriseAdministratorInvitation + + """ + A message confirming the result of accepting an administrator invitation. + """ + message: String +} + +""" +Autogenerated input type of AcceptTopicSuggestion +""" +input AcceptTopicSuggestionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the suggested topic. + """ + name: String! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of AcceptTopicSuggestion +""" +type AcceptTopicSuggestionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The accepted topic. + """ + topic: Topic +} + +""" +Represents an object which can take actions on GitHub. Typically a User or Bot. +""" +interface Actor { + """ + A URL pointing to the actor's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The username of the actor. + """ + login: String! + + """ + The HTTP path for this actor. + """ + resourcePath: URI! + + """ + The HTTP URL for this actor. + """ + url: URI! +} + +""" +Location information for an actor +""" +type ActorLocation { + """ + City + """ + city: String + + """ + Country name + """ + country: String + + """ + Country code + """ + countryCode: String + + """ + Region name + """ + region: String + + """ + Region or state code + """ + regionCode: String +} + +""" +Autogenerated input type of AddAssigneesToAssignable +""" +input AddAssigneesToAssignableInput { + """ + The id of the assignable object to add assignees to. + """ + assignableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Assignable") + + """ + The id of users to add as assignees. + """ + assigneeIds: [ID!]! @possibleTypes(concreteTypes: ["User"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of AddAssigneesToAssignable +""" +type AddAssigneesToAssignablePayload { + """ + The item that was assigned. + """ + assignable: Assignable + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of AddComment +""" +input AddCommentInput { + """ + The contents of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "IssueOrPullRequest") +} + +""" +Autogenerated return type of AddComment +""" +type AddCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The edge from the subject's comment connection. + """ + commentEdge: IssueCommentEdge + + """ + The subject + """ + subject: Node + + """ + The edge from the subject's timeline connection. + """ + timelineEdge: IssueTimelineItemEdge +} + +""" +Autogenerated input type of AddEnterpriseSupportEntitlement +""" +input AddEnterpriseSupportEntitlementInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a member who will receive the support entitlement. + """ + login: String! +} + +""" +Autogenerated return type of AddEnterpriseSupportEntitlement +""" +type AddEnterpriseSupportEntitlementPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of adding the support entitlement. + """ + message: String +} + +""" +Autogenerated input type of AddLabelsToLabelable +""" +input AddLabelsToLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ids of the labels to add. + """ + labelIds: [ID!]! @possibleTypes(concreteTypes: ["Label"]) + + """ + The id of the labelable object to add labels to. + """ + labelableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Labelable") +} + +""" +Autogenerated return type of AddLabelsToLabelable +""" +type AddLabelsToLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was labeled. + """ + labelable: Labelable +} + +""" +Autogenerated input type of AddProjectCard +""" +input AddProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The content of the card. Must be a member of the ProjectCardItem union + """ + contentId: ID @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "ProjectCardItem") + + """ + The note on the card. + """ + note: String + + """ + The Node ID of the ProjectColumn. + """ + projectColumnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of AddProjectCard +""" +type AddProjectCardPayload { + """ + The edge from the ProjectColumn's card connection. + """ + cardEdge: ProjectCardEdge + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectColumn + """ + projectColumn: ProjectColumn +} + +""" +Autogenerated input type of AddProjectColumn +""" +input AddProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the column. + """ + name: String! + + """ + The Node ID of the project. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) +} + +""" +Autogenerated return type of AddProjectColumn +""" +type AddProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The edge from the project's column connection. + """ + columnEdge: ProjectColumnEdge + + """ + The project + """ + project: Project +} + +""" +Autogenerated input type of AddPullRequestReviewComment +""" +input AddPullRequestReviewCommentInput { + """ + The text of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The SHA of the commit to comment on. + """ + commitOID: GitObjectID + + """ + The comment id to reply to. + """ + inReplyTo: ID @possibleTypes(concreteTypes: ["PullRequestReviewComment"]) + + """ + The relative path of the file to comment on. + """ + path: String + + """ + The line index in the diff to comment on. + """ + position: Int + + """ + The node ID of the pull request reviewing + """ + pullRequestId: ID @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Node ID of the review to modify. + """ + pullRequestReviewId: ID @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of AddPullRequestReviewComment +""" +type AddPullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created comment. + """ + comment: PullRequestReviewComment + + """ + The edge from the review's comment connection. + """ + commentEdge: PullRequestReviewCommentEdge +} + +""" +Autogenerated input type of AddPullRequestReview +""" +input AddPullRequestReviewInput { + """ + The contents of the review body comment. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The review line comments. + """ + comments: [DraftPullRequestReviewComment] + + """ + The commit OID the review pertains to. + """ + commitOID: GitObjectID + + """ + The event to perform on the pull request review. + """ + event: PullRequestReviewEvent + + """ + The Node ID of the pull request to modify. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The review line comment threads. + """ + threads: [DraftPullRequestReviewThread] +} + +""" +Autogenerated return type of AddPullRequestReview +""" +type AddPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created pull request review. + """ + pullRequestReview: PullRequestReview + + """ + The edge from the pull request's review connection. + """ + reviewEdge: PullRequestReviewEdge +} + +""" +Autogenerated input type of AddPullRequestReviewThread +""" +input AddPullRequestReviewThreadInput { + """ + Body of the thread's first comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The line of the blob to which the thread refers. The end of the line range for multi-line comments. + """ + line: Int! + + """ + Path to the file being commented on. + """ + path: String! + + """ + The node ID of the pull request reviewing + """ + pullRequestId: ID @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Node ID of the review to modify. + """ + pullRequestReviewId: ID @possibleTypes(concreteTypes: ["PullRequestReview"]) + + """ + The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. + """ + side: DiffSide = RIGHT + + """ + The first line of the range to which the comment refers. + """ + startLine: Int + + """ + The side of the diff on which the start line resides. + """ + startSide: DiffSide = RIGHT +} + +""" +Autogenerated return type of AddPullRequestReviewThread +""" +type AddPullRequestReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created thread. + """ + thread: PullRequestReviewThread +} + +""" +Autogenerated input type of AddReaction +""" +input AddReactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the emoji to react with. + """ + content: ReactionContent! + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["CommitComment", "Issue", "IssueComment", "PullRequest", "PullRequestReview", "PullRequestReviewComment", "TeamDiscussion", "TeamDiscussionComment"], abstractType: "Reactable") +} + +""" +Autogenerated return type of AddReaction +""" +type AddReactionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reaction object. + """ + reaction: Reaction + + """ + The reactable subject. + """ + subject: Reactable +} + +""" +Autogenerated input type of AddStar +""" +input AddStarInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Starrable ID to star. + """ + starrableId: ID! @possibleTypes(concreteTypes: ["Gist", "Repository", "Topic"], abstractType: "Starrable") +} + +""" +Autogenerated return type of AddStar +""" +type AddStarPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The starrable. + """ + starrable: Starrable +} + +""" +Autogenerated input type of AddVerifiableDomain +""" +input AddVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The URL of the domain + """ + domain: URI! + + """ + The ID of the owner to add the domain to + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Enterprise", "Organization"], abstractType: "VerifiableDomainOwner") +} + +""" +Autogenerated return type of AddVerifiableDomain +""" +type AddVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was added. + """ + domain: VerifiableDomain +} + +""" +Represents a 'added_to_project' event on a given issue or pull request. +""" +type AddedToProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard @preview(toggledBy: "starfox-preview") + + """ + Column name referenced by this project event. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +A GitHub App. +""" +type App implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the app. + """ + description: String + id: ID! + + """ + The hex color code, without the leading '#', for the logo background. + """ + logoBackgroundColor: String! + + """ + A URL pointing to the app's logo. + """ + logoUrl( + """ + The size of the resulting image. + """ + size: Int + ): URI! + + """ + The name of the app. + """ + name: String! + + """ + A slug based on the name of the app for use in URLs. + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The URL to the app's homepage. + """ + url: URI! +} + +""" +Autogenerated input type of ArchiveRepository +""" +input ArchiveRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to mark as archived. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of ArchiveRepository +""" +type ArchiveRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was marked as archived. + """ + repository: Repository +} + +""" +An object that can have users assigned to it. +""" +interface Assignable { + """ + A list of Users assigned to this object. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! +} + +""" +Represents an 'assigned' event on any assignable object. +""" +type AssignedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the assignable associated with the event. + """ + assignable: Assignable! + + """ + Identifies the user or mannequin that was assigned. + """ + assignee: Assignee + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the user who was assigned. + """ + user: User @deprecated(reason: "Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC.") +} + +""" +Types that can be assigned to issues. +""" +union Assignee = Bot | Mannequin | Organization | User + +""" +An entry in the audit log. +""" +interface AuditEntry { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Types that can initiate an audit log event. +""" +union AuditEntryActor = Bot | Organization | User + +""" +Ordering options for Audit Log connections. +""" +input AuditLogOrder { + """ + The ordering direction. + """ + direction: OrderDirection + + """ + The field to order Audit Logs by. + """ + field: AuditLogOrderField +} + +""" +Properties by which Audit Log connections can be ordered. +""" +enum AuditLogOrderField { + """ + Order audit log entries by timestamp + """ + CREATED_AT +} + +""" +Represents a 'auto_merge_disabled' event on a given pull request. +""" +type AutoMergeDisabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who disabled auto-merge for this Pull Request + """ + disabler: User + id: ID! + + """ + PullRequest referenced by event + """ + pullRequest: PullRequest + + """ + The reason auto-merge was disabled + """ + reason: String + + """ + The reason_code relating to why auto-merge was disabled + """ + reasonCode: String +} + +""" +Represents a 'auto_merge_enabled' event on a given pull request. +""" +type AutoMergeEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge for this Pull Request + """ + enabler: User + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'auto_rebase_enabled' event on a given pull request. +""" +type AutoRebaseEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge (rebase) for this Pull Request + """ + enabler: User + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'auto_squash_enabled' event on a given pull request. +""" +type AutoSquashEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge (squash) for this Pull Request + """ + enabler: User + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'automatic_base_change_failed' event on a given pull request. +""" +type AutomaticBaseChangeFailedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The new base for this PR + """ + newBase: String! + + """ + The old base for this PR + """ + oldBase: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'automatic_base_change_succeeded' event on a given pull request. +""" +type AutomaticBaseChangeSucceededEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The new base for this PR + """ + newBase: String! + + """ + The old base for this PR + """ + oldBase: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'base_ref_changed' event on a given issue or pull request. +""" +type BaseRefChangedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the name of the base ref for the pull request after it was changed. + """ + currentRefName: String! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Identifies the name of the base ref for the pull request before it was changed. + """ + previousRefName: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'base_ref_deleted' event on a given pull request. +""" +type BaseRefDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the name of the Ref associated with the `base_ref_deleted` event. + """ + baseRefName: String + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'base_ref_force_pushed' event on a given pull request. +""" +type BaseRefForcePushedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the after commit SHA for the 'base_ref_force_pushed' event. + """ + afterCommit: Commit + + """ + Identifies the before commit SHA for the 'base_ref_force_pushed' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the fully qualified ref name for the 'base_ref_force_pushed' event. + """ + ref: Ref +} + +""" +Represents a Git blame. +""" +type Blame { + """ + The list of ranges from a Git blame. + """ + ranges: [BlameRange!]! +} + +""" +Represents a range of information from a Git blame. +""" +type BlameRange { + """ + Identifies the recency of the change, from 1 (new) to 10 (old). This is + calculated as a 2-quantile and determines the length of distance between the + median age of all the changes in the file and the recency of the current + range's change. + """ + age: Int! + + """ + Identifies the line author + """ + commit: Commit! + + """ + The ending line for the range + """ + endingLine: Int! + + """ + The starting line for the range + """ + startingLine: Int! +} + +""" +Represents a Git blob. +""" +type Blob implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + Byte size of Blob object + """ + byteSize: Int! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + id: ID! + + """ + Indicates whether the Blob is binary or text. Returns null if unable to determine the encoding. + """ + isBinary: Boolean + + """ + Indicates whether the contents is truncated + """ + isTruncated: Boolean! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! + + """ + UTF8 text data or null if the Blob is binary + """ + text: String +} + +""" +A special type of user which takes actions on behalf of GitHub Apps. +""" +type Bot implements Actor & Node & UniformResourceLocatable { + """ + A URL pointing to the GitHub App's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The username of the actor. + """ + login: String! + + """ + The HTTP path for this bot + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this bot + """ + url: URI! +} + +""" +A branch protection rule. +""" +type BranchProtectionRule implements Node { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean! + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean! + + """ + A list of conflicts matching branches protection rule and other branch protection rules + """ + branchProtectionRuleConflicts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): BranchProtectionRuleConflictConnection! + + """ + The actor who created this branch protection rule. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean! + id: ID! + + """ + Can admins overwrite branch protection. + """ + isAdminEnforced: Boolean! + + """ + Repository refs that are protected by this rule + """ + matchingRefs( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters refs with query on name + """ + query: String + ): RefConnection! + + """ + Identifies the protection rule pattern. + """ + pattern: String! + + """ + A list push allowances for this branch protection rule. + """ + pushAllowances( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PushAllowanceConnection! + + """ + The repository associated with this branch protection rule. + """ + repository: Repository + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean! + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean! + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean! + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean! + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean! + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean! + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean! + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean! + + """ + A list review dismissal allowances for this branch protection rule. + """ + reviewDismissalAllowances( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReviewDismissalAllowanceConnection! +} + +""" +A conflict between two branch protection rules. +""" +type BranchProtectionRuleConflict { + """ + Identifies the branch protection rule. + """ + branchProtectionRule: BranchProtectionRule + + """ + Identifies the conflicting branch protection rule. + """ + conflictingBranchProtectionRule: BranchProtectionRule + + """ + Identifies the branch ref that has conflicting rules + """ + ref: Ref +} + +""" +The connection type for BranchProtectionRuleConflict. +""" +type BranchProtectionRuleConflictConnection { + """ + A list of edges. + """ + edges: [BranchProtectionRuleConflictEdge] + + """ + A list of nodes. + """ + nodes: [BranchProtectionRuleConflict] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BranchProtectionRuleConflictEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BranchProtectionRuleConflict +} + +""" +The connection type for BranchProtectionRule. +""" +type BranchProtectionRuleConnection { + """ + A list of edges. + """ + edges: [BranchProtectionRuleEdge] + + """ + A list of nodes. + """ + nodes: [BranchProtectionRule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BranchProtectionRuleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BranchProtectionRule +} + +""" +Autogenerated input type of CancelEnterpriseAdminInvitation +""" +input CancelEnterpriseAdminInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pending enterprise administrator invitation. + """ + invitationId: ID! @possibleTypes(concreteTypes: ["EnterpriseAdministratorInvitation"]) +} + +""" +Autogenerated return type of CancelEnterpriseAdminInvitation +""" +type CancelEnterpriseAdminInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was canceled. + """ + invitation: EnterpriseAdministratorInvitation + + """ + A message confirming the result of canceling an administrator invitation. + """ + message: String +} + +""" +Autogenerated input type of ChangeUserStatus +""" +input ChangeUserStatusInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The emoji to represent your status. Can either be a native Unicode emoji or an emoji name with colons, e.g., :grinning:. + """ + emoji: String + + """ + If set, the user status will not be shown after this date. + """ + expiresAt: DateTime + + """ + Whether this status should indicate you are not fully available on GitHub, e.g., you are away. + """ + limitedAvailability: Boolean = false + + """ + A short description of your current status. + """ + message: String + + """ + The ID of the organization whose members will be allowed to see the status. If + omitted, the status will be publicly visible. + """ + organizationId: ID @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of ChangeUserStatus +""" +type ChangeUserStatusPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Your updated status. + """ + status: UserStatus +} + +""" +A single check annotation. +""" +type CheckAnnotation { + """ + The annotation's severity level. + """ + annotationLevel: CheckAnnotationLevel + + """ + The path to the file that this annotation was made on. + """ + blobUrl: URI! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The position of this annotation. + """ + location: CheckAnnotationSpan! + + """ + The annotation's message. + """ + message: String! + + """ + The path that this annotation was made on. + """ + path: String! + + """ + Additional information about the annotation. + """ + rawDetails: String + + """ + The annotation's title + """ + title: String +} + +""" +The connection type for CheckAnnotation. +""" +type CheckAnnotationConnection { + """ + A list of edges. + """ + edges: [CheckAnnotationEdge] + + """ + A list of nodes. + """ + nodes: [CheckAnnotation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Information from a check run analysis to specific lines of code. +""" +input CheckAnnotationData { + """ + Represents an annotation's information level + """ + annotationLevel: CheckAnnotationLevel! + + """ + The location of the annotation + """ + location: CheckAnnotationRange! + + """ + A short description of the feedback for these lines of code. + """ + message: String! + + """ + The path of the file to add an annotation to. + """ + path: String! + + """ + Details about this annotation. + """ + rawDetails: String + + """ + The title that represents the annotation. + """ + title: String +} + +""" +An edge in a connection. +""" +type CheckAnnotationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckAnnotation +} + +""" +Represents an annotation's information level. +""" +enum CheckAnnotationLevel { + """ + An annotation indicating an inescapable error. + """ + FAILURE + + """ + An annotation indicating some information. + """ + NOTICE + + """ + An annotation indicating an ignorable error. + """ + WARNING +} + +""" +A character position in a check annotation. +""" +type CheckAnnotationPosition { + """ + Column number (1 indexed). + """ + column: Int + + """ + Line number (1 indexed). + """ + line: Int! +} + +""" +Information from a check run analysis to specific lines of code. +""" +input CheckAnnotationRange { + """ + The ending column of the range. + """ + endColumn: Int + + """ + The ending line of the range. + """ + endLine: Int! + + """ + The starting column of the range. + """ + startColumn: Int + + """ + The starting line of the range. + """ + startLine: Int! +} + +""" +An inclusive pair of positions for a check annotation. +""" +type CheckAnnotationSpan { + """ + End position (inclusive). + """ + end: CheckAnnotationPosition! + + """ + Start position (inclusive). + """ + start: CheckAnnotationPosition! +} + +""" +The possible states for a check suite or run conclusion. +""" +enum CheckConclusionState { + """ + The check suite or run requires action. + """ + ACTION_REQUIRED + + """ + The check suite or run has been cancelled. + """ + CANCELLED + + """ + The check suite or run has failed. + """ + FAILURE + + """ + The check suite or run was neutral. + """ + NEUTRAL + + """ + The check suite or run was skipped. + """ + SKIPPED + + """ + The check suite or run was marked stale by GitHub. Only GitHub can use this conclusion. + """ + STALE + + """ + The check suite or run has failed at startup. + """ + STARTUP_FAILURE + + """ + The check suite or run has succeeded. + """ + SUCCESS + + """ + The check suite or run has timed out. + """ + TIMED_OUT +} + +""" +A check run. +""" +type CheckRun implements Node & UniformResourceLocatable { + """ + The check run's annotations + """ + annotations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CheckAnnotationConnection + + """ + The check suite that this run is a part of. + """ + checkSuite: CheckSuite! + + """ + Identifies the date and time when the check run was completed. + """ + completedAt: DateTime + + """ + The conclusion of the check run. + """ + conclusion: CheckConclusionState + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The URL from which to find full details of the check run on the integrator's site. + """ + detailsUrl: URI + + """ + A reference for the check run on the integrator's system. + """ + externalId: String + id: ID! + + """ + The name of the check for this check run. + """ + name: String! + + """ + The permalink to the check run summary. + """ + permalink: URI! + + """ + The repository associated with this check run. + """ + repository: Repository! + + """ + The HTTP path for this check run. + """ + resourcePath: URI! + + """ + Identifies the date and time when the check run was started. + """ + startedAt: DateTime + + """ + The current status of the check run. + """ + status: CheckStatusState! + + """ + A string representing the check run's summary + """ + summary: String + + """ + A string representing the check run's text + """ + text: String + + """ + A string representing the check run + """ + title: String + + """ + The HTTP URL for this check run. + """ + url: URI! +} + +""" +Possible further actions the integrator can perform. +""" +input CheckRunAction { + """ + A short explanation of what this action would do. + """ + description: String! + + """ + A reference for the action on the integrator's system. + """ + identifier: String! + + """ + The text to be displayed on a button in the web UI. + """ + label: String! +} + +""" +The connection type for CheckRun. +""" +type CheckRunConnection { + """ + A list of edges. + """ + edges: [CheckRunEdge] + + """ + A list of nodes. + """ + nodes: [CheckRun] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckRunEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckRun +} + +""" +The filters that are available when fetching check runs. +""" +input CheckRunFilter { + """ + Filters the check runs created by this application ID. + """ + appId: Int + + """ + Filters the check runs by this name. + """ + checkName: String + + """ + Filters the check runs by this type. + """ + checkType: CheckRunType + + """ + Filters the check runs by this status. + """ + status: CheckStatusState +} + +""" +Descriptive details about the check run. +""" +input CheckRunOutput { + """ + The annotations that are made as part of the check run. + """ + annotations: [CheckAnnotationData!] + + """ + Images attached to the check run output displayed in the GitHub pull request UI. + """ + images: [CheckRunOutputImage!] + + """ + The summary of the check run (supports Commonmark). + """ + summary: String! + + """ + The details of the check run (supports Commonmark). + """ + text: String + + """ + A title to provide for this check run. + """ + title: String! +} + +""" +Images attached to the check run output displayed in the GitHub pull request UI. +""" +input CheckRunOutputImage { + """ + The alternative text for the image. + """ + alt: String! + + """ + A short image description. + """ + caption: String + + """ + The full URL of the image. + """ + imageUrl: URI! +} + +""" +The possible types of check runs. +""" +enum CheckRunType { + """ + Every check run available. + """ + ALL + + """ + The latest check run. + """ + LATEST +} + +""" +The possible states for a check suite or run status. +""" +enum CheckStatusState { + """ + The check suite or run has been completed. + """ + COMPLETED + + """ + The check suite or run is in progress. + """ + IN_PROGRESS + + """ + The check suite or run has been queued. + """ + QUEUED + + """ + The check suite or run has been requested. + """ + REQUESTED + + """ + The check suite or run is in waiting state. + """ + WAITING +} + +""" +A check suite. +""" +type CheckSuite implements Node { + """ + The GitHub App which created this check suite. + """ + app: App + + """ + The name of the branch for this check suite. + """ + branch: Ref + + """ + The check runs associated with a check suite. + """ + checkRuns( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filters the check runs by this type. + """ + filterBy: CheckRunFilter + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CheckRunConnection + + """ + The commit for this check suite + """ + commit: Commit! + + """ + The conclusion of this check suite. + """ + conclusion: CheckConclusionState + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + A list of open pull requests matching the check suite. + """ + matchingPullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection + + """ + The push that triggered this check suite. + """ + push: Push + + """ + The repository associated with this check suite. + """ + repository: Repository! + + """ + The HTTP path for this check suite + """ + resourcePath: URI! + + """ + The status of this check suite. + """ + status: CheckStatusState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this check suite + """ + url: URI! +} + +""" +The auto-trigger preferences that are available for check suites. +""" +input CheckSuiteAutoTriggerPreference { + """ + The node ID of the application that owns the check suite. + """ + appId: ID! + + """ + Set to `true` to enable automatic creation of CheckSuite events upon pushes to the repository. + """ + setting: Boolean! +} + +""" +The connection type for CheckSuite. +""" +type CheckSuiteConnection { + """ + A list of edges. + """ + edges: [CheckSuiteEdge] + + """ + A list of nodes. + """ + nodes: [CheckSuite] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckSuiteEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckSuite +} + +""" +The filters that are available when fetching check suites. +""" +input CheckSuiteFilter { + """ + Filters the check suites created by this application ID. + """ + appId: Int + + """ + Filters the check suites by this name. + """ + checkName: String +} + +""" +Autogenerated input type of ClearLabelsFromLabelable +""" +input ClearLabelsFromLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the labelable object to clear the labels from. + """ + labelableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Labelable") +} + +""" +Autogenerated return type of ClearLabelsFromLabelable +""" +type ClearLabelsFromLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was unlabeled. + """ + labelable: Labelable +} + +""" +Autogenerated input type of CloneProject +""" +input CloneProjectInput { + """ + The description of the project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not to clone the source project's workflows. + """ + includeWorkflows: Boolean! + + """ + The name of the project. + """ + name: String! + + """ + The visibility of the project, defaults to false (private). + """ + public: Boolean + + """ + The source project to clone. + """ + sourceId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + The owner ID to create the project under. + """ + targetOwnerId: ID! @possibleTypes(concreteTypes: ["Organization", "Repository", "User"], abstractType: "ProjectOwner") +} + +""" +Autogenerated return type of CloneProject +""" +type CloneProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the JobStatus for populating cloned fields. + """ + jobStatusId: String + + """ + The new cloned project. + """ + project: Project +} + +""" +Autogenerated input type of CloneTemplateRepository +""" +input CloneTemplateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A short description of the new repository. + """ + description: String + + """ + Whether to copy all branches from the template to the new repository. Defaults + to copying only the default branch of the template. + """ + includeAllBranches: Boolean = false + + """ + The name of the new repository. + """ + name: String! + + """ + The ID of the owner for the new repository. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "RepositoryOwner") + + """ + The Node ID of the template repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! +} + +""" +Autogenerated return type of CloneTemplateRepository +""" +type CloneTemplateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository. + """ + repository: Repository +} + +""" +An object that can be closed +""" +interface Closable { + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime +} + +""" +Autogenerated input type of CloseIssue +""" +input CloseIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to be closed. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of CloseIssue +""" +type CloseIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was closed. + """ + issue: Issue +} + +""" +Autogenerated input type of ClosePullRequest +""" +input ClosePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be closed. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of ClosePullRequest +""" +type ClosePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was closed. + """ + pullRequest: PullRequest +} + +""" +Represents a 'closed' event on any `Closable`. +""" +type ClosedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Object that was closed. + """ + closable: Closable! + + """ + Object which triggered the creation of this event. + """ + closer: Closer + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The HTTP path for this closed event. + """ + resourcePath: URI! + + """ + The HTTP URL for this closed event. + """ + url: URI! +} + +""" +The object which triggered a `ClosedEvent`. +""" +union Closer = Commit | PullRequest + +""" +The Code of Conduct for a repository +""" +type CodeOfConduct implements Node { + """ + The body of the Code of Conduct + """ + body: String + id: ID! + + """ + The key for the Code of Conduct + """ + key: String! + + """ + The formal name of the Code of Conduct + """ + name: String! + + """ + The HTTP path for this Code of Conduct + """ + resourcePath: URI + + """ + The HTTP URL for this Code of Conduct + """ + url: URI +} + +""" +Collaborators affiliation level with a subject. +""" +enum CollaboratorAffiliation { + """ + All collaborators the authenticated user can see. + """ + ALL + + """ + All collaborators with permissions to an organization-owned subject, regardless of organization membership status. + """ + DIRECT + + """ + All outside collaborators of an organization-owned subject. + """ + OUTSIDE +} + +""" +Represents a comment. +""" +interface Comment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +A comment author association with repository. +""" +enum CommentAuthorAssociation { + """ + Author has been invited to collaborate on the repository. + """ + COLLABORATOR + + """ + Author has previously committed to the repository. + """ + CONTRIBUTOR + + """ + Author has not previously committed to GitHub. + """ + FIRST_TIMER + + """ + Author has not previously committed to the repository. + """ + FIRST_TIME_CONTRIBUTOR + + """ + Author is a placeholder for an unclaimed user. + """ + MANNEQUIN + + """ + Author is a member of the organization that owns the repository. + """ + MEMBER + + """ + Author has no association with the repository. + """ + NONE + + """ + Author is the owner of the repository. + """ + OWNER +} + +""" +The possible errors that will prevent a user from updating a comment. +""" +enum CommentCannotUpdateReason { + """ + Unable to create comment because repository is archived. + """ + ARCHIVED + + """ + You cannot update this comment + """ + DENIED + + """ + You must be the author or have write access to this repository to update this comment. + """ + INSUFFICIENT_ACCESS + + """ + Unable to create comment because issue is locked. + """ + LOCKED + + """ + You must be logged in to update this comment. + """ + LOGIN_REQUIRED + + """ + Repository is under maintenance. + """ + MAINTENANCE + + """ + At least one email address must be verified to update this comment. + """ + VERIFIED_EMAIL_REQUIRED +} + +""" +Represents a 'comment_deleted' event on a given issue or pull request. +""" +type CommentDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The user who authored the deleted comment. + """ + deletedCommentAuthor: Actor + id: ID! +} + +""" +Represents a Git commit. +""" +type Commit implements GitObject & Node & Subscribable & UniformResourceLocatable { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The number of additions in this commit. + """ + additions: Int! + + """ + The pull requests associated with a commit + """ + associatedPullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests. + """ + orderBy: PullRequestOrder = {field: CREATED_AT, direction: ASC} + ): PullRequestConnection + + """ + Authorship details of the commit. + """ + author: GitActor + + """ + Check if the committer and the author match. + """ + authoredByCommitter: Boolean! + + """ + The datetime when this commit was authored. + """ + authoredDate: DateTime! + + """ + The list of authors for this commit based on the git author and the Co-authored-by + message trailer. The git author will always be first. + """ + authors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): GitActorConnection! + + """ + Fetches `git blame` information. + """ + blame( + """ + The file whose Git blame information you want. + """ + path: String! + ): Blame! + + """ + The number of changed files in this commit. + """ + changedFiles: Int! + + """ + The check suites associated with a commit. + """ + checkSuites( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filters the check suites by this type. + """ + filterBy: CheckSuiteFilter + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CheckSuiteConnection + + """ + Comments made on the commit. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + The datetime when this commit was committed. + """ + committedDate: DateTime! + + """ + Check if committed via GitHub web UI. + """ + committedViaWeb: Boolean! + + """ + Committer details of the commit. + """ + committer: GitActor + + """ + The number of deletions in this commit. + """ + deletions: Int! + + """ + The deployments associated with a commit. + """ + deployments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Environments to list deployments for + """ + environments: [String!] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for deployments returned from the connection. + """ + orderBy: DeploymentOrder = {field: CREATED_AT, direction: ASC} + ): DeploymentConnection + + """ + The tree entry representing the file located at the given path. + """ + file( + """ + The path for the file + """ + path: String! + ): TreeEntry + + """ + The linear commit history starting from (and including) this commit, in the same order as `git log`. + """ + history( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + If non-null, filters history to only show commits with matching authorship. + """ + author: CommitAuthor + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + If non-null, filters history to only show commits touching files under this path. + """ + path: String + + """ + Allows specifying a beginning time or date for fetching commits. + """ + since: GitTimestamp + + """ + Allows specifying an ending time or date for fetching commits. + """ + until: GitTimestamp + ): CommitHistoryConnection! + id: ID! + + """ + The Git commit message + """ + message: String! + + """ + The Git commit message body + """ + messageBody: String! + + """ + The commit message body rendered to HTML. + """ + messageBodyHTML: HTML! + + """ + The Git commit message headline + """ + messageHeadline: String! + + """ + The commit message headline rendered to HTML. + """ + messageHeadlineHTML: HTML! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The organization this commit was made on behalf of. + """ + onBehalfOf: Organization + + """ + The parents of a commit. + """ + parents( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitConnection! + + """ + The datetime when this commit was pushed. + """ + pushedDate: DateTime + + """ + The Repository this commit belongs to + """ + repository: Repository! + + """ + The HTTP path for this commit + """ + resourcePath: URI! + + """ + Commit signing information, if present. + """ + signature: GitSignature + + """ + Status information for this commit + """ + status: Status + + """ + Check and Status rollup information for this commit. + """ + statusCheckRollup: StatusCheckRollup + + """ + Returns a list of all submodules in this repository as of this Commit parsed from the .gitmodules file. + """ + submodules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): SubmoduleConnection! + + """ + Returns a URL to download a tarball archive for a repository. + Note: For private repositories, these links are temporary and expire after five minutes. + """ + tarballUrl: URI! + + """ + Commit's root Tree + """ + tree: Tree! + + """ + The HTTP path for the tree of this commit + """ + treeResourcePath: URI! + + """ + The HTTP URL for the tree of this commit + """ + treeUrl: URI! + + """ + The HTTP URL for this commit + """ + url: URI! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + Returns a URL to download a zipball archive for a repository. + Note: For private repositories, these links are temporary and expire after five minutes. + """ + zipballUrl: URI! +} + +""" +Specifies an author for filtering Git commits. +""" +input CommitAuthor { + """ + Email addresses to filter by. Commits authored by any of the specified email addresses will be returned. + """ + emails: [String!] + + """ + ID of a User to filter by. If non-null, only commits authored by this user + will be returned. This field takes precedence over emails. + """ + id: ID +} + +""" +Represents a comment on a given Commit. +""" +type CommitComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the comment body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the commit associated with the comment, if the commit exists. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. + """ + minimizedReason: String + + """ + Identifies the file path associated with the comment. + """ + path: String + + """ + Identifies the line position associated with the comment. + """ + position: Int + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this commit comment. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this commit comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for CommitComment. +""" +type CommitCommentConnection { + """ + A list of edges. + """ + edges: [CommitCommentEdge] + + """ + A list of nodes. + """ + nodes: [CommitComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CommitCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CommitComment +} + +""" +A thread of comments on a commit. +""" +type CommitCommentThread implements Node & RepositoryNode { + """ + The comments that exist in this thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The commit the comments were made on. + """ + commit: Commit + id: ID! + + """ + The file the comments were made on. + """ + path: String + + """ + The position in the diff for the commit that the comment was made on. + """ + position: Int + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +The connection type for Commit. +""" +type CommitConnection { + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Ordering options for commit contribution connections. +""" +input CommitContributionOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field by which to order commit contributions. + """ + field: CommitContributionOrderField! +} + +""" +Properties by which commit contribution connections can be ordered. +""" +enum CommitContributionOrderField { + """ + Order commit contributions by how many commits they represent. + """ + COMMIT_COUNT + + """ + Order commit contributions by when they were made. + """ + OCCURRED_AT +} + +""" +This aggregates commits made by a user within one repository. +""" +type CommitContributionsByRepository { + """ + The commit contributions, each representing a day. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for commit contributions returned from the connection. + """ + orderBy: CommitContributionOrder = {field: OCCURRED_AT, direction: DESC} + ): CreatedCommitContributionConnection! + + """ + The repository in which the commits were made. + """ + repository: Repository! + + """ + The HTTP path for the user's commits to the repository in this time range. + """ + resourcePath: URI! + + """ + The HTTP URL for the user's commits to the repository in this time range. + """ + url: URI! +} + +""" +An edge in a connection. +""" +type CommitEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Commit +} + +""" +The connection type for Commit. +""" +type CommitHistoryConnection { + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a 'connected' event on a given issue or pull request. +""" +type ConnectedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Issue or pull request that made the reference. + """ + source: ReferencedSubject! + + """ + Issue or pull request which was connected. + """ + subject: ReferencedSubject! +} + +""" +A content attachment +""" +type ContentAttachment { + """ + The body text of the content attachment. This parameter supports markdown. + """ + body: String! + + """ + The content reference that the content attachment is attached to. + """ + contentReference: ContentReference! + + """ + Identifies the primary key from the database. + """ + databaseId: Int! + id: ID! + + """ + The title of the content attachment. + """ + title: String! +} + +""" +A content reference +""" +type ContentReference { + """ + Identifies the primary key from the database. + """ + databaseId: Int! + id: ID! + + """ + The reference of the content reference. + """ + reference: String! +} + +""" +Represents a contribution a user made on GitHub, such as opening an issue. +""" +interface Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A calendar of contributions made on GitHub by a user. +""" +type ContributionCalendar { + """ + A list of hex color codes used in this calendar. The darker the color, the more contributions it represents. + """ + colors: [String!]! + + """ + Determine if the color set was chosen because it's currently Halloween. + """ + isHalloween: Boolean! + + """ + A list of the months of contributions in this calendar. + """ + months: [ContributionCalendarMonth!]! + + """ + The count of total contributions in the calendar. + """ + totalContributions: Int! + + """ + A list of the weeks of contributions in this calendar. + """ + weeks: [ContributionCalendarWeek!]! +} + +""" +Represents a single day of contributions on GitHub by a user. +""" +type ContributionCalendarDay { + """ + The hex color code that represents how many contributions were made on this day compared to others in the calendar. + """ + color: String! + + """ + How many contributions were made by the user on this day. + """ + contributionCount: Int! + + """ + Indication of contributions, relative to other days. Can be used to indicate + which color to represent this day on a calendar. + """ + contributionLevel: ContributionLevel! + + """ + The day this square represents. + """ + date: Date! + + """ + A number representing which day of the week this square represents, e.g., 1 is Monday. + """ + weekday: Int! +} + +""" +A month of contributions in a user's contribution graph. +""" +type ContributionCalendarMonth { + """ + The date of the first day of this month. + """ + firstDay: Date! + + """ + The name of the month. + """ + name: String! + + """ + How many weeks started in this month. + """ + totalWeeks: Int! + + """ + The year the month occurred in. + """ + year: Int! +} + +""" +A week of contributions in a user's contribution graph. +""" +type ContributionCalendarWeek { + """ + The days of contributions in this week. + """ + contributionDays: [ContributionCalendarDay!]! + + """ + The date of the earliest square in this week. + """ + firstDay: Date! +} + +""" +Varying levels of contributions from none to many. +""" +enum ContributionLevel { + """ + Lowest 25% of days of contributions. + """ + FIRST_QUARTILE + + """ + Highest 25% of days of contributions. More contributions than the third quartile. + """ + FOURTH_QUARTILE + + """ + No contributions occurred. + """ + NONE + + """ + Second lowest 25% of days of contributions. More contributions than the first quartile. + """ + SECOND_QUARTILE + + """ + Second highest 25% of days of contributions. More contributions than second quartile, less than the fourth quartile. + """ + THIRD_QUARTILE +} + +""" +Ordering options for contribution connections. +""" +input ContributionOrder { + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +A contributions collection aggregates contributions such as opened issues and commits created by a user. +""" +type ContributionsCollection { + """ + Commit contributions made by the user, grouped by repository. + """ + commitContributionsByRepository( + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [CommitContributionsByRepository!]! + + """ + A calendar of this user's contributions on GitHub. + """ + contributionCalendar: ContributionCalendar! + + """ + The years the user has been making contributions with the most recent year first. + """ + contributionYears: [Int!]! + + """ + Determine if this collection's time span ends in the current month. + """ + doesEndInCurrentMonth: Boolean! + + """ + The date of the first restricted contribution the user made in this time + period. Can only be non-null when the user has enabled private contribution counts. + """ + earliestRestrictedContributionDate: Date + + """ + The ending date and time of this collection. + """ + endedAt: DateTime! + + """ + The first issue the user opened on GitHub. This will be null if that issue was + opened outside the collection's time range and ignoreTimeRange is false. If + the issue is not visible but the user has opted to show private contributions, + a RestrictedContribution will be returned. + """ + firstIssueContribution: CreatedIssueOrRestrictedContribution + + """ + The first pull request the user opened on GitHub. This will be null if that + pull request was opened outside the collection's time range and + ignoreTimeRange is not true. If the pull request is not visible but the user + has opted to show private contributions, a RestrictedContribution will be returned. + """ + firstPullRequestContribution: CreatedPullRequestOrRestrictedContribution + + """ + The first repository the user created on GitHub. This will be null if that + first repository was created outside the collection's time range and + ignoreTimeRange is false. If the repository is not visible, then a + RestrictedContribution is returned. + """ + firstRepositoryContribution: CreatedRepositoryOrRestrictedContribution + + """ + Does the user have any more activity in the timeline that occurred prior to the collection's time range? + """ + hasActivityInThePast: Boolean! + + """ + Determine if there are any contributions in this collection. + """ + hasAnyContributions: Boolean! + + """ + Determine if the user made any contributions in this time frame whose details + are not visible because they were made in a private repository. Can only be + true if the user enabled private contribution counts. + """ + hasAnyRestrictedContributions: Boolean! + + """ + Whether or not the collector's time span is all within the same day. + """ + isSingleDay: Boolean! + + """ + A list of issues the user opened. + """ + issueContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Should the user's first issue ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from the result. + """ + excludePopular: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedIssueContributionConnection! + + """ + Issue contributions made by the user, grouped by repository. + """ + issueContributionsByRepository( + """ + Should the user's first issue ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from the result. + """ + excludePopular: Boolean = false + + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [IssueContributionsByRepository!]! + + """ + When the user signed up for GitHub. This will be null if that sign up date + falls outside the collection's time range and ignoreTimeRange is false. + """ + joinedGitHubContribution: JoinedGitHubContribution + + """ + The date of the most recent restricted contribution the user made in this time + period. Can only be non-null when the user has enabled private contribution counts. + """ + latestRestrictedContributionDate: Date + + """ + When this collection's time range does not include any activity from the user, use this + to get a different collection from an earlier time range that does have activity. + """ + mostRecentCollectionWithActivity: ContributionsCollection + + """ + Returns a different contributions collection from an earlier time range than this one + that does not have any contributions. + """ + mostRecentCollectionWithoutActivity: ContributionsCollection + + """ + The issue the user opened on GitHub that received the most comments in the specified + time frame. + """ + popularIssueContribution: CreatedIssueContribution + + """ + The pull request the user opened on GitHub that received the most comments in the + specified time frame. + """ + popularPullRequestContribution: CreatedPullRequestContribution + + """ + Pull request contributions made by the user. + """ + pullRequestContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Should the user's first pull request ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from the result. + """ + excludePopular: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestContributionConnection! + + """ + Pull request contributions made by the user, grouped by repository. + """ + pullRequestContributionsByRepository( + """ + Should the user's first pull request ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from the result. + """ + excludePopular: Boolean = false + + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [PullRequestContributionsByRepository!]! + + """ + Pull request review contributions made by the user. + """ + pullRequestReviewContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestReviewContributionConnection! + + """ + Pull request review contributions made by the user, grouped by repository. + """ + pullRequestReviewContributionsByRepository( + """ + How many repositories should be included. + """ + maxRepositories: Int = 25 + ): [PullRequestReviewContributionsByRepository!]! + + """ + A list of repositories owned by the user that the user created in this time range. + """ + repositoryContributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Should the user's first repository ever be excluded from the result. + """ + excludeFirst: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedRepositoryContributionConnection! + + """ + A count of contributions made by the user that the viewer cannot access. Only + non-zero when the user has chosen to share their private contribution counts. + """ + restrictedContributionsCount: Int! + + """ + The beginning date and time of this collection. + """ + startedAt: DateTime! + + """ + How many commits were made by the user in this time span. + """ + totalCommitContributions: Int! + + """ + How many issues the user opened. + """ + totalIssueContributions( + """ + Should the user's first issue ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many pull requests the user opened. + """ + totalPullRequestContributions( + """ + Should the user's first pull request ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many pull request reviews the user left. + """ + totalPullRequestReviewContributions: Int! + + """ + How many different repositories the user committed to. + """ + totalRepositoriesWithContributedCommits: Int! + + """ + How many different repositories the user opened issues in. + """ + totalRepositoriesWithContributedIssues( + """ + Should the user's first issue ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented issue be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many different repositories the user left pull request reviews in. + """ + totalRepositoriesWithContributedPullRequestReviews: Int! + + """ + How many different repositories the user opened pull requests in. + """ + totalRepositoriesWithContributedPullRequests( + """ + Should the user's first pull request ever be excluded from this count. + """ + excludeFirst: Boolean = false + + """ + Should the user's most commented pull request be excluded from this count. + """ + excludePopular: Boolean = false + ): Int! + + """ + How many repositories the user created. + """ + totalRepositoryContributions( + """ + Should the user's first repository ever be excluded from this count. + """ + excludeFirst: Boolean = false + ): Int! + + """ + The user who made the contributions in this collection. + """ + user: User! +} + +""" +Autogenerated input type of ConvertProjectCardNoteToIssue +""" +input ConvertProjectCardNoteToIssueInput { + """ + The body of the newly created issue. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectCard ID to convert. + """ + projectCardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + The ID of the repository to create the issue in. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title of the newly created issue. Defaults to the card's note text. + """ + title: String +} + +""" +Autogenerated return type of ConvertProjectCardNoteToIssue +""" +type ConvertProjectCardNoteToIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated ProjectCard. + """ + projectCard: ProjectCard +} + +""" +Represents a 'convert_to_draft' event on a given pull request. +""" +type ConvertToDraftEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this convert to draft event. + """ + resourcePath: URI! + + """ + The HTTP URL for this convert to draft event. + """ + url: URI! +} + +""" +Represents a 'converted_note_to_issue' event on a given issue or pull request. +""" +type ConvertedNoteToIssueEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard @preview(toggledBy: "starfox-preview") + + """ + Column name referenced by this project event. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +Autogenerated input type of CreateBranchProtectionRule +""" +input CreateBranchProtectionRuleInput { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean + + """ + Can admins overwrite branch protection. + """ + isAdminEnforced: Boolean + + """ + The glob-like pattern used to determine matching branches. + """ + pattern: String! + + """ + A list of User, Team or App IDs allowed to push to matching branches. + """ + pushActorIds: [ID!] + + """ + The global relay id of the repository in which a new branch protection rule should be created in. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String!] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean + + """ + A list of User or Team IDs allowed to dismiss reviews on pull requests targeting matching branches. + """ + reviewDismissalActorIds: [ID!] +} + +""" +Autogenerated return type of CreateBranchProtectionRule +""" +type CreateBranchProtectionRulePayload { + """ + The newly created BranchProtectionRule. + """ + branchProtectionRule: BranchProtectionRule + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCheckRun +""" +input CreateCheckRunInput { + """ + Possible further actions the integrator can perform, which a user may trigger. + """ + actions: [CheckRunAction!] + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The time that the check run finished. + """ + completedAt: DateTime + + """ + The final conclusion of the check. + """ + conclusion: CheckConclusionState + + """ + The URL of the integrator's site that has the full details of the check. + """ + detailsUrl: URI + + """ + A reference for the run on the integrator's system. + """ + externalId: String + + """ + The SHA of the head commit. + """ + headSha: GitObjectID! + + """ + The name of the check. + """ + name: String! + + """ + Descriptive details about the run. + """ + output: CheckRunOutput + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The time that the check run began. + """ + startedAt: DateTime + + """ + The current status. + """ + status: RequestableCheckStatusState +} + +""" +Autogenerated return type of CreateCheckRun +""" +type CreateCheckRunPayload { + """ + The newly created check run. + """ + checkRun: CheckRun + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCheckSuite +""" +input CreateCheckSuiteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The SHA of the head commit. + """ + headSha: GitObjectID! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateCheckSuite +""" +type CreateCheckSuitePayload { + """ + The newly created check suite. + """ + checkSuite: CheckSuite + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateContentAttachment +""" +input CreateContentAttachmentInput { + """ + The body of the content attachment, which may contain markdown. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the content_reference. + """ + contentReferenceId: ID! @possibleTypes(concreteTypes: ["ContentReference"]) + + """ + The title of the content attachment. + """ + title: String! +} + +""" +Autogenerated return type of CreateContentAttachment +""" +type CreateContentAttachmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created content attachment. + """ + contentAttachment: ContentAttachment +} + +""" +Autogenerated input type of CreateDeployment +""" +input CreateDeploymentInput @preview(toggledBy: "flash-preview") { + """ + Attempt to automatically merge the default branch into the requested ref, defaults to true. + """ + autoMerge: Boolean = true + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Short description of the deployment. + """ + description: String = "" + + """ + Name for the target deployment environment. + """ + environment: String = "production" + + """ + JSON payload with extra information about the deployment. + """ + payload: String = "{}" + + """ + The node ID of the ref to be deployed. + """ + refId: ID! @possibleTypes(concreteTypes: ["Ref"]) + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The status contexts to verify against commit status checks. To bypass required + contexts, pass an empty array. Defaults to all unique contexts. + """ + requiredContexts: [String!] + + """ + Specifies a task to execute. + """ + task: String = "deploy" +} + +""" +Autogenerated return type of CreateDeployment +""" +type CreateDeploymentPayload @preview(toggledBy: "flash-preview") { + """ + True if the default branch has been auto-merged into the deployment ref. + """ + autoMerged: Boolean + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new deployment. + """ + deployment: Deployment +} + +""" +Autogenerated input type of CreateDeploymentStatus +""" +input CreateDeploymentStatusInput @preview(toggledBy: "flash-preview") { + """ + Adds a new inactive status to all non-transient, non-production environment + deployments with the same repository and environment name as the created + status's deployment. + """ + autoInactive: Boolean = true + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the deployment. + """ + deploymentId: ID! @possibleTypes(concreteTypes: ["Deployment"]) + + """ + A short description of the status. Maximum length of 140 characters. + """ + description: String = "" + + """ + If provided, updates the environment of the deploy. Otherwise, does not modify the environment. + """ + environment: String + + """ + Sets the URL for accessing your environment. + """ + environmentUrl: String = "" + + """ + The log URL to associate with this status. This URL should contain + output to keep the user updated while the task is running or serve as + historical information for what happened in the deployment. + """ + logUrl: String = "" + + """ + The state of the deployment. + """ + state: DeploymentStatusState! +} + +""" +Autogenerated return type of CreateDeploymentStatus +""" +type CreateDeploymentStatusPayload @preview(toggledBy: "flash-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new deployment status. + """ + deploymentStatus: DeploymentStatus +} + +""" +Autogenerated input type of CreateEnterpriseOrganization +""" +input CreateEnterpriseOrganizationInput { + """ + The logins for the administrators of the new organization. + """ + adminLogins: [String!]! + + """ + The email used for sending billing receipts. + """ + billingEmail: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise owning the new organization. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of the new organization. + """ + login: String! + + """ + The profile name of the new organization. + """ + profileName: String! +} + +""" +Autogenerated return type of CreateEnterpriseOrganization +""" +type CreateEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise that owns the created organization. + """ + enterprise: Enterprise + + """ + The organization that was created. + """ + organization: Organization +} + +""" +Autogenerated input type of CreateIpAllowListEntry +""" +input CreateIpAllowListEntryInput { + """ + An IP address or range of addresses in CIDR notation. + """ + allowListValue: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether the IP allow list entry is active when an IP allow list is enabled. + """ + isActive: Boolean! + + """ + An optional name for the IP allow list entry. + """ + name: String + + """ + The ID of the owner for which to create the new IP allow list entry. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Enterprise", "Organization"], abstractType: "IpAllowListOwner") +} + +""" +Autogenerated return type of CreateIpAllowListEntry +""" +type CreateIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was created. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of CreateIssue +""" +input CreateIssueInput { + """ + The Node ID for the user assignee for this issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body for the issue description. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of an issue template in the repository, assigns labels and assignees from the template to the issue + """ + issueTemplate: String + + """ + An array of Node IDs of labels for this issue. + """ + labelIds: [ID!] @possibleTypes(concreteTypes: ["Label"]) + + """ + The Node ID of the milestone for this issue. + """ + milestoneId: ID @possibleTypes(concreteTypes: ["Milestone"]) + + """ + An array of Node IDs for projects associated with this issue. + """ + projectIds: [ID!] @possibleTypes(concreteTypes: ["Project"]) + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title for the issue. + """ + title: String! +} + +""" +Autogenerated return type of CreateIssue +""" +type CreateIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new issue. + """ + issue: Issue +} + +""" +Autogenerated input type of CreateLabel +""" +input CreateLabelInput @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A 6 character hex code, without the leading #, identifying the color of the label. + """ + color: String! + + """ + A brief description of the label, such as its purpose. + """ + description: String + + """ + The name of the label. + """ + name: String! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateLabel +""" +type CreateLabelPayload @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new label. + """ + label: Label +} + +""" +Autogenerated input type of CreateProject +""" +input CreateProjectInput { + """ + The description of project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of project. + """ + name: String! + + """ + The owner ID to create the project under. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Organization", "Repository", "User"], abstractType: "ProjectOwner") + + """ + A list of repository IDs to create as linked repositories for the project + """ + repositoryIds: [ID!] @possibleTypes(concreteTypes: ["Repository"]) + + """ + The name of the GitHub-provided template. + """ + template: ProjectTemplate +} + +""" +Autogenerated return type of CreateProject +""" +type CreateProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new project. + """ + project: Project +} + +""" +Autogenerated input type of CreatePullRequest +""" +input CreatePullRequestInput { + """ + The name of the branch you want your changes pulled into. This should be an existing branch + on the current repository. You cannot update the base branch on a pull request to point + to another repository. + """ + baseRefName: String! + + """ + The contents of the pull request. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Indicates whether this pull request should be a draft. + """ + draft: Boolean = false + + """ + The name of the branch where your changes are implemented. For cross-repository pull requests + in the same network, namespace `head_ref_name` with a user like this: `username:branch`. + """ + headRefName: String! + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean = true + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The title of the pull request. + """ + title: String! +} + +""" +Autogenerated return type of CreatePullRequest +""" +type CreatePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of CreateRef +""" +input CreateRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The fully qualified name of the new Ref (ie: `refs/heads/my_new_branch`). + """ + name: String! + + """ + The GitObjectID that the new Ref shall target. Must point to a commit. + """ + oid: GitObjectID! + + """ + The Node ID of the Repository to create the Ref in. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of CreateRef +""" +type CreateRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created ref. + """ + ref: Ref +} + +""" +Autogenerated input type of CreateRepository +""" +input CreateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A short description of the new repository. + """ + description: String + + """ + Indicates if the repository should have the issues feature enabled. + """ + hasIssuesEnabled: Boolean = true + + """ + Indicates if the repository should have the wiki feature enabled. + """ + hasWikiEnabled: Boolean = false + + """ + The URL for a web page about this repository. + """ + homepageUrl: URI + + """ + The name of the new repository. + """ + name: String! + + """ + The ID of the owner for the new repository. + """ + ownerId: ID @possibleTypes(concreteTypes: ["Organization", "User"], abstractType: "RepositoryOwner") + + """ + When an organization is specified as the owner, this ID identifies the team + that should be granted access to the new repository. + """ + teamId: ID @possibleTypes(concreteTypes: ["Team"]) + + """ + Whether this repository should be marked as a template such that anyone who + can access it can create new repositories with the same files and directory structure. + """ + template: Boolean = false + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! +} + +""" +Autogenerated return type of CreateRepository +""" +type CreateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository. + """ + repository: Repository +} + +""" +Autogenerated input type of CreateTeamDiscussionComment +""" +input CreateTeamDiscussionCommentInput { + """ + The content of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the discussion to which the comment belongs. + """ + discussionId: ID! @possibleTypes(concreteTypes: ["TeamDiscussion"]) +} + +""" +Autogenerated return type of CreateTeamDiscussionComment +""" +type CreateTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new comment. + """ + teamDiscussionComment: TeamDiscussionComment +} + +""" +Autogenerated input type of CreateTeamDiscussion +""" +input CreateTeamDiscussionInput { + """ + The content of the discussion. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + If true, restricts the visibility of this discussion to team members and + organization admins. If false or not specified, allows any organization member + to view this discussion. + """ + private: Boolean + + """ + The ID of the team to which the discussion belongs. + """ + teamId: ID! @possibleTypes(concreteTypes: ["Team"]) + + """ + The title of the discussion. + """ + title: String! +} + +""" +Autogenerated return type of CreateTeamDiscussion +""" +type CreateTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new discussion. + """ + teamDiscussion: TeamDiscussion +} + +""" +Represents the contribution a user made by committing to a repository. +""" +type CreatedCommitContribution implements Contribution { + """ + How many commits were made on this day to this repository by the user. + """ + commitCount: Int! + + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The repository the user made a commit in. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedCommitContribution. +""" +type CreatedCommitContributionConnection { + """ + A list of edges. + """ + edges: [CreatedCommitContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedCommitContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of commits across days and repositories in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedCommitContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedCommitContribution +} + +""" +Represents the contribution a user made on GitHub by opening an issue. +""" +type CreatedIssueContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + The issue that was opened. + """ + issue: Issue! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedIssueContribution. +""" +type CreatedIssueContributionConnection { + """ + A list of edges. + """ + edges: [CreatedIssueContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedIssueContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedIssueContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedIssueContribution +} + +""" +Represents either a issue the viewer can access or a restricted contribution. +""" +union CreatedIssueOrRestrictedContribution = CreatedIssueContribution | RestrictedContribution + +""" +Represents the contribution a user made on GitHub by opening a pull request. +""" +type CreatedPullRequestContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The pull request that was opened. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedPullRequestContribution. +""" +type CreatedPullRequestContributionConnection { + """ + A list of edges. + """ + edges: [CreatedPullRequestContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedPullRequestContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedPullRequestContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedPullRequestContribution +} + +""" +Represents either a pull request the viewer can access or a restricted contribution. +""" +union CreatedPullRequestOrRestrictedContribution = CreatedPullRequestContribution | RestrictedContribution + +""" +Represents the contribution a user made by leaving a review on a pull request. +""" +type CreatedPullRequestReviewContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The pull request the user reviewed. + """ + pullRequest: PullRequest! + + """ + The review the user left on the pull request. + """ + pullRequestReview: PullRequestReview! + + """ + The repository containing the pull request that the user reviewed. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedPullRequestReviewContribution. +""" +type CreatedPullRequestReviewContributionConnection { + """ + A list of edges. + """ + edges: [CreatedPullRequestReviewContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedPullRequestReviewContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedPullRequestReviewContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedPullRequestReviewContribution +} + +""" +Represents the contribution a user made on GitHub by creating a repository. +""" +type CreatedRepositoryContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The repository that was created. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedRepositoryContribution. +""" +type CreatedRepositoryContributionConnection { + """ + A list of edges. + """ + edges: [CreatedRepositoryContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedRepositoryContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedRepositoryContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedRepositoryContribution +} + +""" +Represents either a repository the viewer can access or a restricted contribution. +""" +union CreatedRepositoryOrRestrictedContribution = CreatedRepositoryContribution | RestrictedContribution + +""" +Represents a mention made by one issue or pull request to another. +""" +type CrossReferencedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Identifies when the reference was made. + """ + referencedAt: DateTime! + + """ + The HTTP path for this pull request. + """ + resourcePath: URI! + + """ + Issue or pull request that made the reference. + """ + source: ReferencedSubject! + + """ + Issue or pull request to which the reference was made. + """ + target: ReferencedSubject! + + """ + The HTTP URL for this pull request. + """ + url: URI! + + """ + Checks if the target will be closed when the source is merged. + """ + willCloseTarget: Boolean! +} + +""" +An ISO-8601 encoded date string. +""" +scalar Date + +""" +An ISO-8601 encoded UTC date string. +""" +scalar DateTime + +""" +Autogenerated input type of DeclineTopicSuggestion +""" +input DeclineTopicSuggestionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the suggested topic. + """ + name: String! + + """ + The reason why the suggested topic is declined. + """ + reason: TopicSuggestionDeclineReason! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of DeclineTopicSuggestion +""" +type DeclineTopicSuggestionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The declined topic. + """ + topic: Topic +} + +""" +The possible default permissions for repositories. +""" +enum DefaultRepositoryPermissionField { + """ + Can read, write, and administrate repos by default + """ + ADMIN + + """ + No access + """ + NONE + + """ + Can read repos by default + """ + READ + + """ + Can read and write repos by default + """ + WRITE +} + +""" +Entities that can be deleted. +""" +interface Deletable { + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! +} + +""" +Autogenerated input type of DeleteBranchProtectionRule +""" +input DeleteBranchProtectionRuleInput { + """ + The global relay id of the branch protection rule to be deleted. + """ + branchProtectionRuleId: ID! @possibleTypes(concreteTypes: ["BranchProtectionRule"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of DeleteBranchProtectionRule +""" +type DeleteBranchProtectionRulePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteDeployment +""" +input DeleteDeploymentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the deployment to be deleted. + """ + id: ID! @possibleTypes(concreteTypes: ["Deployment"]) +} + +""" +Autogenerated return type of DeleteDeployment +""" +type DeleteDeploymentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteIpAllowListEntry +""" +input DeleteIpAllowListEntryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IP allow list entry to delete. + """ + ipAllowListEntryId: ID! @possibleTypes(concreteTypes: ["IpAllowListEntry"]) +} + +""" +Autogenerated return type of DeleteIpAllowListEntry +""" +type DeleteIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was deleted. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of DeleteIssueComment +""" +input DeleteIssueCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["IssueComment"]) +} + +""" +Autogenerated return type of DeleteIssueComment +""" +type DeleteIssueCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteIssue +""" +input DeleteIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to delete. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of DeleteIssue +""" +type DeleteIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the issue belonged to + """ + repository: Repository +} + +""" +Autogenerated input type of DeleteLabel +""" +input DeleteLabelInput @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the label to be deleted. + """ + id: ID! @possibleTypes(concreteTypes: ["Label"]) +} + +""" +Autogenerated return type of DeleteLabel +""" +type DeleteLabelPayload @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeletePackageVersion +""" +input DeletePackageVersionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the package version to be deleted. + """ + packageVersionId: ID! @possibleTypes(concreteTypes: ["PackageVersion"]) +} + +""" +Autogenerated return type of DeletePackageVersion +""" +type DeletePackageVersionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not the operation succeeded. + """ + success: Boolean +} + +""" +Autogenerated input type of DeleteProjectCard +""" +input DeleteProjectCardInput { + """ + The id of the card to delete. + """ + cardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of DeleteProjectCard +""" +type DeleteProjectCardPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The column the deleted card was in. + """ + column: ProjectColumn + + """ + The deleted card ID. + """ + deletedCardId: ID +} + +""" +Autogenerated input type of DeleteProjectColumn +""" +input DeleteProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to delete. + """ + columnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of DeleteProjectColumn +""" +type DeleteProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted column ID. + """ + deletedColumnId: ID + + """ + The project the deleted column was in. + """ + project: Project +} + +""" +Autogenerated input type of DeleteProject +""" +input DeleteProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Project ID to update. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) +} + +""" +Autogenerated return type of DeleteProject +""" +type DeleteProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository or organization the project was removed from. + """ + owner: ProjectOwner +} + +""" +Autogenerated input type of DeletePullRequestReviewComment +""" +input DeletePullRequestReviewCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["PullRequestReviewComment"]) +} + +""" +Autogenerated return type of DeletePullRequestReviewComment +""" +type DeletePullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request review the deleted comment belonged to. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of DeletePullRequestReview +""" +input DeletePullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to delete. + """ + pullRequestReviewId: ID! @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of DeletePullRequestReview +""" +type DeletePullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of DeleteRef +""" +input DeleteRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the Ref to be deleted. + """ + refId: ID! @possibleTypes(concreteTypes: ["Ref"]) +} + +""" +Autogenerated return type of DeleteRef +""" +type DeleteRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteTeamDiscussionComment +""" +input DeleteTeamDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussionComment"]) +} + +""" +Autogenerated return type of DeleteTeamDiscussionComment +""" +type DeleteTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteTeamDiscussion +""" +input DeleteTeamDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion ID to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussion"]) +} + +""" +Autogenerated return type of DeleteTeamDiscussion +""" +type DeleteTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteVerifiableDomain +""" +input DeleteVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to delete. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of DeleteVerifiableDomain +""" +type DeleteVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owning account from which the domain was deleted. + """ + owner: VerifiableDomainOwner +} + +""" +Represents a 'demilestoned' event on a given issue or pull request. +""" +type DemilestonedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the milestone title associated with the 'demilestoned' event. + """ + milestoneTitle: String! + + """ + Object referenced by event. + """ + subject: MilestoneItem! +} + +""" +A dependency manifest entry +""" +type DependencyGraphDependency @preview(toggledBy: "hawkgirl-preview") { + """ + Does the dependency itself have dependencies? + """ + hasDependencies: Boolean! + + """ + The dependency package manager + """ + packageManager: String + + """ + The required package name + """ + packageName: String! + + """ + The repository containing the package + """ + repository: Repository + + """ + The dependency version requirements + """ + requirements: String! +} + +""" +The connection type for DependencyGraphDependency. +""" +type DependencyGraphDependencyConnection @preview(toggledBy: "hawkgirl-preview") { + """ + A list of edges. + """ + edges: [DependencyGraphDependencyEdge] + + """ + A list of nodes. + """ + nodes: [DependencyGraphDependency] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DependencyGraphDependencyEdge @preview(toggledBy: "hawkgirl-preview") { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DependencyGraphDependency +} + +""" +Dependency manifest for a repository +""" +type DependencyGraphManifest implements Node @preview(toggledBy: "hawkgirl-preview") { + """ + Path to view the manifest file blob + """ + blobPath: String! + + """ + A list of manifest dependencies + """ + dependencies( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DependencyGraphDependencyConnection + + """ + The number of dependencies listed in the manifest + """ + dependenciesCount: Int + + """ + Is the manifest too big to parse? + """ + exceedsMaxSize: Boolean! + + """ + Fully qualified manifest filename + """ + filename: String! + id: ID! + + """ + Were we able to parse the manifest? + """ + parseable: Boolean! + + """ + The repository containing the manifest + """ + repository: Repository! +} + +""" +The connection type for DependencyGraphManifest. +""" +type DependencyGraphManifestConnection @preview(toggledBy: "hawkgirl-preview") { + """ + A list of edges. + """ + edges: [DependencyGraphManifestEdge] + + """ + A list of nodes. + """ + nodes: [DependencyGraphManifest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DependencyGraphManifestEdge @preview(toggledBy: "hawkgirl-preview") { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DependencyGraphManifest +} + +""" +A repository deploy key. +""" +type DeployKey implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The deploy key. + """ + key: String! + + """ + Whether or not the deploy key is read only. + """ + readOnly: Boolean! + + """ + The deploy key title. + """ + title: String! + + """ + Whether or not the deploy key has been verified. + """ + verified: Boolean! +} + +""" +The connection type for DeployKey. +""" +type DeployKeyConnection { + """ + A list of edges. + """ + edges: [DeployKeyEdge] + + """ + A list of nodes. + """ + nodes: [DeployKey] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeployKeyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeployKey +} + +""" +Represents a 'deployed' event on a given pull request. +""" +type DeployedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The deployment associated with the 'deployed' event. + """ + deployment: Deployment! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The ref associated with the 'deployed' event. + """ + ref: Ref +} + +""" +Represents triggered deployment instance. +""" +type Deployment implements Node { + """ + Identifies the commit sha of the deployment. + """ + commit: Commit + + """ + Identifies the oid of the deployment commit, even if the commit has been deleted. + """ + commitOid: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who triggered the deployment. + """ + creator: Actor! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The deployment description. + """ + description: String + + """ + The latest environment to which this deployment was made. + """ + environment: String + id: ID! + + """ + The latest environment to which this deployment was made. + """ + latestEnvironment: String + + """ + The latest status of this deployment. + """ + latestStatus: DeploymentStatus + + """ + The original environment to which this deployment was made. + """ + originalEnvironment: String + + """ + Extra information that a deployment system might need. + """ + payload: String + + """ + Identifies the Ref of the deployment, if the deployment was created by ref. + """ + ref: Ref + + """ + Identifies the repository associated with the deployment. + """ + repository: Repository! + + """ + The current state of the deployment. + """ + state: DeploymentState + + """ + A list of statuses associated with the deployment. + """ + statuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeploymentStatusConnection + + """ + The deployment task. + """ + task: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for Deployment. +""" +type DeploymentConnection { + """ + A list of edges. + """ + edges: [DeploymentEdge] + + """ + A list of nodes. + """ + nodes: [Deployment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Deployment +} + +""" +Represents a 'deployment_environment_changed' event on a given pull request. +""" +type DeploymentEnvironmentChangedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The deployment status that updated the deployment environment. + """ + deploymentStatus: DeploymentStatus! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Ordering options for deployment connections +""" +input DeploymentOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order deployments by. + """ + field: DeploymentOrderField! +} + +""" +Properties by which deployment connections can be ordered. +""" +enum DeploymentOrderField { + """ + Order collection by creation time + """ + CREATED_AT +} + +""" +The possible states in which a deployment can be. +""" +enum DeploymentState { + """ + The pending deployment was not updated after 30 minutes. + """ + ABANDONED + + """ + The deployment is currently active. + """ + ACTIVE + + """ + An inactive transient deployment. + """ + DESTROYED + + """ + The deployment experienced an error. + """ + ERROR + + """ + The deployment has failed. + """ + FAILURE + + """ + The deployment is inactive. + """ + INACTIVE + + """ + The deployment is in progress. + """ + IN_PROGRESS + + """ + The deployment is pending. + """ + PENDING + + """ + The deployment has queued + """ + QUEUED + + """ + The deployment is waiting. + """ + WAITING +} + +""" +Describes the status of a given deployment attempt. +""" +type DeploymentStatus implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who triggered the deployment. + """ + creator: Actor! + + """ + Identifies the deployment associated with status. + """ + deployment: Deployment! + + """ + Identifies the description of the deployment. + """ + description: String + + """ + Identifies the environment of the deployment at the time of this deployment status + """ + environment: String @preview(toggledBy: "flash-preview") + + """ + Identifies the environment URL of the deployment. + """ + environmentUrl: URI + id: ID! + + """ + Identifies the log URL of the deployment. + """ + logUrl: URI + + """ + Identifies the current state of the deployment. + """ + state: DeploymentStatusState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for DeploymentStatus. +""" +type DeploymentStatusConnection { + """ + A list of edges. + """ + edges: [DeploymentStatusEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentStatus] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentStatusEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentStatus +} + +""" +The possible states for a deployment status. +""" +enum DeploymentStatusState { + """ + The deployment experienced an error. + """ + ERROR + + """ + The deployment has failed. + """ + FAILURE + + """ + The deployment is inactive. + """ + INACTIVE + + """ + The deployment is in progress. + """ + IN_PROGRESS + + """ + The deployment is pending. + """ + PENDING + + """ + The deployment is queued + """ + QUEUED + + """ + The deployment was successful. + """ + SUCCESS + + """ + The deployment is waiting. + """ + WAITING +} + +""" +The possible sides of a diff. +""" +enum DiffSide { + """ + The left side of the diff. + """ + LEFT + + """ + The right side of the diff. + """ + RIGHT +} + +""" +Represents a 'disconnected' event on a given issue or pull request. +""" +type DisconnectedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Issue or pull request from which the issue was disconnected. + """ + source: ReferencedSubject! + + """ + Issue or pull request which was disconnected. + """ + subject: ReferencedSubject! +} + +""" +Autogenerated input type of DismissPullRequestReview +""" +input DismissPullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The contents of the pull request review dismissal message. + """ + message: String! + + """ + The Node ID of the pull request review to modify. + """ + pullRequestReviewId: ID! @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of DismissPullRequestReview +""" +type DismissPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The dismissed pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Specifies a review comment to be left with a Pull Request Review. +""" +input DraftPullRequestReviewComment { + """ + Body of the comment to leave. + """ + body: String! + + """ + Path to the file being commented on. + """ + path: String! + + """ + Position in the file to leave a comment on. + """ + position: Int! +} + +""" +Specifies a review comment thread to be left with a Pull Request Review. +""" +input DraftPullRequestReviewThread { + """ + Body of the comment to leave. + """ + body: String! + + """ + The line of the blob to which the thread refers. The end of the line range for multi-line comments. + """ + line: Int! + + """ + Path to the file being commented on. + """ + path: String! + + """ + The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. + """ + side: DiffSide = RIGHT + + """ + The first line of the range to which the comment refers. + """ + startLine: Int + + """ + The side of the diff on which the start line resides. + """ + startSide: DiffSide = RIGHT +} + +""" +An account to manage multiple organizations with consolidated policy and billing. +""" +type Enterprise implements Node { + """ + A URL pointing to the enterprise's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Enterprise billing information visible to enterprise billing managers. + """ + billingInfo: EnterpriseBillingInfo + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the enterprise. + """ + description: String + + """ + The description of the enterprise as HTML. + """ + descriptionHTML: HTML! + id: ID! + + """ + The location of the enterprise. + """ + location: String + + """ + A list of users who are members of this enterprise. + """ + members( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Only return members within the selected GitHub Enterprise deployment + """ + deployment: EnterpriseUserDeployment + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for members returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + Only return members within the organizations with these logins + """ + organizationLogins: [String!] + + """ + The search string to look for. + """ + query: String + + """ + The role of the user in the enterprise organization or server. + """ + role: EnterpriseUserAccountMembershipRole + ): EnterpriseMemberConnection! + + """ + The name of the enterprise. + """ + name: String! + + """ + A list of organizations that belong to this enterprise. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations returned from the connection. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + ): OrganizationConnection! + + """ + Enterprise information only visible to enterprise owners. + """ + ownerInfo: EnterpriseOwnerInfo + + """ + The HTTP path for this enterprise. + """ + resourcePath: URI! + + """ + The URL-friendly identifier for the enterprise. + """ + slug: String! + + """ + The HTTP URL for this enterprise. + """ + url: URI! + + """ + A list of user accounts on this enterprise. + """ + userAccounts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): EnterpriseUserAccountConnection! + + """ + Is the current viewer an admin of this enterprise? + """ + viewerIsAdmin: Boolean! + + """ + The URL of the enterprise website. + """ + websiteUrl: URI +} + +""" +The connection type for User. +""" +type EnterpriseAdministratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is an administrator of an enterprise. +""" +type EnterpriseAdministratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The role of the administrator. + """ + role: EnterpriseAdministratorRole! +} + +""" +An invitation for a user to become an owner or billing manager of an enterprise. +""" +type EnterpriseAdministratorInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email of the person who was invited to the enterprise. + """ + email: String + + """ + The enterprise the invitation is for. + """ + enterprise: Enterprise! + id: ID! + + """ + The user who was invited to the enterprise. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User + + """ + The invitee's pending role in the enterprise (owner or billing_manager). + """ + role: EnterpriseAdministratorRole! +} + +""" +The connection type for EnterpriseAdministratorInvitation. +""" +type EnterpriseAdministratorInvitationConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorInvitationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseAdministratorInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseAdministratorInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseAdministratorInvitation +} + +""" +Ordering options for enterprise administrator invitation connections +""" +input EnterpriseAdministratorInvitationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order enterprise administrator invitations by. + """ + field: EnterpriseAdministratorInvitationOrderField! +} + +""" +Properties by which enterprise administrator invitation connections can be ordered. +""" +enum EnterpriseAdministratorInvitationOrderField { + """ + Order enterprise administrator member invitations by creation time + """ + CREATED_AT +} + +""" +The possible administrator roles in an enterprise account. +""" +enum EnterpriseAdministratorRole { + """ + Represents a billing manager of the enterprise account. + """ + BILLING_MANAGER + + """ + Represents an owner of the enterprise account. + """ + OWNER +} + +""" +Metadata for an audit entry containing enterprise account information. +""" +interface EnterpriseAuditEntryData { + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI +} + +""" +Enterprise billing information visible to enterprise billing managers and owners. +""" +type EnterpriseBillingInfo { + """ + The number of licenseable users/emails across the enterprise. + """ + allLicensableUsersCount: Int! + + """ + The number of data packs used by all organizations owned by the enterprise. + """ + assetPacks: Int! + + """ + The number of available seats across all owned organizations based on the unique number of billable users. + """ + availableSeats: Int! @deprecated(reason: "`availableSeats` will be replaced with `totalAvailableLicenses` to provide more clarity on the value being returned Use EnterpriseBillingInfo.totalAvailableLicenses instead. Removal on 2020-01-01 UTC.") + + """ + The bandwidth quota in GB for all organizations owned by the enterprise. + """ + bandwidthQuota: Float! + + """ + The bandwidth usage in GB for all organizations owned by the enterprise. + """ + bandwidthUsage: Float! + + """ + The bandwidth usage as a percentage of the bandwidth quota. + """ + bandwidthUsagePercentage: Int! + + """ + The total seats across all organizations owned by the enterprise. + """ + seats: Int! @deprecated(reason: "`seats` will be replaced with `totalLicenses` to provide more clarity on the value being returned Use EnterpriseBillingInfo.totalLicenses instead. Removal on 2020-01-01 UTC.") + + """ + The storage quota in GB for all organizations owned by the enterprise. + """ + storageQuota: Float! + + """ + The storage usage in GB for all organizations owned by the enterprise. + """ + storageUsage: Float! + + """ + The storage usage as a percentage of the storage quota. + """ + storageUsagePercentage: Int! + + """ + The number of available licenses across all owned organizations based on the unique number of billable users. + """ + totalAvailableLicenses: Int! + + """ + The total number of licenses allocated. + """ + totalLicenses: Int! +} + +""" +The possible values for the enterprise default repository permission setting. +""" +enum EnterpriseDefaultRepositoryPermissionSettingValue { + """ + Organization members will be able to clone, pull, push, and add new collaborators to all organization repositories. + """ + ADMIN + + """ + Organization members will only be able to clone and pull public repositories. + """ + NONE + + """ + Organizations in the enterprise choose default repository permissions for their members. + """ + NO_POLICY + + """ + Organization members will be able to clone and pull all organization repositories. + """ + READ + + """ + Organization members will be able to clone, pull, and push all organization repositories. + """ + WRITE +} + +""" +The possible values for an enabled/disabled enterprise setting. +""" +enum EnterpriseEnabledDisabledSettingValue { + """ + The setting is disabled for organizations in the enterprise. + """ + DISABLED + + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + There is no policy set for organizations in the enterprise. + """ + NO_POLICY +} + +""" +The possible values for an enabled/no policy enterprise setting. +""" +enum EnterpriseEnabledSettingValue { + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + There is no policy set for organizations in the enterprise. + """ + NO_POLICY +} + +""" +An identity provider configured to provision identities for an enterprise. +""" +type EnterpriseIdentityProvider implements Node { + """ + The digest algorithm used to sign SAML requests for the identity provider. + """ + digestMethod: SamlDigestAlgorithm + + """ + The enterprise this identity provider belongs to. + """ + enterprise: Enterprise + + """ + ExternalIdentities provisioned by this identity provider. + """ + externalIdentities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ExternalIdentityConnection! + id: ID! + + """ + The x509 certificate used by the identity provider to sign assertions and responses. + """ + idpCertificate: X509Certificate + + """ + The Issuer Entity ID for the SAML identity provider. + """ + issuer: String + + """ + Recovery codes that can be used by admins to access the enterprise if the identity provider is unavailable. + """ + recoveryCodes: [String!] + + """ + The signature algorithm used to sign SAML requests for the identity provider. + """ + signatureMethod: SamlSignatureAlgorithm + + """ + The URL endpoint for the identity provider's SAML SSO. + """ + ssoUrl: URI +} + +""" +An object that is a member of an enterprise. +""" +union EnterpriseMember = EnterpriseUserAccount | User + +""" +The connection type for EnterpriseMember. +""" +type EnterpriseMemberConnection { + """ + A list of edges. + """ + edges: [EnterpriseMemberEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseMember] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is a member of an enterprise through one or more organizations. +""" +type EnterpriseMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the user does not have a license for the enterprise. + """ + isUnlicensed: Boolean! @deprecated(reason: "All members consume a license Removal on 2021-01-01 UTC.") + + """ + The item at the end of the edge. + """ + node: EnterpriseMember +} + +""" +Ordering options for enterprise member connections. +""" +input EnterpriseMemberOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order enterprise members by. + """ + field: EnterpriseMemberOrderField! +} + +""" +Properties by which enterprise member connections can be ordered. +""" +enum EnterpriseMemberOrderField { + """ + Order enterprise members by creation time + """ + CREATED_AT + + """ + Order enterprise members by login + """ + LOGIN +} + +""" +The possible values for the enterprise members can create repositories setting. +""" +enum EnterpriseMembersCanCreateRepositoriesSettingValue { + """ + Members will be able to create public and private repositories. + """ + ALL + + """ + Members will not be able to create public or private repositories. + """ + DISABLED + + """ + Organization administrators choose whether to allow members to create repositories. + """ + NO_POLICY + + """ + Members will be able to create only private repositories. + """ + PRIVATE + + """ + Members will be able to create only public repositories. + """ + PUBLIC +} + +""" +The possible values for the members can make purchases setting. +""" +enum EnterpriseMembersCanMakePurchasesSettingValue { + """ + The setting is disabled for organizations in the enterprise. + """ + DISABLED + + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED +} + +""" +The connection type for Organization. +""" +type EnterpriseOrganizationMembershipConnection { + """ + A list of edges. + """ + edges: [EnterpriseOrganizationMembershipEdge] + + """ + A list of nodes. + """ + nodes: [Organization] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An enterprise organization that a user is a member of. +""" +type EnterpriseOrganizationMembershipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Organization + + """ + The role of the user in the enterprise membership. + """ + role: EnterpriseUserAccountMembershipRole! +} + +""" +The connection type for User. +""" +type EnterpriseOutsideCollaboratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseOutsideCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is an outside collaborator of an enterprise through one or more organizations. +""" +type EnterpriseOutsideCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the outside collaborator does not have a license for the enterprise. + """ + isUnlicensed: Boolean! @deprecated(reason: "All outside collaborators consume a license Removal on 2021-01-01 UTC.") + + """ + The item at the end of the edge. + """ + node: User + + """ + The enterprise organization repositories this user is a member of. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories. + """ + orderBy: RepositoryOrder = {field: NAME, direction: ASC} + ): EnterpriseRepositoryInfoConnection! +} + +""" +Enterprise information only visible to enterprise owners. +""" +type EnterpriseOwnerInfo { + """ + A list of all of the administrators for this enterprise. + """ + admins( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for administrators returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + + """ + The role to filter by. + """ + role: EnterpriseAdministratorRole + ): EnterpriseAdministratorConnection! + + """ + A list of users in the enterprise who currently have two-factor authentication disabled. + """ + affiliatedUsersWithTwoFactorDisabled( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + Whether or not affiliated users with two-factor authentication disabled exist in the enterprise. + """ + affiliatedUsersWithTwoFactorDisabledExist: Boolean! + + """ + The setting value for whether private repository forking is enabled for repositories in organizations in this enterprise. + """ + allowPrivateRepositoryForkingSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided private repository forking setting value. + """ + allowPrivateRepositoryForkingSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for base repository permissions for organizations in this enterprise. + """ + defaultRepositoryPermissionSetting: EnterpriseDefaultRepositoryPermissionSettingValue! + + """ + A list of enterprise organizations configured with the provided default repository permission. + """ + defaultRepositoryPermissionSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The permission to find organizations for. + """ + value: DefaultRepositoryPermissionField! + ): OrganizationConnection! + + """ + A list of domains owned by the enterprise. + """ + domains( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter whether or not the domain is verified. + """ + isVerified: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for verifiable domains returned. + """ + orderBy: VerifiableDomainOrder = {field: DOMAIN, direction: ASC} + ): VerifiableDomainConnection! + + """ + Enterprise Server installations owned by the enterprise. + """ + enterpriseServerInstallations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Whether or not to only return installations discovered via GitHub Connect. + """ + connectedOnly: Boolean = false + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server installations returned. + """ + orderBy: EnterpriseServerInstallationOrder = {field: HOST_NAME, direction: ASC} + ): EnterpriseServerInstallationConnection! + + """ + The setting value for whether the enterprise has an IP allow list enabled. + """ + ipAllowListEnabledSetting: IpAllowListEnabledSettingValue! + + """ + The IP addresses that are allowed to access resources owned by the enterprise. + """ + ipAllowListEntries( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for IP allow list entries returned. + """ + orderBy: IpAllowListEntryOrder = {field: ALLOW_LIST_VALUE, direction: ASC} + ): IpAllowListEntryConnection! + + """ + Whether or not the default repository permission is currently being updated. + """ + isUpdatingDefaultRepositoryPermission: Boolean! + + """ + Whether the two-factor authentication requirement is currently being enforced. + """ + isUpdatingTwoFactorRequirement: Boolean! + + """ + The setting value for whether organization members with admin permissions on a + repository can change repository visibility. + """ + membersCanChangeRepositoryVisibilitySetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided can change repository visibility setting value. + """ + membersCanChangeRepositoryVisibilitySettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members of organizations in the enterprise can create internal repositories. + """ + membersCanCreateInternalRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create private repositories. + """ + membersCanCreatePrivateRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create public repositories. + """ + membersCanCreatePublicRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create repositories. + """ + membersCanCreateRepositoriesSetting: EnterpriseMembersCanCreateRepositoriesSettingValue + + """ + A list of enterprise organizations configured with the provided repository creation setting value. + """ + membersCanCreateRepositoriesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting to find organizations for. + """ + value: OrganizationMembersCanCreateRepositoriesSettingValue! + ): OrganizationConnection! + + """ + The setting value for whether members with admin permissions for repositories can delete issues. + """ + membersCanDeleteIssuesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can delete issues setting value. + """ + membersCanDeleteIssuesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members with admin permissions for repositories can delete or transfer repositories. + """ + membersCanDeleteRepositoriesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can delete repositories setting value. + """ + membersCanDeleteRepositoriesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members of organizations in the enterprise can invite outside collaborators. + """ + membersCanInviteCollaboratorsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can invite collaborators setting value. + """ + membersCanInviteCollaboratorsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + Indicates whether members of this enterprise's organizations can purchase additional services for those organizations. + """ + membersCanMakePurchasesSetting: EnterpriseMembersCanMakePurchasesSettingValue! + + """ + The setting value for whether members with admin permissions for repositories can update protected branches. + """ + membersCanUpdateProtectedBranchesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can update protected branches setting value. + """ + membersCanUpdateProtectedBranchesSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether members can view dependency insights. + """ + membersCanViewDependencyInsightsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can view dependency insights setting value. + """ + membersCanViewDependencyInsightsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether organization projects are enabled for organizations in this enterprise. + """ + organizationProjectsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided organization projects setting value. + """ + organizationProjectsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + A list of outside collaborators across the repositories in the enterprise. + """ + outsideCollaborators( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The login of one specific outside collaborator. + """ + login: String + + """ + Ordering options for outside collaborators returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + + """ + Only return outside collaborators on repositories with this visibility. + """ + visibility: RepositoryVisibility + ): EnterpriseOutsideCollaboratorConnection! + + """ + A list of pending administrator invitations for the enterprise. + """ + pendingAdminInvitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pending enterprise administrator invitations returned from the connection. + """ + orderBy: EnterpriseAdministratorInvitationOrder = {field: CREATED_AT, direction: DESC} + + """ + The search string to look for. + """ + query: String + + """ + The role to filter by. + """ + role: EnterpriseAdministratorRole + ): EnterpriseAdministratorInvitationConnection! + + """ + A list of pending collaborator invitations across the repositories in the enterprise. + """ + pendingCollaboratorInvitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pending repository collaborator invitations returned from the connection. + """ + orderBy: RepositoryInvitationOrder = {field: CREATED_AT, direction: DESC} + + """ + The search string to look for. + """ + query: String + ): RepositoryInvitationConnection! + + """ + A list of pending collaborators across the repositories in the enterprise. + """ + pendingCollaborators( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pending repository collaborator invitations returned from the connection. + """ + orderBy: RepositoryInvitationOrder = {field: CREATED_AT, direction: DESC} + + """ + The search string to look for. + """ + query: String + ): EnterprisePendingCollaboratorConnection! @deprecated(reason: "Repository invitations can now be associated with an email, not only an invitee. Use the `pendingCollaboratorInvitations` field instead. Removal on 2020-10-01 UTC.") + + """ + A list of pending member invitations for organizations in the enterprise. + """ + pendingMemberInvitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The search string to look for. + """ + query: String + ): EnterprisePendingMemberInvitationConnection! + + """ + The setting value for whether repository projects are enabled in this enterprise. + """ + repositoryProjectsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided repository projects setting value. + """ + repositoryProjectsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The SAML Identity Provider for the enterprise. + """ + samlIdentityProvider: EnterpriseIdentityProvider + + """ + A list of enterprise organizations configured with the SAML single sign-on setting value. + """ + samlIdentityProviderSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: IdentityProviderConfigurationState! + ): OrganizationConnection! + + """ + A list of members with a support entitlement. + """ + supportEntitlements( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for support entitlement users returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + ): EnterpriseMemberConnection! + + """ + The setting value for whether team discussions are enabled for organizations in this enterprise. + """ + teamDiscussionsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided team discussions setting value. + """ + teamDiscussionsSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! + + """ + The setting value for whether the enterprise requires two-factor authentication for its organizations and users. + """ + twoFactorRequiredSetting: EnterpriseEnabledSettingValue! + + """ + A list of enterprise organizations configured with the two-factor authentication setting value. + """ + twoFactorRequiredSettingOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations with this setting. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The setting value to find organizations for. + """ + value: Boolean! + ): OrganizationConnection! +} + +""" +The connection type for User. +""" +type EnterprisePendingCollaboratorConnection { + """ + A list of edges. + """ + edges: [EnterprisePendingCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A user with an invitation to be a collaborator on a repository owned by an organization in an enterprise. +""" +type EnterprisePendingCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the invited collaborator does not have a license for the enterprise. + """ + isUnlicensed: Boolean! @deprecated(reason: "All pending collaborators consume a license Removal on 2021-01-01 UTC.") + + """ + The item at the end of the edge. + """ + node: User + + """ + The enterprise organization repositories this user is a member of. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories. + """ + orderBy: RepositoryOrder = {field: NAME, direction: ASC} + ): EnterpriseRepositoryInfoConnection! +} + +""" +The connection type for OrganizationInvitation. +""" +type EnterprisePendingMemberInvitationConnection { + """ + A list of edges. + """ + edges: [EnterprisePendingMemberInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the total count of unique users in the connection. + """ + totalUniqueUserCount: Int! +} + +""" +An invitation to be a member in an enterprise organization. +""" +type EnterprisePendingMemberInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the invitation has a license for the enterprise. + """ + isUnlicensed: Boolean! @deprecated(reason: "All pending members consume a license Removal on 2020-07-01 UTC.") + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +A subset of repository information queryable from an enterprise. +""" +type EnterpriseRepositoryInfo implements Node { + id: ID! + + """ + Identifies if the repository is private. + """ + isPrivate: Boolean! + + """ + The repository's name. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! +} + +""" +The connection type for EnterpriseRepositoryInfo. +""" +type EnterpriseRepositoryInfoConnection { + """ + A list of edges. + """ + edges: [EnterpriseRepositoryInfoEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseRepositoryInfo] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseRepositoryInfoEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseRepositoryInfo +} + +""" +An Enterprise Server installation. +""" +type EnterpriseServerInstallation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The customer name to which the Enterprise Server installation belongs. + """ + customerName: String! + + """ + The host name of the Enterprise Server installation. + """ + hostName: String! + id: ID! + + """ + Whether or not the installation is connected to an Enterprise Server installation via GitHub Connect. + """ + isConnected: Boolean! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + User accounts on this Enterprise Server installation. + """ + userAccounts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server user accounts returned from the connection. + """ + orderBy: EnterpriseServerUserAccountOrder = {field: LOGIN, direction: ASC} + ): EnterpriseServerUserAccountConnection! + + """ + User accounts uploads for the Enterprise Server installation. + """ + userAccountsUploads( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server user accounts uploads returned from the connection. + """ + orderBy: EnterpriseServerUserAccountsUploadOrder = {field: CREATED_AT, direction: DESC} + ): EnterpriseServerUserAccountsUploadConnection! +} + +""" +The connection type for EnterpriseServerInstallation. +""" +type EnterpriseServerInstallationConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerInstallationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerInstallation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerInstallationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerInstallation +} + +""" +Ordering options for Enterprise Server installation connections. +""" +input EnterpriseServerInstallationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order Enterprise Server installations by. + """ + field: EnterpriseServerInstallationOrderField! +} + +""" +Properties by which Enterprise Server installation connections can be ordered. +""" +enum EnterpriseServerInstallationOrderField { + """ + Order Enterprise Server installations by creation time + """ + CREATED_AT + + """ + Order Enterprise Server installations by customer name + """ + CUSTOMER_NAME + + """ + Order Enterprise Server installations by host name + """ + HOST_NAME +} + +""" +A user account on an Enterprise Server installation. +""" +type EnterpriseServerUserAccount implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + User emails belonging to this user account. + """ + emails( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Enterprise Server user account emails returned from the connection. + """ + orderBy: EnterpriseServerUserAccountEmailOrder = {field: EMAIL, direction: ASC} + ): EnterpriseServerUserAccountEmailConnection! + + """ + The Enterprise Server installation on which this user account exists. + """ + enterpriseServerInstallation: EnterpriseServerInstallation! + id: ID! + + """ + Whether the user account is a site administrator on the Enterprise Server installation. + """ + isSiteAdmin: Boolean! + + """ + The login of the user account on the Enterprise Server installation. + """ + login: String! + + """ + The profile name of the user account on the Enterprise Server installation. + """ + profileName: String + + """ + The date and time when the user account was created on the Enterprise Server installation. + """ + remoteCreatedAt: DateTime! + + """ + The ID of the user account on the Enterprise Server installation. + """ + remoteUserId: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for EnterpriseServerUserAccount. +""" +type EnterpriseServerUserAccountConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccount] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccount +} + +""" +An email belonging to a user account on an Enterprise Server installation. +""" +type EnterpriseServerUserAccountEmail implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email address. + """ + email: String! + id: ID! + + """ + Indicates whether this is the primary email of the associated user account. + """ + isPrimary: Boolean! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user account to which the email belongs. + """ + userAccount: EnterpriseServerUserAccount! +} + +""" +The connection type for EnterpriseServerUserAccountEmail. +""" +type EnterpriseServerUserAccountEmailConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountEmailEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccountEmail] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountEmailEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccountEmail +} + +""" +Ordering options for Enterprise Server user account email connections. +""" +input EnterpriseServerUserAccountEmailOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order emails by. + """ + field: EnterpriseServerUserAccountEmailOrderField! +} + +""" +Properties by which Enterprise Server user account email connections can be ordered. +""" +enum EnterpriseServerUserAccountEmailOrderField { + """ + Order emails by email + """ + EMAIL +} + +""" +Ordering options for Enterprise Server user account connections. +""" +input EnterpriseServerUserAccountOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order user accounts by. + """ + field: EnterpriseServerUserAccountOrderField! +} + +""" +Properties by which Enterprise Server user account connections can be ordered. +""" +enum EnterpriseServerUserAccountOrderField { + """ + Order user accounts by login + """ + LOGIN + + """ + Order user accounts by creation time on the Enterprise Server installation + """ + REMOTE_CREATED_AT +} + +""" +A user accounts upload from an Enterprise Server installation. +""" +type EnterpriseServerUserAccountsUpload implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The enterprise to which this upload belongs. + """ + enterprise: Enterprise! + + """ + The Enterprise Server installation for which this upload was generated. + """ + enterpriseServerInstallation: EnterpriseServerInstallation! + id: ID! + + """ + The name of the file uploaded. + """ + name: String! + + """ + The synchronization state of the upload + """ + syncState: EnterpriseServerUserAccountsUploadSyncState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for EnterpriseServerUserAccountsUpload. +""" +type EnterpriseServerUserAccountsUploadConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountsUploadEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccountsUpload] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountsUploadEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccountsUpload +} + +""" +Ordering options for Enterprise Server user accounts upload connections. +""" +input EnterpriseServerUserAccountsUploadOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order user accounts uploads by. + """ + field: EnterpriseServerUserAccountsUploadOrderField! +} + +""" +Properties by which Enterprise Server user accounts upload connections can be ordered. +""" +enum EnterpriseServerUserAccountsUploadOrderField { + """ + Order user accounts uploads by creation time + """ + CREATED_AT +} + +""" +Synchronization state of the Enterprise Server user accounts upload +""" +enum EnterpriseServerUserAccountsUploadSyncState { + """ + The synchronization of the upload failed. + """ + FAILURE + + """ + The synchronization of the upload is pending. + """ + PENDING + + """ + The synchronization of the upload succeeded. + """ + SUCCESS +} + +""" +An account for a user who is an admin of an enterprise or a member of an enterprise through one or more organizations. +""" +type EnterpriseUserAccount implements Actor & Node { + """ + A URL pointing to the enterprise user account's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The enterprise in which this user account exists. + """ + enterprise: Enterprise! + id: ID! + + """ + An identifier for the enterprise user account, a login or email address + """ + login: String! + + """ + The name of the enterprise user account + """ + name: String + + """ + A list of enterprise organizations this user is a member of. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations returned from the connection. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + + """ + The role of the user in the enterprise organization. + """ + role: EnterpriseUserAccountMembershipRole + ): EnterpriseOrganizationMembershipConnection! + + """ + The HTTP path for this user. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this user. + """ + url: URI! + + """ + The user within the enterprise. + """ + user: User +} + +""" +The connection type for EnterpriseUserAccount. +""" +type EnterpriseUserAccountConnection { + """ + A list of edges. + """ + edges: [EnterpriseUserAccountEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseUserAccount] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseUserAccountEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseUserAccount +} + +""" +The possible roles for enterprise membership. +""" +enum EnterpriseUserAccountMembershipRole { + """ + The user is a member of the enterprise membership. + """ + MEMBER + + """ + The user is an owner of the enterprise membership. + """ + OWNER +} + +""" +The possible GitHub Enterprise deployments where this user can exist. +""" +enum EnterpriseUserDeployment { + """ + The user is part of a GitHub Enterprise Cloud deployment. + """ + CLOUD + + """ + The user is part of a GitHub Enterprise Server deployment. + """ + SERVER +} + +""" +An external identity provisioned by SAML SSO or SCIM. +""" +type ExternalIdentity implements Node { + """ + The GUID for this identity + """ + guid: String! + id: ID! + + """ + Organization invitation for this SCIM-provisioned external identity + """ + organizationInvitation: OrganizationInvitation + + """ + SAML Identity attributes + """ + samlIdentity: ExternalIdentitySamlAttributes + + """ + SCIM Identity attributes + """ + scimIdentity: ExternalIdentityScimAttributes + + """ + User linked to this external identity. Will be NULL if this identity has not been claimed by an organization member. + """ + user: User +} + +""" +The connection type for ExternalIdentity. +""" +type ExternalIdentityConnection { + """ + A list of edges. + """ + edges: [ExternalIdentityEdge] + + """ + A list of nodes. + """ + nodes: [ExternalIdentity] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ExternalIdentityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ExternalIdentity +} + +""" +SAML attributes for the External Identity +""" +type ExternalIdentitySamlAttributes { + """ + The emails associated with the SAML identity + """ + emails: [UserEmailMetadata!] + + """ + Family name of the SAML identity + """ + familyName: String + + """ + Given name of the SAML identity + """ + givenName: String + + """ + The groups linked to this identity in IDP + """ + groups: [String!] + + """ + The NameID of the SAML identity + """ + nameId: String + + """ + The userName of the SAML identity + """ + username: String +} + +""" +SCIM attributes for the External Identity +""" +type ExternalIdentityScimAttributes { + """ + The emails associated with the SCIM identity + """ + emails: [UserEmailMetadata!] + + """ + Family name of the SCIM identity + """ + familyName: String + + """ + Given name of the SCIM identity + """ + givenName: String + + """ + The groups linked to this identity in IDP + """ + groups: [String!] + + """ + The userName of the SCIM identity + """ + username: String +} + +""" +The possible viewed states of a file . +""" +enum FileViewedState { + """ + The file has new changes since last viewed. + """ + DISMISSED + + """ + The file has not been marked as viewed. + """ + UNVIEWED + + """ + The file has been marked as viewed. + """ + VIEWED +} + +""" +Autogenerated input type of FollowUser +""" +input FollowUserInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the user to follow. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of FollowUser +""" +type FollowUserPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was followed. + """ + user: User +} + +""" +The connection type for User. +""" +type FollowerConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The connection type for User. +""" +type FollowingConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A funding platform link for a repository. +""" +type FundingLink { + """ + The funding platform this link is for. + """ + platform: FundingPlatform! + + """ + The configured URL for this funding link. + """ + url: URI! +} + +""" +The possible funding platforms for repository funding links. +""" +enum FundingPlatform { + """ + Community Bridge funding platform. + """ + COMMUNITY_BRIDGE + + """ + Custom funding platform. + """ + CUSTOM + + """ + GitHub funding platform. + """ + GITHUB + + """ + IssueHunt funding platform. + """ + ISSUEHUNT + + """ + Ko-fi funding platform. + """ + KO_FI + + """ + Liberapay funding platform. + """ + LIBERAPAY + + """ + Open Collective funding platform. + """ + OPEN_COLLECTIVE + + """ + Otechie funding platform. + """ + OTECHIE + + """ + Patreon funding platform. + """ + PATREON + + """ + Tidelift funding platform. + """ + TIDELIFT +} + +""" +A generic hovercard context with a message and icon +""" +type GenericHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! +} + +""" +A Gist. +""" +type Gist implements Node & Starrable & UniformResourceLocatable { + """ + A list of comments associated with the gist + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): GistCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The gist description. + """ + description: String + + """ + The files in this gist. + """ + files( + """ + The maximum number of files to return. + """ + limit: Int = 10 + + """ + The oid of the files to return + """ + oid: GitObjectID + ): [GistFile] + + """ + A list of forks associated with the gist + """ + forks( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for gists returned from the connection + """ + orderBy: GistOrder + ): GistConnection! + id: ID! + + """ + Identifies if the gist is a fork. + """ + isFork: Boolean! + + """ + Whether the gist is public or not. + """ + isPublic: Boolean! + + """ + The gist name. + """ + name: String! + + """ + The gist owner. + """ + owner: RepositoryOwner + + """ + Identifies when the gist was last pushed to. + """ + pushedAt: DateTime + + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this Gist. + """ + url: URI! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +Represents a comment on an Gist. +""" +type GistComment implements Comment & Deletable & Minimizable & Node & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the gist. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the comment body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The associated gist. + """ + gist: Gist! + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for GistComment. +""" +type GistCommentConnection { + """ + A list of edges. + """ + edges: [GistCommentEdge] + + """ + A list of nodes. + """ + nodes: [GistComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GistCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: GistComment +} + +""" +The connection type for Gist. +""" +type GistConnection { + """ + A list of edges. + """ + edges: [GistEdge] + + """ + A list of nodes. + """ + nodes: [Gist] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GistEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Gist +} + +""" +A file in a gist. +""" +type GistFile { + """ + The file name encoded to remove characters that are invalid in URL paths. + """ + encodedName: String + + """ + The gist file encoding. + """ + encoding: String + + """ + The file extension from the file name. + """ + extension: String + + """ + Indicates if this file is an image. + """ + isImage: Boolean! + + """ + Whether the file's contents were truncated. + """ + isTruncated: Boolean! + + """ + The programming language this file is written in. + """ + language: Language + + """ + The gist file name. + """ + name: String + + """ + The gist file size in bytes. + """ + size: Int + + """ + UTF8 text data or null if the file is binary + """ + text( + """ + Optionally truncate the returned file to this length. + """ + truncate: Int + ): String +} + +""" +Ordering options for gist connections +""" +input GistOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repositories by. + """ + field: GistOrderField! +} + +""" +Properties by which gist connections can be ordered. +""" +enum GistOrderField { + """ + Order gists by creation time + """ + CREATED_AT + + """ + Order gists by push time + """ + PUSHED_AT + + """ + Order gists by update time + """ + UPDATED_AT +} + +""" +The privacy of a Gist +""" +enum GistPrivacy { + """ + Gists that are public and secret + """ + ALL + + """ + Public + """ + PUBLIC + + """ + Secret + """ + SECRET +} + +""" +Represents an actor in a Git commit (ie. an author or committer). +""" +type GitActor { + """ + A URL pointing to the author's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The timestamp of the Git action (authoring or committing). + """ + date: GitTimestamp + + """ + The email in the Git commit. + """ + email: String + + """ + The name in the Git commit. + """ + name: String + + """ + The GitHub user corresponding to the email field. Null if no such user exists. + """ + user: User +} + +""" +The connection type for GitActor. +""" +type GitActorConnection { + """ + A list of edges. + """ + edges: [GitActorEdge] + + """ + A list of nodes. + """ + nodes: [GitActor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GitActorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: GitActor +} + +""" +Represents information about the GitHub instance. +""" +type GitHubMetadata { + """ + Returns a String that's a SHA of `github-services` + """ + gitHubServicesSha: GitObjectID! + + """ + IP addresses that users connect to for git operations + """ + gitIpAddresses: [String!] + + """ + IP addresses that service hooks are sent from + """ + hookIpAddresses: [String!] + + """ + IP addresses that the importer connects from + """ + importerIpAddresses: [String!] + + """ + Whether or not users are verified + """ + isPasswordAuthenticationVerifiable: Boolean! + + """ + IP addresses for GitHub Pages' A records + """ + pagesIpAddresses: [String!] +} + +""" +Represents a Git object. +""" +interface GitObject { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + id: ID! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! +} + +""" +A Git object ID. +""" +scalar GitObjectID + +""" +A fully qualified reference name (e.g. `refs/heads/master`). +""" +scalar GitRefname @preview(toggledBy: "update-refs-preview") + +""" +Git SSH string +""" +scalar GitSSHRemote + +""" +Information about a signature (GPG or S/MIME) on a Commit or Tag. +""" +interface GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +The state of a Git signature. +""" +enum GitSignatureState { + """ + The signing certificate or its chain could not be verified + """ + BAD_CERT + + """ + Invalid email used for signing + """ + BAD_EMAIL + + """ + Signing key expired + """ + EXPIRED_KEY + + """ + Internal error - the GPG verification service misbehaved + """ + GPGVERIFY_ERROR + + """ + Internal error - the GPG verification service is unavailable at the moment + """ + GPGVERIFY_UNAVAILABLE + + """ + Invalid signature + """ + INVALID + + """ + Malformed signature + """ + MALFORMED_SIG + + """ + The usage flags for the key that signed this don't allow signing + """ + NOT_SIGNING_KEY + + """ + Email used for signing not known to GitHub + """ + NO_USER + + """ + Valid signature, though certificate revocation check failed + """ + OCSP_ERROR + + """ + Valid signature, pending certificate revocation checking + """ + OCSP_PENDING + + """ + One or more certificates in chain has been revoked + """ + OCSP_REVOKED + + """ + Key used for signing not known to GitHub + """ + UNKNOWN_KEY + + """ + Unknown signature type + """ + UNKNOWN_SIG_TYPE + + """ + Unsigned + """ + UNSIGNED + + """ + Email used for signing unverified on GitHub + """ + UNVERIFIED_EMAIL + + """ + Valid signature and verified by GitHub + """ + VALID +} + +""" +An ISO-8601 encoded date string. Unlike the DateTime type, GitTimestamp is not converted in UTC. +""" +scalar GitTimestamp + +""" +Represents a GPG signature on a Commit or Tag. +""" +type GpgSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Hex-encoded ID of the key that signed this object. + """ + keyId: String + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +A string containing HTML code. +""" +scalar HTML + +""" +Represents a 'head_ref_deleted' event on a given pull request. +""" +type HeadRefDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the Ref associated with the `head_ref_deleted` event. + """ + headRef: Ref + + """ + Identifies the name of the Ref associated with the `head_ref_deleted` event. + """ + headRefName: String! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'head_ref_force_pushed' event on a given pull request. +""" +type HeadRefForcePushedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the after commit SHA for the 'head_ref_force_pushed' event. + """ + afterCommit: Commit + + """ + Identifies the before commit SHA for the 'head_ref_force_pushed' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the fully qualified ref name for the 'head_ref_force_pushed' event. + """ + ref: Ref +} + +""" +Represents a 'head_ref_restored' event on a given pull request. +""" +type HeadRefRestoredEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Detail needed to display a hovercard for a user +""" +type Hovercard { + """ + Each of the contexts for this hovercard + """ + contexts: [HovercardContext!]! +} + +""" +An individual line of a hovercard +""" +interface HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! +} + +""" +The possible states in which authentication can be configured with an identity provider. +""" +enum IdentityProviderConfigurationState { + """ + Authentication with an identity provider is configured but not enforced. + """ + CONFIGURED + + """ + Authentication with an identity provider is configured and enforced. + """ + ENFORCED + + """ + Authentication with an identity provider is not configured. + """ + UNCONFIGURED +} + +""" +Autogenerated input type of ImportProject +""" +input ImportProjectInput { + """ + The description of Project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A list of columns containing issues and pull requests. + """ + columnImports: [ProjectColumnImport!]! + + """ + The name of Project. + """ + name: String! + + """ + The name of the Organization or User to create the Project under. + """ + ownerName: String! + + """ + Whether the Project is public or not. + """ + public: Boolean = false +} + +""" +Autogenerated return type of ImportProject +""" +type ImportProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new Project! + """ + project: Project +} + +""" +Autogenerated input type of InviteEnterpriseAdmin +""" +input InviteEnterpriseAdminInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The email of the person to invite as an administrator. + """ + email: String + + """ + The ID of the enterprise to which you want to invite an administrator. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a user to invite as an administrator. + """ + invitee: String + + """ + The role of the administrator. + """ + role: EnterpriseAdministratorRole +} + +""" +Autogenerated return type of InviteEnterpriseAdmin +""" +type InviteEnterpriseAdminPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created enterprise administrator invitation. + """ + invitation: EnterpriseAdministratorInvitation +} + +""" +The possible values for the IP allow list enabled setting. +""" +enum IpAllowListEnabledSettingValue { + """ + The setting is disabled for the owner. + """ + DISABLED + + """ + The setting is enabled for the owner. + """ + ENABLED +} + +""" +An IP address or range of addresses that is allowed to access an owner's resources. +""" +type IpAllowListEntry implements Node { + """ + A single IP address or range of IP addresses in CIDR notation. + """ + allowListValue: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Whether the entry is currently active. + """ + isActive: Boolean! + + """ + The name of the IP allow list entry. + """ + name: String + + """ + The owner of the IP allow list entry. + """ + owner: IpAllowListOwner! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for IpAllowListEntry. +""" +type IpAllowListEntryConnection { + """ + A list of edges. + """ + edges: [IpAllowListEntryEdge] + + """ + A list of nodes. + """ + nodes: [IpAllowListEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type IpAllowListEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IpAllowListEntry +} + +""" +Ordering options for IP allow list entry connections. +""" +input IpAllowListEntryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order IP allow list entries by. + """ + field: IpAllowListEntryOrderField! +} + +""" +Properties by which IP allow list entry connections can be ordered. +""" +enum IpAllowListEntryOrderField { + """ + Order IP allow list entries by the allow list value. + """ + ALLOW_LIST_VALUE + + """ + Order IP allow list entries by creation time. + """ + CREATED_AT +} + +""" +Types that can own an IP allow list. +""" +union IpAllowListOwner = Enterprise | Organization + +""" +An Issue is a place to discuss ideas, enhancements, tasks, and bugs for a project. +""" +type Issue implements Assignable & Closable & Comment & Labelable & Lockable & Node & Reactable & RepositoryNode & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + A list of Users assigned to this object. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the body of the issue. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The http path for this issue body + """ + bodyResourcePath: URI! + + """ + Identifies the body of the issue rendered to text. + """ + bodyText: String! + + """ + The http URL for this issue body + """ + bodyUrl: URI! + + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + A list of comments associated with the Issue. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issue comments returned from the connection. + """ + orderBy: IssueCommentOrder + ): IssueCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The hovercard information for this issue + """ + hovercard( + """ + Whether or not to include notification contexts + """ + includeNotificationContexts: Boolean = true + ): Hovercard! + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Is this issue read by the viewer + """ + isReadByViewer: Boolean + + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + `true` if the object is locked + """ + locked: Boolean! + + """ + Identifies the milestone associated with the issue. + """ + milestone: Milestone + + """ + Identifies the issue number. + """ + number: Int! + + """ + A list of Users that are participating in the Issue conversation. + """ + participants( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + List of project cards associated with this issue. + """ + projectCards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this issue + """ + resourcePath: URI! + + """ + Identifies the state of the issue. + """ + state: IssueState! + + """ + A list of events, comments, commits, etc. associated with the issue. + """ + timeline( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows filtering timeline events by a `since` timestamp. + """ + since: DateTime + ): IssueTimelineConnection! @deprecated(reason: "`timeline` will be removed Use Issue.timelineItems instead. Removal on 2020-10-01 UTC.") + + """ + A list of events, comments, commits, etc. associated with the issue. + """ + timelineItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter timeline items by type. + """ + itemTypes: [IssueTimelineItemsItemType!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter timeline items by a `since` timestamp. + """ + since: DateTime + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): IssueTimelineItemsConnection! + + """ + Identifies the issue title. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Represents a comment on an Issue. +""" +type IssueComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + Identifies the issue associated with the comment. + """ + issue: Issue! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Returns the pull request associated with the comment, if this comment was made on a + pull request. + """ + pullRequest: PullRequest + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this issue comment + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue comment + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for IssueComment. +""" +type IssueCommentConnection { + """ + A list of edges. + """ + edges: [IssueCommentEdge] + + """ + A list of nodes. + """ + nodes: [IssueComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type IssueCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueComment +} + +""" +Ways in which lists of issue comments can be ordered upon return. +""" +input IssueCommentOrder { + """ + The direction in which to order issue comments by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order issue comments by. + """ + field: IssueCommentOrderField! +} + +""" +Properties by which issue comment connections can be ordered. +""" +enum IssueCommentOrderField { + """ + Order issue comments by update time + """ + UPDATED_AT +} + +""" +The connection type for Issue. +""" +type IssueConnection { + """ + A list of edges. + """ + edges: [IssueEdge] + + """ + A list of nodes. + """ + nodes: [Issue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates issues opened by a user within one repository. +""" +type IssueContributionsByRepository { + """ + The issue contributions. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedIssueContributionConnection! + + """ + The repository in which the issues were opened. + """ + repository: Repository! +} + +""" +An edge in a connection. +""" +type IssueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Issue +} + +""" +Ways in which to filter lists of issues. +""" +input IssueFilters { + """ + List issues assigned to given name. Pass in `null` for issues with no assigned + user, and `*` for issues assigned to any user. + """ + assignee: String + + """ + List issues created by given name. + """ + createdBy: String + + """ + List issues where the list of label names exist on the issue. + """ + labels: [String!] + + """ + List issues where the given name is mentioned in the issue. + """ + mentioned: String + + """ + List issues by given milestone argument. If an string representation of an + integer is passed, it should refer to a milestone by its number field. Pass in + `null` for issues with no milestone, and `*` for issues that are assigned to any milestone. + """ + milestone: String + + """ + List issues that have been updated at or after the given date. + """ + since: DateTime + + """ + List issues filtered by the list of states given. + """ + states: [IssueState!] + + """ + List issues subscribed to by viewer. + """ + viewerSubscribed: Boolean = false +} + +""" +Used for return value of Repository.issueOrPullRequest. +""" +union IssueOrPullRequest = Issue | PullRequest + +""" +Ways in which lists of issues can be ordered upon return. +""" +input IssueOrder { + """ + The direction in which to order issues by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order issues by. + """ + field: IssueOrderField! +} + +""" +Properties by which issue connections can be ordered. +""" +enum IssueOrderField { + """ + Order issues by comment count + """ + COMMENTS + + """ + Order issues by creation time + """ + CREATED_AT + + """ + Order issues by update time + """ + UPDATED_AT +} + +""" +The possible states of an issue. +""" +enum IssueState { + """ + An issue that has been closed + """ + CLOSED + + """ + An issue that is still open + """ + OPEN +} + +""" +A repository issue template. +""" +type IssueTemplate { + """ + The template purpose. + """ + about: String + + """ + The suggested issue body. + """ + body: String + + """ + The template name. + """ + name: String! + + """ + The suggested issue title. + """ + title: String +} + +""" +The connection type for IssueTimelineItem. +""" +type IssueTimelineConnection { + """ + A list of edges. + """ + edges: [IssueTimelineItemEdge] + + """ + A list of nodes. + """ + nodes: [IssueTimelineItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An item in an issue timeline +""" +union IssueTimelineItem = AssignedEvent | ClosedEvent | Commit | CrossReferencedEvent | DemilestonedEvent | IssueComment | LabeledEvent | LockedEvent | MilestonedEvent | ReferencedEvent | RenamedTitleEvent | ReopenedEvent | SubscribedEvent | TransferredEvent | UnassignedEvent | UnlabeledEvent | UnlockedEvent | UnsubscribedEvent | UserBlockedEvent + +""" +An edge in a connection. +""" +type IssueTimelineItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueTimelineItem +} + +""" +An item in an issue timeline +""" +union IssueTimelineItems = AddedToProjectEvent | AssignedEvent | ClosedEvent | CommentDeletedEvent | ConnectedEvent | ConvertedNoteToIssueEvent | CrossReferencedEvent | DemilestonedEvent | DisconnectedEvent | IssueComment | LabeledEvent | LockedEvent | MarkedAsDuplicateEvent | MentionedEvent | MilestonedEvent | MovedColumnsInProjectEvent | PinnedEvent | ReferencedEvent | RemovedFromProjectEvent | RenamedTitleEvent | ReopenedEvent | SubscribedEvent | TransferredEvent | UnassignedEvent | UnlabeledEvent | UnlockedEvent | UnmarkedAsDuplicateEvent | UnpinnedEvent | UnsubscribedEvent | UserBlockedEvent + +""" +The connection type for IssueTimelineItems. +""" +type IssueTimelineItemsConnection { + """ + A list of edges. + """ + edges: [IssueTimelineItemsEdge] + + """ + Identifies the count of items after applying `before` and `after` filters. + """ + filteredCount: Int! + + """ + A list of nodes. + """ + nodes: [IssueTimelineItems] + + """ + Identifies the count of items after applying `before`/`after` filters and `first`/`last`/`skip` slicing. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the date and time when the timeline was last updated. + """ + updatedAt: DateTime! +} + +""" +An edge in a connection. +""" +type IssueTimelineItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueTimelineItems +} + +""" +The possible item types found in a timeline. +""" +enum IssueTimelineItemsItemType { + """ + Represents a 'added_to_project' event on a given issue or pull request. + """ + ADDED_TO_PROJECT_EVENT + + """ + Represents an 'assigned' event on any assignable object. + """ + ASSIGNED_EVENT + + """ + Represents a 'closed' event on any `Closable`. + """ + CLOSED_EVENT + + """ + Represents a 'comment_deleted' event on a given issue or pull request. + """ + COMMENT_DELETED_EVENT + + """ + Represents a 'connected' event on a given issue or pull request. + """ + CONNECTED_EVENT + + """ + Represents a 'converted_note_to_issue' event on a given issue or pull request. + """ + CONVERTED_NOTE_TO_ISSUE_EVENT + + """ + Represents a mention made by one issue or pull request to another. + """ + CROSS_REFERENCED_EVENT + + """ + Represents a 'demilestoned' event on a given issue or pull request. + """ + DEMILESTONED_EVENT + + """ + Represents a 'disconnected' event on a given issue or pull request. + """ + DISCONNECTED_EVENT + + """ + Represents a comment on an Issue. + """ + ISSUE_COMMENT + + """ + Represents a 'labeled' event on a given issue or pull request. + """ + LABELED_EVENT + + """ + Represents a 'locked' event on a given issue or pull request. + """ + LOCKED_EVENT + + """ + Represents a 'marked_as_duplicate' event on a given issue or pull request. + """ + MARKED_AS_DUPLICATE_EVENT + + """ + Represents a 'mentioned' event on a given issue or pull request. + """ + MENTIONED_EVENT + + """ + Represents a 'milestoned' event on a given issue or pull request. + """ + MILESTONED_EVENT + + """ + Represents a 'moved_columns_in_project' event on a given issue or pull request. + """ + MOVED_COLUMNS_IN_PROJECT_EVENT + + """ + Represents a 'pinned' event on a given issue or pull request. + """ + PINNED_EVENT + + """ + Represents a 'referenced' event on a given `ReferencedSubject`. + """ + REFERENCED_EVENT + + """ + Represents a 'removed_from_project' event on a given issue or pull request. + """ + REMOVED_FROM_PROJECT_EVENT + + """ + Represents a 'renamed' event on a given issue or pull request + """ + RENAMED_TITLE_EVENT + + """ + Represents a 'reopened' event on any `Closable`. + """ + REOPENED_EVENT + + """ + Represents a 'subscribed' event on a given `Subscribable`. + """ + SUBSCRIBED_EVENT + + """ + Represents a 'transferred' event on a given issue or pull request. + """ + TRANSFERRED_EVENT + + """ + Represents an 'unassigned' event on any assignable object. + """ + UNASSIGNED_EVENT + + """ + Represents an 'unlabeled' event on a given issue or pull request. + """ + UNLABELED_EVENT + + """ + Represents an 'unlocked' event on a given issue or pull request. + """ + UNLOCKED_EVENT + + """ + Represents an 'unmarked_as_duplicate' event on a given issue or pull request. + """ + UNMARKED_AS_DUPLICATE_EVENT + + """ + Represents an 'unpinned' event on a given issue or pull request. + """ + UNPINNED_EVENT + + """ + Represents an 'unsubscribed' event on a given `Subscribable`. + """ + UNSUBSCRIBED_EVENT + + """ + Represents a 'user_blocked' event on a given user. + """ + USER_BLOCKED_EVENT +} + +""" +Represents a user signing up for a GitHub account. +""" +type JoinedGitHubContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A label for categorizing Issues or Milestones with a given Repository. +""" +type Label implements Node { + """ + Identifies the label color. + """ + color: String! + + """ + Identifies the date and time when the label was created. + """ + createdAt: DateTime + + """ + A brief description of this label. + """ + description: String + id: ID! + + """ + Indicates whether or not this is a default label. + """ + isDefault: Boolean! + + """ + A list of issues associated with this label. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Identifies the label name. + """ + name: String! + + """ + A list of pull requests associated with this label. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + The repository associated with this label. + """ + repository: Repository! + + """ + The HTTP path for this label. + """ + resourcePath: URI! + + """ + Identifies the date and time when the label was last updated. + """ + updatedAt: DateTime + + """ + The HTTP URL for this label. + """ + url: URI! +} + +""" +The connection type for Label. +""" +type LabelConnection { + """ + A list of edges. + """ + edges: [LabelEdge] + + """ + A list of nodes. + """ + nodes: [Label] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type LabelEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Label +} + +""" +Ways in which lists of labels can be ordered upon return. +""" +input LabelOrder { + """ + The direction in which to order labels by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order labels by. + """ + field: LabelOrderField! +} + +""" +Properties by which label connections can be ordered. +""" +enum LabelOrderField { + """ + Order labels by creation time + """ + CREATED_AT + + """ + Order labels by name + """ + NAME +} + +""" +An object that can have labels assigned to it. +""" +interface Labelable { + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection +} + +""" +Represents a 'labeled' event on a given issue or pull request. +""" +type LabeledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the label associated with the 'labeled' event. + """ + label: Label! + + """ + Identifies the `Labelable` associated with the event. + """ + labelable: Labelable! +} + +""" +Represents a given language found in repositories. +""" +type Language implements Node { + """ + The color defined for the current language. + """ + color: String + id: ID! + + """ + The name of the current language. + """ + name: String! +} + +""" +A list of languages associated with the parent. +""" +type LanguageConnection { + """ + A list of edges. + """ + edges: [LanguageEdge] + + """ + A list of nodes. + """ + nodes: [Language] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total size in bytes of files written in that language. + """ + totalSize: Int! +} + +""" +Represents the language of a repository. +""" +type LanguageEdge { + cursor: String! + node: Language! + + """ + The number of bytes of code written in the language. + """ + size: Int! +} + +""" +Ordering options for language connections. +""" +input LanguageOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order languages by. + """ + field: LanguageOrderField! +} + +""" +Properties by which language connections can be ordered. +""" +enum LanguageOrderField { + """ + Order languages by the size of all files containing the language + """ + SIZE +} + +""" +A repository's open source license +""" +type License implements Node { + """ + The full text of the license + """ + body: String! + + """ + The conditions set by the license + """ + conditions: [LicenseRule]! + + """ + A human-readable description of the license + """ + description: String + + """ + Whether the license should be featured + """ + featured: Boolean! + + """ + Whether the license should be displayed in license pickers + """ + hidden: Boolean! + id: ID! + + """ + Instructions on how to implement the license + """ + implementation: String + + """ + The lowercased SPDX ID of the license + """ + key: String! + + """ + The limitations set by the license + """ + limitations: [LicenseRule]! + + """ + The license full name specified by + """ + name: String! + + """ + Customary short name if applicable (e.g, GPLv3) + """ + nickname: String + + """ + The permissions set by the license + """ + permissions: [LicenseRule]! + + """ + Whether the license is a pseudo-license placeholder (e.g., other, no-license) + """ + pseudoLicense: Boolean! + + """ + Short identifier specified by + """ + spdxId: String + + """ + URL to the license on + """ + url: URI +} + +""" +Describes a License's conditions, permissions, and limitations +""" +type LicenseRule { + """ + A description of the rule + """ + description: String! + + """ + The machine-readable rule key + """ + key: String! + + """ + The human-readable rule label + """ + label: String! +} + +""" +Autogenerated input type of LinkRepositoryToProject +""" +input LinkRepositoryToProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to link to a Repository + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + The ID of the Repository to link to a Project. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of LinkRepositoryToProject +""" +type LinkRepositoryToProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The linked Project. + """ + project: Project + + """ + The linked Repository. + """ + repository: Repository +} + +""" +Autogenerated input type of LockLockable +""" +input LockLockableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A reason for why the item will be locked. + """ + lockReason: LockReason + + """ + ID of the item to be locked. + """ + lockableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Lockable") +} + +""" +Autogenerated return type of LockLockable +""" +type LockLockablePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was locked. + """ + lockedRecord: Lockable +} + +""" +The possible reasons that an issue or pull request was locked. +""" +enum LockReason { + """ + The issue or pull request was locked because the conversation was off-topic. + """ + OFF_TOPIC + + """ + The issue or pull request was locked because the conversation was resolved. + """ + RESOLVED + + """ + The issue or pull request was locked because the conversation was spam. + """ + SPAM + + """ + The issue or pull request was locked because the conversation was too heated. + """ + TOO_HEATED +} + +""" +An object that can be locked. +""" +interface Lockable { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + `true` if the object is locked + """ + locked: Boolean! +} + +""" +Represents a 'locked' event on a given issue or pull request. +""" +type LockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reason that the conversation was locked (optional). + """ + lockReason: LockReason + + """ + Object that was locked. + """ + lockable: Lockable! +} + +""" +A placeholder user for attribution of imported data on GitHub. +""" +type Mannequin implements Actor & Node & UniformResourceLocatable { + """ + A URL pointing to the GitHub App's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The mannequin's email on the source instance. + """ + email: String + id: ID! + + """ + The username of the actor. + """ + login: String! + + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The URL to this resource. + """ + url: URI! +} + +""" +Autogenerated input type of MarkFileAsViewed +""" +input MarkFileAsViewedInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The path of the file to mark as viewed + """ + path: String! + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of MarkFileAsViewed +""" +type MarkFileAsViewedPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of MarkPullRequestReadyForReview +""" +input MarkPullRequestReadyForReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be marked as ready for review. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of MarkPullRequestReadyForReview +""" +type MarkPullRequestReadyForReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is ready for review. + """ + pullRequest: PullRequest +} + +""" +Represents a 'marked_as_duplicate' event on a given issue or pull request. +""" +type MarkedAsDuplicateEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + The authoritative issue or pull request which has been duplicated by another. + """ + canonical: IssueOrPullRequest + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The issue or pull request which has been marked as a duplicate of another. + """ + duplicate: IssueOrPullRequest + id: ID! + + """ + Canonical and duplicate belong to different repositories. + """ + isCrossRepository: Boolean! +} + +""" +A public description of a Marketplace category. +""" +type MarketplaceCategory implements Node { + """ + The category's description. + """ + description: String + + """ + The technical description of how apps listed in this category work with GitHub. + """ + howItWorks: String + id: ID! + + """ + The category's name. + """ + name: String! + + """ + How many Marketplace listings have this as their primary category. + """ + primaryListingCount: Int! + + """ + The HTTP path for this Marketplace category. + """ + resourcePath: URI! + + """ + How many Marketplace listings have this as their secondary category. + """ + secondaryListingCount: Int! + + """ + The short name of the category used in its URL. + """ + slug: String! + + """ + The HTTP URL for this Marketplace category. + """ + url: URI! +} + +""" +A listing in the GitHub integration marketplace. +""" +type MarketplaceListing implements Node { + """ + The GitHub App this listing represents. + """ + app: App + + """ + URL to the listing owner's company site. + """ + companyUrl: URI + + """ + The HTTP path for configuring access to the listing's integration or OAuth app + """ + configurationResourcePath: URI! + + """ + The HTTP URL for configuring access to the listing's integration or OAuth app + """ + configurationUrl: URI! + + """ + URL to the listing's documentation. + """ + documentationUrl: URI + + """ + The listing's detailed description. + """ + extendedDescription: String + + """ + The listing's detailed description rendered to HTML. + """ + extendedDescriptionHTML: HTML! + + """ + The listing's introductory description. + """ + fullDescription: String! + + """ + The listing's introductory description rendered to HTML. + """ + fullDescriptionHTML: HTML! + + """ + Does this listing have any plans with a free trial? + """ + hasPublishedFreeTrialPlans: Boolean! + + """ + Does this listing have a terms of service link? + """ + hasTermsOfService: Boolean! + + """ + Whether the creator of the app is a verified org + """ + hasVerifiedOwner: Boolean! + + """ + A technical description of how this app works with GitHub. + """ + howItWorks: String + + """ + The listing's technical description rendered to HTML. + """ + howItWorksHTML: HTML! + id: ID! + + """ + URL to install the product to the viewer's account or organization. + """ + installationUrl: URI + + """ + Whether this listing's app has been installed for the current viewer + """ + installedForViewer: Boolean! + + """ + Whether this listing has been removed from the Marketplace. + """ + isArchived: Boolean! + + """ + Whether this listing is still an editable draft that has not been submitted + for review and is not publicly visible in the Marketplace. + """ + isDraft: Boolean! + + """ + Whether the product this listing represents is available as part of a paid plan. + """ + isPaid: Boolean! + + """ + Whether this listing has been approved for display in the Marketplace. + """ + isPublic: Boolean! + + """ + Whether this listing has been rejected by GitHub for display in the Marketplace. + """ + isRejected: Boolean! + + """ + Whether this listing has been approved for unverified display in the Marketplace. + """ + isUnverified: Boolean! + + """ + Whether this draft listing has been submitted for review for approval to be unverified in the Marketplace. + """ + isUnverifiedPending: Boolean! + + """ + Whether this draft listing has been submitted for review from GitHub for approval to be verified in the Marketplace. + """ + isVerificationPendingFromDraft: Boolean! + + """ + Whether this unverified listing has been submitted for review from GitHub for approval to be verified in the Marketplace. + """ + isVerificationPendingFromUnverified: Boolean! + + """ + Whether this listing has been approved for verified display in the Marketplace. + """ + isVerified: Boolean! + + """ + The hex color code, without the leading '#', for the logo background. + """ + logoBackgroundColor: String! + + """ + URL for the listing's logo image. + """ + logoUrl( + """ + The size in pixels of the resulting square image. + """ + size: Int = 400 + ): URI + + """ + The listing's full name. + """ + name: String! + + """ + The listing's very short description without a trailing period or ampersands. + """ + normalizedShortDescription: String! + + """ + URL to the listing's detailed pricing. + """ + pricingUrl: URI + + """ + The category that best describes the listing. + """ + primaryCategory: MarketplaceCategory! + + """ + URL to the listing's privacy policy, may return an empty string for listings that do not require a privacy policy URL. + """ + privacyPolicyUrl: URI! + + """ + The HTTP path for the Marketplace listing. + """ + resourcePath: URI! + + """ + The URLs for the listing's screenshots. + """ + screenshotUrls: [String]! + + """ + An alternate category that describes the listing. + """ + secondaryCategory: MarketplaceCategory + + """ + The listing's very short description. + """ + shortDescription: String! + + """ + The short name of the listing used in its URL. + """ + slug: String! + + """ + URL to the listing's status page. + """ + statusUrl: URI + + """ + An email address for support for this listing's app. + """ + supportEmail: String + + """ + Either a URL or an email address for support for this listing's app, may + return an empty string for listings that do not require a support URL. + """ + supportUrl: URI! + + """ + URL to the listing's terms of service. + """ + termsOfServiceUrl: URI + + """ + The HTTP URL for the Marketplace listing. + """ + url: URI! + + """ + Can the current viewer add plans for this Marketplace listing. + """ + viewerCanAddPlans: Boolean! + + """ + Can the current viewer approve this Marketplace listing. + """ + viewerCanApprove: Boolean! + + """ + Can the current viewer delist this Marketplace listing. + """ + viewerCanDelist: Boolean! + + """ + Can the current viewer edit this Marketplace listing. + """ + viewerCanEdit: Boolean! + + """ + Can the current viewer edit the primary and secondary category of this + Marketplace listing. + """ + viewerCanEditCategories: Boolean! + + """ + Can the current viewer edit the plans for this Marketplace listing. + """ + viewerCanEditPlans: Boolean! + + """ + Can the current viewer return this Marketplace listing to draft state + so it becomes editable again. + """ + viewerCanRedraft: Boolean! + + """ + Can the current viewer reject this Marketplace listing by returning it to + an editable draft state or rejecting it entirely. + """ + viewerCanReject: Boolean! + + """ + Can the current viewer request this listing be reviewed for display in + the Marketplace as verified. + """ + viewerCanRequestApproval: Boolean! + + """ + Indicates whether the current user has an active subscription to this Marketplace listing. + """ + viewerHasPurchased: Boolean! + + """ + Indicates if the current user has purchased a subscription to this Marketplace listing + for all of the organizations the user owns. + """ + viewerHasPurchasedForAllOrganizations: Boolean! + + """ + Does the current viewer role allow them to administer this Marketplace listing. + """ + viewerIsListingAdmin: Boolean! +} + +""" +Look up Marketplace Listings +""" +type MarketplaceListingConnection { + """ + A list of edges. + """ + edges: [MarketplaceListingEdge] + + """ + A list of nodes. + """ + nodes: [MarketplaceListing] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MarketplaceListingEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: MarketplaceListing +} + +""" +Entities that have members who can set status messages. +""" +interface MemberStatusable { + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for user statuses returned from the connection. + """ + orderBy: UserStatusOrder = {field: UPDATED_AT, direction: DESC} + ): UserStatusConnection! +} + +""" +Audit log entry for a members_can_delete_repos.clear event. +""" +type MembersCanDeleteReposClearAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a members_can_delete_repos.disable event. +""" +type MembersCanDeleteReposDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a members_can_delete_repos.enable event. +""" +type MembersCanDeleteReposEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Represents a 'mentioned' event on a given issue or pull request. +""" +type MentionedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! +} + +""" +Autogenerated input type of MergeBranch +""" +input MergeBranchInput { + """ + The email address to associate with this commit. + """ + authorEmail: String + + """ + The name of the base branch that the provided head will be merged into. + """ + base: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Message to use for the merge commit. If omitted, a default will be used. + """ + commitMessage: String + + """ + The head to merge into the base branch. This can be a branch name or a commit GitObjectID. + """ + head: String! + + """ + The Node ID of the Repository containing the base branch that will be modified. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of MergeBranch +""" +type MergeBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The resulting merge Commit. + """ + mergeCommit: Commit +} + +""" +Autogenerated input type of MergePullRequest +""" +input MergePullRequestInput { + """ + The email address to associate with this merge. + """ + authorEmail: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Commit body to use for the merge commit; if omitted, a default message will be used + """ + commitBody: String + + """ + Commit headline to use for the merge commit; if omitted, a default message will be used. + """ + commitHeadline: String + + """ + OID that the pull request head ref must match to allow merge; if omitted, no check is performed. + """ + expectedHeadOid: GitObjectID + + """ + The merge method to use. If omitted, defaults to 'MERGE' + """ + mergeMethod: PullRequestMergeMethod = MERGE + + """ + ID of the pull request to be merged. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of MergePullRequest +""" +type MergePullRequestPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was merged. + """ + pullRequest: PullRequest +} + +""" +Detailed status information about a pull request merge. +""" +enum MergeStateStatus { + """ + The head ref is out of date. + """ + BEHIND + + """ + The merge is blocked. + """ + BLOCKED + + """ + Mergeable and passing commit status. + """ + CLEAN + + """ + The merge commit cannot be cleanly created. + """ + DIRTY + + """ + The merge is blocked due to the pull request being a draft. + """ + DRAFT @deprecated(reason: "DRAFT state will be removed from this enum and `isDraft` should be used instead Use PullRequest.isDraft instead. Removal on 2021-01-01 UTC.") + + """ + Mergeable with passing commit status and pre-receive hooks. + """ + HAS_HOOKS + + """ + The state cannot currently be determined. + """ + UNKNOWN + + """ + Mergeable with non-passing commit status. + """ + UNSTABLE +} + +""" +Whether or not a PullRequest can be merged. +""" +enum MergeableState { + """ + The pull request cannot be merged due to merge conflicts. + """ + CONFLICTING + + """ + The pull request can be merged. + """ + MERGEABLE + + """ + The mergeability of the pull request is still being calculated. + """ + UNKNOWN +} + +""" +Represents a 'merged' event on a given pull request. +""" +type MergedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the commit associated with the `merge` event. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the Ref associated with the `merge` event. + """ + mergeRef: Ref + + """ + Identifies the name of the Ref associated with the `merge` event. + """ + mergeRefName: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this merged event. + """ + resourcePath: URI! + + """ + The HTTP URL for this merged event. + """ + url: URI! +} + +""" +Represents a Milestone object on a given repository. +""" +type Milestone implements Closable & Node & UniformResourceLocatable { + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who created the milestone. + """ + creator: Actor + + """ + Identifies the description of the milestone. + """ + description: String + + """ + Identifies the due date of the milestone. + """ + dueOn: DateTime + id: ID! + + """ + A list of issues associated with the milestone. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Identifies the number of the milestone. + """ + number: Int! + + """ + Identifies the percentage complete for the milestone + """ + progressPercentage: Float! + + """ + A list of pull requests associated with the milestone. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + The repository associated with this milestone. + """ + repository: Repository! + + """ + The HTTP path for this milestone + """ + resourcePath: URI! + + """ + Identifies the state of the milestone. + """ + state: MilestoneState! + + """ + Identifies the title of the milestone. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this milestone + """ + url: URI! +} + +""" +The connection type for Milestone. +""" +type MilestoneConnection { + """ + A list of edges. + """ + edges: [MilestoneEdge] + + """ + A list of nodes. + """ + nodes: [Milestone] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MilestoneEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Milestone +} + +""" +Types that can be inside a Milestone. +""" +union MilestoneItem = Issue | PullRequest + +""" +Ordering options for milestone connections. +""" +input MilestoneOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order milestones by. + """ + field: MilestoneOrderField! +} + +""" +Properties by which milestone connections can be ordered. +""" +enum MilestoneOrderField { + """ + Order milestones by when they were created. + """ + CREATED_AT + + """ + Order milestones by when they are due. + """ + DUE_DATE + + """ + Order milestones by their number. + """ + NUMBER + + """ + Order milestones by when they were last updated. + """ + UPDATED_AT +} + +""" +The possible states of a milestone. +""" +enum MilestoneState { + """ + A milestone that has been closed. + """ + CLOSED + + """ + A milestone that is still open. + """ + OPEN +} + +""" +Represents a 'milestoned' event on a given issue or pull request. +""" +type MilestonedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the milestone title associated with the 'milestoned' event. + """ + milestoneTitle: String! + + """ + Object referenced by event. + """ + subject: MilestoneItem! +} + +""" +Entities that can be minimized. +""" +interface Minimizable { + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + Returns why the comment was minimized. + """ + minimizedReason: String + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! +} + +""" +Autogenerated input type of MinimizeComment +""" +input MinimizeCommentInput { + """ + The classification of comment + """ + classifier: ReportedContentClassifiers! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["CommitComment", "GistComment", "IssueComment", "PullRequestReviewComment"], abstractType: "Minimizable") +} + +""" +Autogenerated return type of MinimizeComment +""" +type MinimizeCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The comment that was minimized. + """ + minimizedComment: Minimizable +} + +""" +Autogenerated input type of MoveProjectCard +""" +input MoveProjectCardInput { + """ + Place the new card after the card with this id. Pass null to place it at the top. + """ + afterCardId: ID @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + The id of the card to move. + """ + cardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to move it into. + """ + columnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of MoveProjectCard +""" +type MoveProjectCardPayload { + """ + The new edge of the moved card. + """ + cardEdge: ProjectCardEdge + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of MoveProjectColumn +""" +input MoveProjectColumnInput { + """ + Place the new column after the column with this id. Pass null to place it at the front. + """ + afterColumnId: ID @possibleTypes(concreteTypes: ["ProjectColumn"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to move. + """ + columnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of MoveProjectColumn +""" +type MoveProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new edge of the moved column. + """ + columnEdge: ProjectColumnEdge +} + +""" +Represents a 'moved_columns_in_project' event on a given issue or pull request. +""" +type MovedColumnsInProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Column name the issue or pull request was moved from. + """ + previousProjectColumnName: String! @preview(toggledBy: "starfox-preview") + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard @preview(toggledBy: "starfox-preview") + + """ + Column name the issue or pull request was moved to. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +The root query for implementing GraphQL mutations. +""" +type Mutation { + """ + Accepts a pending invitation for a user to become an administrator of an enterprise. + """ + acceptEnterpriseAdministratorInvitation(input: AcceptEnterpriseAdministratorInvitationInput!): AcceptEnterpriseAdministratorInvitationPayload + + """ + Applies a suggested topic to the repository. + """ + acceptTopicSuggestion(input: AcceptTopicSuggestionInput!): AcceptTopicSuggestionPayload + + """ + Adds assignees to an assignable object. + """ + addAssigneesToAssignable(input: AddAssigneesToAssignableInput!): AddAssigneesToAssignablePayload + + """ + Adds a comment to an Issue or Pull Request. + """ + addComment(input: AddCommentInput!): AddCommentPayload + + """ + Adds a support entitlement to an enterprise member. + """ + addEnterpriseSupportEntitlement(input: AddEnterpriseSupportEntitlementInput!): AddEnterpriseSupportEntitlementPayload + + """ + Adds labels to a labelable object. + """ + addLabelsToLabelable(input: AddLabelsToLabelableInput!): AddLabelsToLabelablePayload + + """ + Adds a card to a ProjectColumn. Either `contentId` or `note` must be provided but **not** both. + """ + addProjectCard(input: AddProjectCardInput!): AddProjectCardPayload + + """ + Adds a column to a Project. + """ + addProjectColumn(input: AddProjectColumnInput!): AddProjectColumnPayload + + """ + Adds a review to a Pull Request. + """ + addPullRequestReview(input: AddPullRequestReviewInput!): AddPullRequestReviewPayload + + """ + Adds a comment to a review. + """ + addPullRequestReviewComment(input: AddPullRequestReviewCommentInput!): AddPullRequestReviewCommentPayload + + """ + Adds a new thread to a pending Pull Request Review. + """ + addPullRequestReviewThread(input: AddPullRequestReviewThreadInput!): AddPullRequestReviewThreadPayload + + """ + Adds a reaction to a subject. + """ + addReaction(input: AddReactionInput!): AddReactionPayload + + """ + Adds a star to a Starrable. + """ + addStar(input: AddStarInput!): AddStarPayload + + """ + Adds a verifiable domain to an owning account. + """ + addVerifiableDomain(input: AddVerifiableDomainInput!): AddVerifiableDomainPayload + + """ + Marks a repository as archived. + """ + archiveRepository(input: ArchiveRepositoryInput!): ArchiveRepositoryPayload + + """ + Cancels a pending invitation for an administrator to join an enterprise. + """ + cancelEnterpriseAdminInvitation(input: CancelEnterpriseAdminInvitationInput!): CancelEnterpriseAdminInvitationPayload + + """ + Update your status on GitHub. + """ + changeUserStatus(input: ChangeUserStatusInput!): ChangeUserStatusPayload + + """ + Clears all labels from a labelable object. + """ + clearLabelsFromLabelable(input: ClearLabelsFromLabelableInput!): ClearLabelsFromLabelablePayload + + """ + Creates a new project by cloning configuration from an existing project. + """ + cloneProject(input: CloneProjectInput!): CloneProjectPayload + + """ + Create a new repository with the same files and directory structure as a template repository. + """ + cloneTemplateRepository(input: CloneTemplateRepositoryInput!): CloneTemplateRepositoryPayload + + """ + Close an issue. + """ + closeIssue(input: CloseIssueInput!): CloseIssuePayload + + """ + Close a pull request. + """ + closePullRequest(input: ClosePullRequestInput!): ClosePullRequestPayload + + """ + Convert a project note card to one associated with a newly created issue. + """ + convertProjectCardNoteToIssue(input: ConvertProjectCardNoteToIssueInput!): ConvertProjectCardNoteToIssuePayload + + """ + Create a new branch protection rule + """ + createBranchProtectionRule(input: CreateBranchProtectionRuleInput!): CreateBranchProtectionRulePayload + + """ + Create a check run. + """ + createCheckRun(input: CreateCheckRunInput!): CreateCheckRunPayload + + """ + Create a check suite + """ + createCheckSuite(input: CreateCheckSuiteInput!): CreateCheckSuitePayload + + """ + Create a content attachment. + """ + createContentAttachment(input: CreateContentAttachmentInput!): CreateContentAttachmentPayload @preview(toggledBy: "corsair-preview") + + """ + Creates a new deployment event. + """ + createDeployment(input: CreateDeploymentInput!): CreateDeploymentPayload @preview(toggledBy: "flash-preview") + + """ + Create a deployment status. + """ + createDeploymentStatus(input: CreateDeploymentStatusInput!): CreateDeploymentStatusPayload @preview(toggledBy: "flash-preview") + + """ + Creates an organization as part of an enterprise account. + """ + createEnterpriseOrganization(input: CreateEnterpriseOrganizationInput!): CreateEnterpriseOrganizationPayload + + """ + Creates a new IP allow list entry. + """ + createIpAllowListEntry(input: CreateIpAllowListEntryInput!): CreateIpAllowListEntryPayload + + """ + Creates a new issue. + """ + createIssue(input: CreateIssueInput!): CreateIssuePayload + + """ + Creates a new label. + """ + createLabel(input: CreateLabelInput!): CreateLabelPayload @preview(toggledBy: "bane-preview") + + """ + Creates a new project. + """ + createProject(input: CreateProjectInput!): CreateProjectPayload + + """ + Create a new pull request + """ + createPullRequest(input: CreatePullRequestInput!): CreatePullRequestPayload + + """ + Create a new Git Ref. + """ + createRef(input: CreateRefInput!): CreateRefPayload + + """ + Create a new repository. + """ + createRepository(input: CreateRepositoryInput!): CreateRepositoryPayload + + """ + Creates a new team discussion. + """ + createTeamDiscussion(input: CreateTeamDiscussionInput!): CreateTeamDiscussionPayload + + """ + Creates a new team discussion comment. + """ + createTeamDiscussionComment(input: CreateTeamDiscussionCommentInput!): CreateTeamDiscussionCommentPayload + + """ + Rejects a suggested topic for the repository. + """ + declineTopicSuggestion(input: DeclineTopicSuggestionInput!): DeclineTopicSuggestionPayload + + """ + Delete a branch protection rule + """ + deleteBranchProtectionRule(input: DeleteBranchProtectionRuleInput!): DeleteBranchProtectionRulePayload + + """ + Deletes a deployment. + """ + deleteDeployment(input: DeleteDeploymentInput!): DeleteDeploymentPayload + + """ + Deletes an IP allow list entry. + """ + deleteIpAllowListEntry(input: DeleteIpAllowListEntryInput!): DeleteIpAllowListEntryPayload + + """ + Deletes an Issue object. + """ + deleteIssue(input: DeleteIssueInput!): DeleteIssuePayload + + """ + Deletes an IssueComment object. + """ + deleteIssueComment(input: DeleteIssueCommentInput!): DeleteIssueCommentPayload + + """ + Deletes a label. + """ + deleteLabel(input: DeleteLabelInput!): DeleteLabelPayload @preview(toggledBy: "bane-preview") + + """ + Delete a package version. + """ + deletePackageVersion(input: DeletePackageVersionInput!): DeletePackageVersionPayload @preview(toggledBy: "package-deletes-preview") + + """ + Deletes a project. + """ + deleteProject(input: DeleteProjectInput!): DeleteProjectPayload + + """ + Deletes a project card. + """ + deleteProjectCard(input: DeleteProjectCardInput!): DeleteProjectCardPayload + + """ + Deletes a project column. + """ + deleteProjectColumn(input: DeleteProjectColumnInput!): DeleteProjectColumnPayload + + """ + Deletes a pull request review. + """ + deletePullRequestReview(input: DeletePullRequestReviewInput!): DeletePullRequestReviewPayload + + """ + Deletes a pull request review comment. + """ + deletePullRequestReviewComment(input: DeletePullRequestReviewCommentInput!): DeletePullRequestReviewCommentPayload + + """ + Delete a Git Ref. + """ + deleteRef(input: DeleteRefInput!): DeleteRefPayload + + """ + Deletes a team discussion. + """ + deleteTeamDiscussion(input: DeleteTeamDiscussionInput!): DeleteTeamDiscussionPayload + + """ + Deletes a team discussion comment. + """ + deleteTeamDiscussionComment(input: DeleteTeamDiscussionCommentInput!): DeleteTeamDiscussionCommentPayload + + """ + Deletes a verifiable domain. + """ + deleteVerifiableDomain(input: DeleteVerifiableDomainInput!): DeleteVerifiableDomainPayload + + """ + Dismisses an approved or rejected pull request review. + """ + dismissPullRequestReview(input: DismissPullRequestReviewInput!): DismissPullRequestReviewPayload + + """ + Follow a user. + """ + followUser(input: FollowUserInput!): FollowUserPayload + + """ + Creates a new project by importing columns and a list of issues/PRs. + """ + importProject(input: ImportProjectInput!): ImportProjectPayload @preview(toggledBy: "slothette-preview") + + """ + Invite someone to become an administrator of the enterprise. + """ + inviteEnterpriseAdmin(input: InviteEnterpriseAdminInput!): InviteEnterpriseAdminPayload + + """ + Creates a repository link for a project. + """ + linkRepositoryToProject(input: LinkRepositoryToProjectInput!): LinkRepositoryToProjectPayload + + """ + Lock a lockable object + """ + lockLockable(input: LockLockableInput!): LockLockablePayload + + """ + Mark a pull request file as viewed + """ + markFileAsViewed(input: MarkFileAsViewedInput!): MarkFileAsViewedPayload + + """ + Marks a pull request ready for review. + """ + markPullRequestReadyForReview(input: MarkPullRequestReadyForReviewInput!): MarkPullRequestReadyForReviewPayload + + """ + Merge a head into a branch. + """ + mergeBranch(input: MergeBranchInput!): MergeBranchPayload + + """ + Merge a pull request. + """ + mergePullRequest(input: MergePullRequestInput!): MergePullRequestPayload + + """ + Minimizes a comment on an Issue, Commit, Pull Request, or Gist + """ + minimizeComment(input: MinimizeCommentInput!): MinimizeCommentPayload + + """ + Moves a project card to another place. + """ + moveProjectCard(input: MoveProjectCardInput!): MoveProjectCardPayload + + """ + Moves a project column to another place. + """ + moveProjectColumn(input: MoveProjectColumnInput!): MoveProjectColumnPayload + + """ + Pin an issue to a repository + """ + pinIssue(input: PinIssueInput!): PinIssuePayload @preview(toggledBy: "elektra-preview") + + """ + Regenerates the identity provider recovery codes for an enterprise + """ + regenerateEnterpriseIdentityProviderRecoveryCodes(input: RegenerateEnterpriseIdentityProviderRecoveryCodesInput!): RegenerateEnterpriseIdentityProviderRecoveryCodesPayload + + """ + Regenerates a verifiable domain's verification token. + """ + regenerateVerifiableDomainToken(input: RegenerateVerifiableDomainTokenInput!): RegenerateVerifiableDomainTokenPayload + + """ + Removes assignees from an assignable object. + """ + removeAssigneesFromAssignable(input: RemoveAssigneesFromAssignableInput!): RemoveAssigneesFromAssignablePayload + + """ + Removes an administrator from the enterprise. + """ + removeEnterpriseAdmin(input: RemoveEnterpriseAdminInput!): RemoveEnterpriseAdminPayload + + """ + Removes the identity provider from an enterprise + """ + removeEnterpriseIdentityProvider(input: RemoveEnterpriseIdentityProviderInput!): RemoveEnterpriseIdentityProviderPayload + + """ + Removes an organization from the enterprise + """ + removeEnterpriseOrganization(input: RemoveEnterpriseOrganizationInput!): RemoveEnterpriseOrganizationPayload + + """ + Removes a support entitlement from an enterprise member. + """ + removeEnterpriseSupportEntitlement(input: RemoveEnterpriseSupportEntitlementInput!): RemoveEnterpriseSupportEntitlementPayload + + """ + Removes labels from a Labelable object. + """ + removeLabelsFromLabelable(input: RemoveLabelsFromLabelableInput!): RemoveLabelsFromLabelablePayload + + """ + Removes outside collaborator from all repositories in an organization. + """ + removeOutsideCollaborator(input: RemoveOutsideCollaboratorInput!): RemoveOutsideCollaboratorPayload + + """ + Removes a reaction from a subject. + """ + removeReaction(input: RemoveReactionInput!): RemoveReactionPayload + + """ + Removes a star from a Starrable. + """ + removeStar(input: RemoveStarInput!): RemoveStarPayload + + """ + Reopen a issue. + """ + reopenIssue(input: ReopenIssueInput!): ReopenIssuePayload + + """ + Reopen a pull request. + """ + reopenPullRequest(input: ReopenPullRequestInput!): ReopenPullRequestPayload + + """ + Set review requests on a pull request. + """ + requestReviews(input: RequestReviewsInput!): RequestReviewsPayload + + """ + Rerequests an existing check suite. + """ + rerequestCheckSuite(input: RerequestCheckSuiteInput!): RerequestCheckSuitePayload + + """ + Marks a review thread as resolved. + """ + resolveReviewThread(input: ResolveReviewThreadInput!): ResolveReviewThreadPayload + + """ + Creates or updates the identity provider for an enterprise. + """ + setEnterpriseIdentityProvider(input: SetEnterpriseIdentityProviderInput!): SetEnterpriseIdentityProviderPayload + + """ + Set an organization level interaction limit for an organization's public repositories. + """ + setOrganizationInteractionLimit(input: SetOrganizationInteractionLimitInput!): SetOrganizationInteractionLimitPayload + + """ + Sets an interaction limit setting for a repository. + """ + setRepositoryInteractionLimit(input: SetRepositoryInteractionLimitInput!): SetRepositoryInteractionLimitPayload + + """ + Set a user level interaction limit for an user's public repositories. + """ + setUserInteractionLimit(input: SetUserInteractionLimitInput!): SetUserInteractionLimitPayload + + """ + Submits a pending pull request review. + """ + submitPullRequestReview(input: SubmitPullRequestReviewInput!): SubmitPullRequestReviewPayload + + """ + Transfer an issue to a different repository + """ + transferIssue(input: TransferIssueInput!): TransferIssuePayload + + """ + Unarchives a repository. + """ + unarchiveRepository(input: UnarchiveRepositoryInput!): UnarchiveRepositoryPayload + + """ + Unfollow a user. + """ + unfollowUser(input: UnfollowUserInput!): UnfollowUserPayload + + """ + Deletes a repository link from a project. + """ + unlinkRepositoryFromProject(input: UnlinkRepositoryFromProjectInput!): UnlinkRepositoryFromProjectPayload + + """ + Unlock a lockable object + """ + unlockLockable(input: UnlockLockableInput!): UnlockLockablePayload + + """ + Unmark a pull request file as viewed + """ + unmarkFileAsViewed(input: UnmarkFileAsViewedInput!): UnmarkFileAsViewedPayload + + """ + Unmark an issue as a duplicate of another issue. + """ + unmarkIssueAsDuplicate(input: UnmarkIssueAsDuplicateInput!): UnmarkIssueAsDuplicatePayload + + """ + Unminimizes a comment on an Issue, Commit, Pull Request, or Gist + """ + unminimizeComment(input: UnminimizeCommentInput!): UnminimizeCommentPayload + + """ + Unpin a pinned issue from a repository + """ + unpinIssue(input: UnpinIssueInput!): UnpinIssuePayload @preview(toggledBy: "elektra-preview") + + """ + Marks a review thread as unresolved. + """ + unresolveReviewThread(input: UnresolveReviewThreadInput!): UnresolveReviewThreadPayload + + """ + Create a new branch protection rule + """ + updateBranchProtectionRule(input: UpdateBranchProtectionRuleInput!): UpdateBranchProtectionRulePayload + + """ + Update a check run + """ + updateCheckRun(input: UpdateCheckRunInput!): UpdateCheckRunPayload + + """ + Modifies the settings of an existing check suite + """ + updateCheckSuitePreferences(input: UpdateCheckSuitePreferencesInput!): UpdateCheckSuitePreferencesPayload + + """ + Updates the role of an enterprise administrator. + """ + updateEnterpriseAdministratorRole(input: UpdateEnterpriseAdministratorRoleInput!): UpdateEnterpriseAdministratorRolePayload + + """ + Sets whether private repository forks are enabled for an enterprise. + """ + updateEnterpriseAllowPrivateRepositoryForkingSetting(input: UpdateEnterpriseAllowPrivateRepositoryForkingSettingInput!): UpdateEnterpriseAllowPrivateRepositoryForkingSettingPayload + + """ + Sets the default repository permission for organizations in an enterprise. + """ + updateEnterpriseDefaultRepositoryPermissionSetting(input: UpdateEnterpriseDefaultRepositoryPermissionSettingInput!): UpdateEnterpriseDefaultRepositoryPermissionSettingPayload + + """ + Sets whether organization members with admin permissions on a repository can change repository visibility. + """ + updateEnterpriseMembersCanChangeRepositoryVisibilitySetting(input: UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingInput!): UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingPayload + + """ + Sets the members can create repositories setting for an enterprise. + """ + updateEnterpriseMembersCanCreateRepositoriesSetting(input: UpdateEnterpriseMembersCanCreateRepositoriesSettingInput!): UpdateEnterpriseMembersCanCreateRepositoriesSettingPayload + + """ + Sets the members can delete issues setting for an enterprise. + """ + updateEnterpriseMembersCanDeleteIssuesSetting(input: UpdateEnterpriseMembersCanDeleteIssuesSettingInput!): UpdateEnterpriseMembersCanDeleteIssuesSettingPayload + + """ + Sets the members can delete repositories setting for an enterprise. + """ + updateEnterpriseMembersCanDeleteRepositoriesSetting(input: UpdateEnterpriseMembersCanDeleteRepositoriesSettingInput!): UpdateEnterpriseMembersCanDeleteRepositoriesSettingPayload + + """ + Sets whether members can invite collaborators are enabled for an enterprise. + """ + updateEnterpriseMembersCanInviteCollaboratorsSetting(input: UpdateEnterpriseMembersCanInviteCollaboratorsSettingInput!): UpdateEnterpriseMembersCanInviteCollaboratorsSettingPayload + + """ + Sets whether or not an organization admin can make purchases. + """ + updateEnterpriseMembersCanMakePurchasesSetting(input: UpdateEnterpriseMembersCanMakePurchasesSettingInput!): UpdateEnterpriseMembersCanMakePurchasesSettingPayload + + """ + Sets the members can update protected branches setting for an enterprise. + """ + updateEnterpriseMembersCanUpdateProtectedBranchesSetting(input: UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingInput!): UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingPayload + + """ + Sets the members can view dependency insights for an enterprise. + """ + updateEnterpriseMembersCanViewDependencyInsightsSetting(input: UpdateEnterpriseMembersCanViewDependencyInsightsSettingInput!): UpdateEnterpriseMembersCanViewDependencyInsightsSettingPayload + + """ + Sets whether organization projects are enabled for an enterprise. + """ + updateEnterpriseOrganizationProjectsSetting(input: UpdateEnterpriseOrganizationProjectsSettingInput!): UpdateEnterpriseOrganizationProjectsSettingPayload + + """ + Updates an enterprise's profile. + """ + updateEnterpriseProfile(input: UpdateEnterpriseProfileInput!): UpdateEnterpriseProfilePayload + + """ + Sets whether repository projects are enabled for a enterprise. + """ + updateEnterpriseRepositoryProjectsSetting(input: UpdateEnterpriseRepositoryProjectsSettingInput!): UpdateEnterpriseRepositoryProjectsSettingPayload + + """ + Sets whether team discussions are enabled for an enterprise. + """ + updateEnterpriseTeamDiscussionsSetting(input: UpdateEnterpriseTeamDiscussionsSettingInput!): UpdateEnterpriseTeamDiscussionsSettingPayload + + """ + Sets whether two factor authentication is required for all users in an enterprise. + """ + updateEnterpriseTwoFactorAuthenticationRequiredSetting(input: UpdateEnterpriseTwoFactorAuthenticationRequiredSettingInput!): UpdateEnterpriseTwoFactorAuthenticationRequiredSettingPayload + + """ + Sets whether an IP allow list is enabled on an owner. + """ + updateIpAllowListEnabledSetting(input: UpdateIpAllowListEnabledSettingInput!): UpdateIpAllowListEnabledSettingPayload + + """ + Updates an IP allow list entry. + """ + updateIpAllowListEntry(input: UpdateIpAllowListEntryInput!): UpdateIpAllowListEntryPayload + + """ + Updates an Issue. + """ + updateIssue(input: UpdateIssueInput!): UpdateIssuePayload + + """ + Updates an IssueComment object. + """ + updateIssueComment(input: UpdateIssueCommentInput!): UpdateIssueCommentPayload + + """ + Updates an existing label. + """ + updateLabel(input: UpdateLabelInput!): UpdateLabelPayload @preview(toggledBy: "bane-preview") + + """ + Updates an existing project. + """ + updateProject(input: UpdateProjectInput!): UpdateProjectPayload + + """ + Updates an existing project card. + """ + updateProjectCard(input: UpdateProjectCardInput!): UpdateProjectCardPayload + + """ + Updates an existing project column. + """ + updateProjectColumn(input: UpdateProjectColumnInput!): UpdateProjectColumnPayload + + """ + Update a pull request + """ + updatePullRequest(input: UpdatePullRequestInput!): UpdatePullRequestPayload + + """ + Updates the body of a pull request review. + """ + updatePullRequestReview(input: UpdatePullRequestReviewInput!): UpdatePullRequestReviewPayload + + """ + Updates a pull request review comment. + """ + updatePullRequestReviewComment(input: UpdatePullRequestReviewCommentInput!): UpdatePullRequestReviewCommentPayload + + """ + Update a Git Ref. + """ + updateRef(input: UpdateRefInput!): UpdateRefPayload + + """ + Creates, updates and/or deletes multiple refs in a repository. + + This mutation takes a list of `RefUpdate`s and performs these updates + on the repository. All updates are performed atomically, meaning that + if one of them is rejected, no other ref will be modified. + + `RefUpdate.beforeOid` specifies that the given reference needs to point + to the given value before performing any updates. A value of + `0000000000000000000000000000000000000000` can be used to verify that + the references should not exist. + + `RefUpdate.afterOid` specifies the value that the given reference + will point to after performing all updates. A value of + `0000000000000000000000000000000000000000` can be used to delete a + reference. + + If `RefUpdate.force` is set to `true`, a non-fast-forward updates + for the given reference will be allowed. + """ + updateRefs(input: UpdateRefsInput!): UpdateRefsPayload @preview(toggledBy: "update-refs-preview") + + """ + Update information about a repository. + """ + updateRepository(input: UpdateRepositoryInput!): UpdateRepositoryPayload + + """ + Updates the state for subscribable subjects. + """ + updateSubscription(input: UpdateSubscriptionInput!): UpdateSubscriptionPayload + + """ + Updates a team discussion. + """ + updateTeamDiscussion(input: UpdateTeamDiscussionInput!): UpdateTeamDiscussionPayload + + """ + Updates a discussion comment. + """ + updateTeamDiscussionComment(input: UpdateTeamDiscussionCommentInput!): UpdateTeamDiscussionCommentPayload + + """ + Updates team review assignment. + """ + updateTeamReviewAssignment(input: UpdateTeamReviewAssignmentInput!): UpdateTeamReviewAssignmentPayload @preview(toggledBy: "stone-crop-preview") + + """ + Replaces the repository's topics with the given topics. + """ + updateTopics(input: UpdateTopicsInput!): UpdateTopicsPayload + + """ + Verify that a verifiable domain has the expected DNS record. + """ + verifyVerifiableDomain(input: VerifyVerifiableDomainInput!): VerifyVerifiableDomainPayload +} + +""" +An object with an ID. +""" +interface Node { + """ + ID of the object. + """ + id: ID! +} + +""" +Metadata for an audit entry with action oauth_application.* +""" +interface OauthApplicationAuditEntryData { + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI +} + +""" +Audit log entry for a oauth_application.create event. +""" +type OauthApplicationCreateAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The application URL of the OAuth Application. + """ + applicationUrl: URI + + """ + The callback URL of the OAuth Application. + """ + callbackUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The rate limit of the OAuth Application. + """ + rateLimit: Int + + """ + The state of the OAuth Application. + """ + state: OauthApplicationCreateAuditEntryState + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The state of an OAuth Application when it was created. +""" +enum OauthApplicationCreateAuditEntryState { + """ + The OAuth Application was active and allowed to have OAuth Accesses. + """ + ACTIVE + + """ + The OAuth Application was in the process of being deleted. + """ + PENDING_DELETION + + """ + The OAuth Application was suspended from generating OAuth Accesses due to abuse or security concerns. + """ + SUSPENDED +} + +""" +The corresponding operation type for the action +""" +enum OperationType { + """ + An existing resource was accessed + """ + ACCESS + + """ + A resource performed an authentication event + """ + AUTHENTICATION + + """ + A new resource was created + """ + CREATE + + """ + An existing resource was modified + """ + MODIFY + + """ + An existing resource was removed + """ + REMOVE + + """ + An existing resource was restored + """ + RESTORE + + """ + An existing resource was transferred between multiple resources + """ + TRANSFER +} + +""" +Possible directions in which to order a list of items when provided an `orderBy` argument. +""" +enum OrderDirection { + """ + Specifies an ascending order for a given `orderBy` argument. + """ + ASC + + """ + Specifies a descending order for a given `orderBy` argument. + """ + DESC +} + +""" +Audit log entry for a org.add_billing_manager +""" +type OrgAddBillingManagerAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The email address used to invite a billing manager for the organization. + """ + invitationEmail: String + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.add_member +""" +type OrgAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The permission level of the member added to the organization. + """ + permission: OrgAddMemberAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The permissions available to members on an Organization. +""" +enum OrgAddMemberAuditEntryPermission { + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + Can read and clone repositories. + """ + READ +} + +""" +Audit log entry for a org.block_user +""" +type OrgBlockUserAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The blocked user. + """ + blockedUser: User + + """ + The username of the blocked user. + """ + blockedUserName: String + + """ + The HTTP path for the blocked user. + """ + blockedUserResourcePath: URI + + """ + The HTTP URL for the blocked user. + """ + blockedUserUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.config.disable_collaborators_only event. +""" +type OrgConfigDisableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.config.enable_collaborators_only event. +""" +type OrgConfigEnableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.create event. +""" +type OrgCreateAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The billing plan for the Organization. + """ + billingPlan: OrgCreateAuditEntryBillingPlan + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The billing plans available for organizations. +""" +enum OrgCreateAuditEntryBillingPlan { + """ + Team Plan + """ + BUSINESS + + """ + Enterprise Cloud Plan + """ + BUSINESS_PLUS + + """ + Free Plan + """ + FREE + + """ + Tiered Per Seat Plan + """ + TIERED_PER_SEAT + + """ + Legacy Unlimited Plan + """ + UNLIMITED +} + +""" +Audit log entry for a org.disable_oauth_app_restrictions event. +""" +type OrgDisableOauthAppRestrictionsAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.disable_saml event. +""" +type OrgDisableSamlAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The SAML provider's digest algorithm URL. + """ + digestMethodUrl: URI + id: ID! + + """ + The SAML provider's issuer URL. + """ + issuerUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The SAML provider's signature algorithm URL. + """ + signatureMethodUrl: URI + + """ + The SAML provider's single sign-on URL. + """ + singleSignOnUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.disable_two_factor_requirement event. +""" +type OrgDisableTwoFactorRequirementAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_oauth_app_restrictions event. +""" +type OrgEnableOauthAppRestrictionsAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_saml event. +""" +type OrgEnableSamlAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The SAML provider's digest algorithm URL. + """ + digestMethodUrl: URI + id: ID! + + """ + The SAML provider's issuer URL. + """ + issuerUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The SAML provider's signature algorithm URL. + """ + signatureMethodUrl: URI + + """ + The SAML provider's single sign-on URL. + """ + singleSignOnUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_two_factor_requirement event. +""" +type OrgEnableTwoFactorRequirementAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.invite_member event. +""" +type OrgInviteMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The email address of the organization invitation. + """ + email: String + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The organization invitation. + """ + organizationInvitation: OrganizationInvitation + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.invite_to_business event. +""" +type OrgInviteToBusinessAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_approved event. +""" +type OrgOauthAppAccessApprovedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_denied event. +""" +type OrgOauthAppAccessDeniedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_requested event. +""" +type OrgOauthAppAccessRequestedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The name of the OAuth Application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth Application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth Application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.remove_billing_manager event. +""" +type OrgRemoveBillingManagerAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the billing manager being removed. + """ + reason: OrgRemoveBillingManagerAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The reason a billing manager was removed from an Organization. +""" +enum OrgRemoveBillingManagerAuditEntryReason { + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + SAML SSO enforcement requires an external identity + """ + SAML_SSO_ENFORCEMENT_REQUIRES_EXTERNAL_IDENTITY + + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE +} + +""" +Audit log entry for a org.remove_member event. +""" +type OrgRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The types of membership the member has with the organization. + """ + membershipTypes: [OrgRemoveMemberAuditEntryMembershipType!] + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the member being removed. + """ + reason: OrgRemoveMemberAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The type of membership a user has with an Organization. +""" +enum OrgRemoveMemberAuditEntryMembershipType { + """ + Organization administrators have full access and can change several settings, + including the names of repositories that belong to the Organization and Owners + team membership. In addition, organization admins can delete the organization + and all of its repositories. + """ + ADMIN + + """ + A billing manager is a user who manages the billing settings for the Organization, such as updating payment information. + """ + BILLING_MANAGER + + """ + A direct member is a user that is a member of the Organization. + """ + DIRECT_MEMBER + + """ + An outside collaborator is a person who isn't explicitly a member of the + Organization, but who has Read, Write, or Admin permissions to one or more + repositories in the organization. + """ + OUTSIDE_COLLABORATOR + + """ + An unaffiliated collaborator is a person who is not a member of the + Organization and does not have access to any repositories in the Organization. + """ + UNAFFILIATED +} + +""" +The reason a member was removed from an Organization. +""" +enum OrgRemoveMemberAuditEntryReason { + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + SAML SSO enforcement requires an external identity + """ + SAML_SSO_ENFORCEMENT_REQUIRES_EXTERNAL_IDENTITY + + """ + User was removed from organization during account recovery + """ + TWO_FACTOR_ACCOUNT_RECOVERY + + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE + + """ + User account has been deleted + """ + USER_ACCOUNT_DELETED +} + +""" +Audit log entry for a org.remove_outside_collaborator event. +""" +type OrgRemoveOutsideCollaboratorAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The types of membership the outside collaborator has with the organization. + """ + membershipTypes: [OrgRemoveOutsideCollaboratorAuditEntryMembershipType!] + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the outside collaborator being removed from the Organization. + """ + reason: OrgRemoveOutsideCollaboratorAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The type of membership a user has with an Organization. +""" +enum OrgRemoveOutsideCollaboratorAuditEntryMembershipType { + """ + A billing manager is a user who manages the billing settings for the Organization, such as updating payment information. + """ + BILLING_MANAGER + + """ + An outside collaborator is a person who isn't explicitly a member of the + Organization, but who has Read, Write, or Admin permissions to one or more + repositories in the organization. + """ + OUTSIDE_COLLABORATOR + + """ + An unaffiliated collaborator is a person who is not a member of the + Organization and does not have access to any repositories in the organization. + """ + UNAFFILIATED +} + +""" +The reason an outside collaborator was removed from an Organization. +""" +enum OrgRemoveOutsideCollaboratorAuditEntryReason { + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE +} + +""" +Audit log entry for a org.restore_member event. +""" +type OrgRestoreMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The number of custom email routings for the restored member. + """ + restoredCustomEmailRoutingsCount: Int + + """ + The number of issue assignments for the restored member. + """ + restoredIssueAssignmentsCount: Int + + """ + Restored organization membership objects. + """ + restoredMemberships: [OrgRestoreMemberAuditEntryMembership!] + + """ + The number of restored memberships. + """ + restoredMembershipsCount: Int + + """ + The number of repositories of the restored member. + """ + restoredRepositoriesCount: Int + + """ + The number of starred repositories for the restored member. + """ + restoredRepositoryStarsCount: Int + + """ + The number of watched repositories for the restored member. + """ + restoredRepositoryWatchesCount: Int + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Types of memberships that can be restored for an Organization member. +""" +union OrgRestoreMemberAuditEntryMembership = OrgRestoreMemberMembershipOrganizationAuditEntryData | OrgRestoreMemberMembershipRepositoryAuditEntryData | OrgRestoreMemberMembershipTeamAuditEntryData + +""" +Metadata for an organization membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipOrganizationAuditEntryData implements OrganizationAuditEntryData { + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI +} + +""" +Metadata for a repository membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipRepositoryAuditEntryData implements RepositoryAuditEntryData { + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI +} + +""" +Metadata for a team membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipTeamAuditEntryData implements TeamAuditEntryData { + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI +} + +""" +Audit log entry for a org.unblock_user +""" +type OrgUnblockUserAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The user being unblocked by the organization. + """ + blockedUser: User + + """ + The username of the blocked user. + """ + blockedUserName: String + + """ + The HTTP path for the blocked user. + """ + blockedUserResourcePath: URI + + """ + The HTTP URL for the blocked user. + """ + blockedUserUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.update_default_repository_permission +""" +type OrgUpdateDefaultRepositoryPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new default repository permission level for the organization. + """ + permission: OrgUpdateDefaultRepositoryPermissionAuditEntryPermission + + """ + The former default repository permission level for the organization. + """ + permissionWas: OrgUpdateDefaultRepositoryPermissionAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The default permission a repository can have in an Organization. +""" +enum OrgUpdateDefaultRepositoryPermissionAuditEntryPermission { + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + No default permission value. + """ + NONE + + """ + Can read and clone repositories. + """ + READ + + """ + Can read, clone and push to repositories. + """ + WRITE +} + +""" +Audit log entry for a org.update_member event. +""" +type OrgUpdateMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new member permission level for the organization. + """ + permission: OrgUpdateMemberAuditEntryPermission + + """ + The former member permission level for the organization. + """ + permissionWas: OrgUpdateMemberAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The permissions available to members on an Organization. +""" +enum OrgUpdateMemberAuditEntryPermission { + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + Can read and clone repositories. + """ + READ +} + +""" +Audit log entry for a org.update_member_repository_creation_permission event. +""" +type OrgUpdateMemberRepositoryCreationPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + Can members create repositories in the organization. + """ + canCreateRepositories: Boolean + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The permission for visibility level of repositories for this organization. + """ + visibility: OrgUpdateMemberRepositoryCreationPermissionAuditEntryVisibility +} + +""" +The permissions available for repository creation on an Organization. +""" +enum OrgUpdateMemberRepositoryCreationPermissionAuditEntryVisibility { + """ + All organization members are restricted from creating any repositories. + """ + ALL + + """ + All organization members are restricted from creating internal repositories. + """ + INTERNAL + + """ + All organization members are allowed to create any repositories. + """ + NONE + + """ + All organization members are restricted from creating private repositories. + """ + PRIVATE + + """ + All organization members are restricted from creating private or internal repositories. + """ + PRIVATE_INTERNAL + + """ + All organization members are restricted from creating public repositories. + """ + PUBLIC + + """ + All organization members are restricted from creating public or internal repositories. + """ + PUBLIC_INTERNAL + + """ + All organization members are restricted from creating public or private repositories. + """ + PUBLIC_PRIVATE +} + +""" +Audit log entry for a org.update_member_repository_invitation_permission event. +""" +type OrgUpdateMemberRepositoryInvitationPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + Can outside collaborators be invited to repositories in the organization. + """ + canInviteOutsideCollaboratorsToRepositories: Boolean + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +An account on GitHub, with one or more owners, that has repositories, members and teams. +""" +type Organization implements Actor & MemberStatusable & Node & PackageOwner & ProfileOwner & ProjectOwner & RepositoryOwner & Sponsorable & UniformResourceLocatable { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems( + """ + Filter to only a particular kind of pinnable item. + """ + type: PinnableItemType + ): Boolean! + + """ + Audit log entries of the organization + """ + auditLog( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned audit log entries. + """ + orderBy: AuditLogOrder = {field: CREATED_AT, direction: DESC} + + """ + The query string to filter audit entries + """ + query: String + ): OrganizationAuditEntryConnection! + + """ + A URL pointing to the organization's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The organization's public profile description. + """ + description: String + + """ + The organization's public profile description rendered to HTML. + """ + descriptionHTML: String + + """ + A list of domains owned by the organization. + """ + domains( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter by if the domain is verified. + """ + isVerified: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for verifiable domains returned. + """ + orderBy: VerifiableDomainOrder = {field: DOMAIN, direction: ASC} + ): VerifiableDomainConnection + + """ + The organization's public email. + """ + email: String + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + id: ID! + + """ + The interaction ability settings for this organization. + """ + interactionAbility: RepositoryInteractionAbility + + """ + The setting value for whether the organization has an IP allow list enabled. + """ + ipAllowListEnabledSetting: IpAllowListEnabledSettingValue! + + """ + The IP addresses that are allowed to access resources owned by the organization. + """ + ipAllowListEntries( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for IP allow list entries returned. + """ + orderBy: IpAllowListEntryOrder = {field: ALLOW_LIST_VALUE, direction: ASC} + ): IpAllowListEntryConnection! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Whether the organization has verified its profile email and website, always false on Enterprise. + """ + isVerified: Boolean! + + """ + Showcases a selection of repositories and gists that the profile owner has + either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The organization's public profile location. + """ + location: String + + """ + The organization's login name. + """ + login: String! + + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for user statuses returned from the connection. + """ + orderBy: UserStatusOrder = {field: UPDATED_AT, direction: DESC} + ): UserStatusConnection! + + """ + A list of users who are members of this organization. + """ + membersWithRole( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationMemberConnection! + + """ + The organization's public profile name. + """ + name: String + + """ + The HTTP path creating a new team + """ + newTeamResourcePath: URI! + + """ + The HTTP URL creating a new team + """ + newTeamUrl: URI! + + """ + The billing email for the organization. + """ + organizationBillingEmail: String + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! + + """ + A list of users who have been invited to join this organization. + """ + pendingMembers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinnable items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinned items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + The HTTP path listing organization's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing organization's projects + """ + projectsUrl: URI! + + """ + A list of repositories that the user owns. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they are forks of another repository + """ + isFork: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + Find Repository. + """ + repository( + """ + Name of Repository to find. + """ + name: String! + ): Repository + + """ + When true the organization requires all members, billing managers, and outside + collaborators to enable two-factor authentication. + """ + requiresTwoFactorAuthentication: Boolean + + """ + The HTTP path for this organization. + """ + resourcePath: URI! + + """ + The Organization's SAML identity providers + """ + samlIdentityProvider: OrganizationIdentityProvider + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + This object's sponsorships as the maintainer. + """ + sponsorshipsAsMaintainer( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + This object's sponsorships as the sponsor. + """ + sponsorshipsAsSponsor( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + Find an organization's team by its slug. + """ + team( + """ + The name or slug of the team to find. + """ + slug: String! + ): Team + + """ + A list of teams in this organization. + """ + teams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + If true, filters teams that are mapped to an LDAP Group (Enterprise only) + """ + ldapMapped: Boolean + + """ + Ordering options for teams returned from the connection + """ + orderBy: TeamOrder + + """ + If non-null, filters teams according to privacy + """ + privacy: TeamPrivacy + + """ + If non-null, filters teams with query on team name and team slug + """ + query: String + + """ + If non-null, filters teams according to whether the viewer is an admin or member on team + """ + role: TeamRole + + """ + If true, restrict to only root teams + """ + rootTeamsOnly: Boolean = false + + """ + User logins to filter by + """ + userLogins: [String!] + ): TeamConnection! + + """ + The HTTP path listing organization's teams + """ + teamsResourcePath: URI! + + """ + The HTTP URL listing organization's teams + """ + teamsUrl: URI! + + """ + The organization's Twitter username. + """ + twitterUsername: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this organization. + """ + url: URI! + + """ + Organization is adminable by the viewer. + """ + viewerCanAdminister: Boolean! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Viewer can create repositories on this organization + """ + viewerCanCreateRepositories: Boolean! + + """ + Viewer can create teams on this organization. + """ + viewerCanCreateTeams: Boolean! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + Viewer is an active member of this organization. + """ + viewerIsAMember: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! + + """ + The organization's public profile URL. + """ + websiteUrl: URI +} + +""" +An audit entry in an organization audit log. +""" +union OrganizationAuditEntry = MembersCanDeleteReposClearAuditEntry | MembersCanDeleteReposDisableAuditEntry | MembersCanDeleteReposEnableAuditEntry | OauthApplicationCreateAuditEntry | OrgAddBillingManagerAuditEntry | OrgAddMemberAuditEntry | OrgBlockUserAuditEntry | OrgConfigDisableCollaboratorsOnlyAuditEntry | OrgConfigEnableCollaboratorsOnlyAuditEntry | OrgCreateAuditEntry | OrgDisableOauthAppRestrictionsAuditEntry | OrgDisableSamlAuditEntry | OrgDisableTwoFactorRequirementAuditEntry | OrgEnableOauthAppRestrictionsAuditEntry | OrgEnableSamlAuditEntry | OrgEnableTwoFactorRequirementAuditEntry | OrgInviteMemberAuditEntry | OrgInviteToBusinessAuditEntry | OrgOauthAppAccessApprovedAuditEntry | OrgOauthAppAccessDeniedAuditEntry | OrgOauthAppAccessRequestedAuditEntry | OrgRemoveBillingManagerAuditEntry | OrgRemoveMemberAuditEntry | OrgRemoveOutsideCollaboratorAuditEntry | OrgRestoreMemberAuditEntry | OrgUnblockUserAuditEntry | OrgUpdateDefaultRepositoryPermissionAuditEntry | OrgUpdateMemberAuditEntry | OrgUpdateMemberRepositoryCreationPermissionAuditEntry | OrgUpdateMemberRepositoryInvitationPermissionAuditEntry | PrivateRepositoryForkingDisableAuditEntry | PrivateRepositoryForkingEnableAuditEntry | RepoAccessAuditEntry | RepoAddMemberAuditEntry | RepoAddTopicAuditEntry | RepoArchivedAuditEntry | RepoChangeMergeSettingAuditEntry | RepoConfigDisableAnonymousGitAccessAuditEntry | RepoConfigDisableCollaboratorsOnlyAuditEntry | RepoConfigDisableContributorsOnlyAuditEntry | RepoConfigDisableSockpuppetDisallowedAuditEntry | RepoConfigEnableAnonymousGitAccessAuditEntry | RepoConfigEnableCollaboratorsOnlyAuditEntry | RepoConfigEnableContributorsOnlyAuditEntry | RepoConfigEnableSockpuppetDisallowedAuditEntry | RepoConfigLockAnonymousGitAccessAuditEntry | RepoConfigUnlockAnonymousGitAccessAuditEntry | RepoCreateAuditEntry | RepoDestroyAuditEntry | RepoRemoveMemberAuditEntry | RepoRemoveTopicAuditEntry | RepositoryVisibilityChangeDisableAuditEntry | RepositoryVisibilityChangeEnableAuditEntry | TeamAddMemberAuditEntry | TeamAddRepositoryAuditEntry | TeamChangeParentTeamAuditEntry | TeamRemoveMemberAuditEntry | TeamRemoveRepositoryAuditEntry + +""" +The connection type for OrganizationAuditEntry. +""" +type OrganizationAuditEntryConnection { + """ + A list of edges. + """ + edges: [OrganizationAuditEntryEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationAuditEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Metadata for an audit entry with action org.* +""" +interface OrganizationAuditEntryData { + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI +} + +""" +An edge in a connection. +""" +type OrganizationAuditEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationAuditEntry +} + +""" +The connection type for Organization. +""" +type OrganizationConnection { + """ + A list of edges. + """ + edges: [OrganizationEdge] + + """ + A list of nodes. + """ + nodes: [Organization] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type OrganizationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Organization +} + +""" +An Identity Provider configured to provision SAML and SCIM identities for Organizations +""" +type OrganizationIdentityProvider implements Node { + """ + The digest algorithm used to sign SAML requests for the Identity Provider. + """ + digestMethod: URI + + """ + External Identities provisioned by this Identity Provider + """ + externalIdentities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ExternalIdentityConnection! + id: ID! + + """ + The x509 certificate used by the Identity Provider to sign assertions and responses. + """ + idpCertificate: X509Certificate + + """ + The Issuer Entity ID for the SAML Identity Provider + """ + issuer: String + + """ + Organization this Identity Provider belongs to + """ + organization: Organization + + """ + The signature algorithm used to sign SAML requests for the Identity Provider. + """ + signatureMethod: URI + + """ + The URL endpoint for the Identity Provider's SAML SSO. + """ + ssoUrl: URI +} + +""" +An Invitation for a user to an organization. +""" +type OrganizationInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email address of the user invited to the organization. + """ + email: String + id: ID! + + """ + The type of invitation that was sent (e.g. email, user). + """ + invitationType: OrganizationInvitationType! + + """ + The user who was invited to the organization. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User! + + """ + The organization the invite is for + """ + organization: Organization! + + """ + The user's pending role in the organization (e.g. member, owner). + """ + role: OrganizationInvitationRole! +} + +""" +The connection type for OrganizationInvitation. +""" +type OrganizationInvitationConnection { + """ + A list of edges. + """ + edges: [OrganizationInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type OrganizationInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +The possible organization invitation roles. +""" +enum OrganizationInvitationRole { + """ + The user is invited to be an admin of the organization. + """ + ADMIN + + """ + The user is invited to be a billing manager of the organization. + """ + BILLING_MANAGER + + """ + The user is invited to be a direct member of the organization. + """ + DIRECT_MEMBER + + """ + The user's previous role will be reinstated. + """ + REINSTATE +} + +""" +The possible organization invitation types. +""" +enum OrganizationInvitationType { + """ + The invitation was to an email address. + """ + EMAIL + + """ + The invitation was to an existing user. + """ + USER +} + +""" +The connection type for User. +""" +type OrganizationMemberConnection { + """ + A list of edges. + """ + edges: [OrganizationMemberEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user within an organization. +""" +type OrganizationMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the organization member has two factor enabled or not. Returns null if information is not available to viewer. + """ + hasTwoFactorEnabled: Boolean + + """ + The item at the end of the edge. + """ + node: User + + """ + The role this user has in the organization. + """ + role: OrganizationMemberRole +} + +""" +The possible roles within an organization for its members. +""" +enum OrganizationMemberRole { + """ + The user is an administrator of the organization. + """ + ADMIN + + """ + The user is a member of the organization. + """ + MEMBER +} + +""" +The possible values for the members can create repositories setting on an organization. +""" +enum OrganizationMembersCanCreateRepositoriesSettingValue { + """ + Members will be able to create public and private repositories. + """ + ALL + + """ + Members will not be able to create public or private repositories. + """ + DISABLED + + """ + Members will be able to create only private repositories. + """ + PRIVATE +} + +""" +Ordering options for organization connections. +""" +input OrganizationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order organizations by. + """ + field: OrganizationOrderField! +} + +""" +Properties by which organization connections can be ordered. +""" +enum OrganizationOrderField { + """ + Order organizations by creation time + """ + CREATED_AT + + """ + Order organizations by login + """ + LOGIN +} + +""" +An organization teams hovercard context +""" +type OrganizationTeamsHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Teams in this organization the user is a member of that are relevant + """ + relevantTeams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): TeamConnection! + + """ + The path for the full team list for this user + """ + teamsResourcePath: URI! + + """ + The URL for the full team list for this user + """ + teamsUrl: URI! + + """ + The total number of teams the user is on in the organization + """ + totalTeamCount: Int! +} + +""" +An organization list hovercard context +""" +type OrganizationsHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Organizations this user is a member of that are relevant + """ + relevantOrganizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationConnection! + + """ + The total number of organizations this user is in + """ + totalOrganizationCount: Int! +} + +""" +Information for an uploaded package. +""" +type Package implements Node { + id: ID! + + """ + Find the latest version for the package. + """ + latestVersion: PackageVersion + + """ + Identifies the name of the package. + """ + name: String! + + """ + Identifies the type of the package. + """ + packageType: PackageType! + + """ + The repository this package belongs to. + """ + repository: Repository + + """ + Statistics about package activity. + """ + statistics: PackageStatistics + + """ + Find package version by version string. + """ + version( + """ + The package version. + """ + version: String! + ): PackageVersion + + """ + list of versions for this package + """ + versions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering of the returned packages. + """ + orderBy: PackageVersionOrder = {field: CREATED_AT, direction: DESC} + ): PackageVersionConnection! +} + +""" +The connection type for Package. +""" +type PackageConnection { + """ + A list of edges. + """ + edges: [PackageEdge] + + """ + A list of nodes. + """ + nodes: [Package] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Package +} + +""" +A file in a package version. +""" +type PackageFile implements Node { + id: ID! + + """ + MD5 hash of the file. + """ + md5: String + + """ + Name of the file. + """ + name: String! + + """ + The package version this file belongs to. + """ + packageVersion: PackageVersion + + """ + SHA1 hash of the file. + """ + sha1: String + + """ + SHA256 hash of the file. + """ + sha256: String + + """ + Size of the file in bytes. + """ + size: Int + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + URL to download the asset. + """ + url: URI +} + +""" +The connection type for PackageFile. +""" +type PackageFileConnection { + """ + A list of edges. + """ + edges: [PackageFileEdge] + + """ + A list of nodes. + """ + nodes: [PackageFile] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageFileEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PackageFile +} + +""" +Ways in which lists of package files can be ordered upon return. +""" +input PackageFileOrder { + """ + The direction in which to order package files by the specified field. + """ + direction: OrderDirection + + """ + The field in which to order package files by. + """ + field: PackageFileOrderField +} + +""" +Properties by which package file connections can be ordered. +""" +enum PackageFileOrderField { + """ + Order package files by creation time + """ + CREATED_AT +} + +""" +Ways in which lists of packages can be ordered upon return. +""" +input PackageOrder { + """ + The direction in which to order packages by the specified field. + """ + direction: OrderDirection + + """ + The field in which to order packages by. + """ + field: PackageOrderField +} + +""" +Properties by which package connections can be ordered. +""" +enum PackageOrderField { + """ + Order packages by creation time + """ + CREATED_AT +} + +""" +Represents an owner of a package. +""" +interface PackageOwner { + id: ID! + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! +} + +""" +Represents a object that contains package activity statistics such as downloads. +""" +type PackageStatistics { + """ + Number of times the package was downloaded since it was created. + """ + downloadsTotalCount: Int! +} + +""" +A version tag contains the mapping between a tag name and a version. +""" +type PackageTag implements Node { + id: ID! + + """ + Identifies the tag name of the version. + """ + name: String! + + """ + Version that the tag is associated with. + """ + version: PackageVersion +} + +""" +The possible types of a package. +""" +enum PackageType { + """ + A debian package. + """ + DEBIAN + + """ + A docker image. + """ + DOCKER + + """ + A maven package. + """ + MAVEN + + """ + An npm package. + """ + NPM + + """ + A nuget package. + """ + NUGET + + """ + A python package. + """ + PYPI + + """ + A rubygems package. + """ + RUBYGEMS +} + +""" +Information about a specific package version. +""" +type PackageVersion implements Node { + """ + List of files associated with this package version + """ + files( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering of the returned package files. + """ + orderBy: PackageFileOrder = {field: CREATED_AT, direction: ASC} + ): PackageFileConnection! + id: ID! + + """ + The package associated with this version. + """ + package: Package + + """ + The platform this version was built for. + """ + platform: String + + """ + Whether or not this version is a pre-release. + """ + preRelease: Boolean! + + """ + The README of this package version. + """ + readme: String + + """ + The release associated with this package version. + """ + release: Release + + """ + Statistics about package activity. + """ + statistics: PackageVersionStatistics + + """ + The package version summary. + """ + summary: String + + """ + The version string. + """ + version: String! +} + +""" +The connection type for PackageVersion. +""" +type PackageVersionConnection { + """ + A list of edges. + """ + edges: [PackageVersionEdge] + + """ + A list of nodes. + """ + nodes: [PackageVersion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageVersionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PackageVersion +} + +""" +Ways in which lists of package versions can be ordered upon return. +""" +input PackageVersionOrder { + """ + The direction in which to order package versions by the specified field. + """ + direction: OrderDirection + + """ + The field in which to order package versions by. + """ + field: PackageVersionOrderField +} + +""" +Properties by which package version connections can be ordered. +""" +enum PackageVersionOrderField { + """ + Order package versions by creation time + """ + CREATED_AT +} + +""" +Represents a object that contains package version activity statistics such as downloads. +""" +type PackageVersionStatistics { + """ + Number of times the package was downloaded since it was created. + """ + downloadsTotalCount: Int! +} + +""" +Information about pagination in a connection. +""" +type PageInfo { + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String + + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String +} + +""" +Types that can grant permissions on a repository to a user +""" +union PermissionGranter = Organization | Repository | Team + +""" +A level of permission and source for a user's access to a repository. +""" +type PermissionSource { + """ + The organization the repository belongs to. + """ + organization: Organization! + + """ + The level of access this source has granted to the user. + """ + permission: DefaultRepositoryPermissionField! + + """ + The source of this permission. + """ + source: PermissionGranter! +} + +""" +Autogenerated input type of PinIssue +""" +input PinIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to be pinned + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of PinIssue +""" +type PinIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was pinned + """ + issue: Issue +} + +""" +Types that can be pinned to a profile page. +""" +union PinnableItem = Gist | Repository + +""" +The connection type for PinnableItem. +""" +type PinnableItemConnection { + """ + A list of edges. + """ + edges: [PinnableItemEdge] + + """ + A list of nodes. + """ + nodes: [PinnableItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnableItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnableItem +} + +""" +Represents items that can be pinned to a profile page or dashboard. +""" +enum PinnableItemType { + """ + A gist. + """ + GIST + + """ + An issue. + """ + ISSUE + + """ + An organization. + """ + ORGANIZATION + + """ + A project. + """ + PROJECT + + """ + A pull request. + """ + PULL_REQUEST + + """ + A repository. + """ + REPOSITORY + + """ + A team. + """ + TEAM + + """ + A user. + """ + USER +} + +""" +Represents a 'pinned' event on a given issue or pull request. +""" +type PinnedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +A Pinned Issue is a issue pinned to a repository's index page. +""" +type PinnedIssue implements Node @preview(toggledBy: "elektra-preview") { + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The issue that was pinned. + """ + issue: Issue! + + """ + The actor that pinned this issue. + """ + pinnedBy: Actor! + + """ + The repository that this issue was pinned to. + """ + repository: Repository! +} + +""" +The connection type for PinnedIssue. +""" +type PinnedIssueConnection @preview(toggledBy: "elektra-preview") { + """ + A list of edges. + """ + edges: [PinnedIssueEdge] + + """ + A list of nodes. + """ + nodes: [PinnedIssue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnedIssueEdge @preview(toggledBy: "elektra-preview") { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnedIssue +} + +""" +An ISO-8601 encoded UTC date string with millisecond precision. +""" +scalar PreciseDateTime + +""" +Audit log entry for a private_repository_forking.disable event. +""" +type PrivateRepositoryForkingDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a private_repository_forking.enable event. +""" +type PrivateRepositoryForkingEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +A curatable list of repositories relating to a repository owner, which defaults +to showing the most popular repositories they own. +""" +type ProfileItemShowcase { + """ + Whether or not the owner has pinned any repositories or gists. + """ + hasPinnedItems: Boolean! + + """ + The repositories and gists in the showcase. If the profile owner has any + pinned items, those will be returned. Otherwise, the profile owner's popular + repositories will be returned. + """ + items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PinnableItemConnection! +} + +""" +Represents any entity on GitHub that has a profile page. +""" +interface ProfileOwner { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems( + """ + Filter to only a particular kind of pinnable item. + """ + type: PinnableItemType + ): Boolean! + + """ + The public profile email. + """ + email: String + id: ID! + + """ + Showcases a selection of repositories and gists that the profile owner has + either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The public profile location. + """ + location: String + + """ + The username used to login. + """ + login: String! + + """ + The public profile name. + """ + name: String + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinnable items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinned items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + The public profile website URL. + """ + websiteUrl: URI +} + +""" +Projects manage issues, pull requests and notes within a project owner. +""" +type Project implements Closable & Node & Updatable { + """ + The project's description body. + """ + body: String + + """ + The projects description body rendered to HTML. + """ + bodyHTML: HTML! + + """ + `true` if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + List of columns in the project + """ + columns( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectColumnConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who originally created the project. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project's name. + """ + name: String! + + """ + The project's number. + """ + number: Int! + + """ + The project's owner. Currently limited to repositories, organizations, and users. + """ + owner: ProjectOwner! + + """ + List of pending cards in this project + """ + pendingCards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + Project progress details. + """ + progress: ProjectProgress! + + """ + The HTTP path for this project + """ + resourcePath: URI! + + """ + Whether the project is open or closed. + """ + state: ProjectState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project + """ + url: URI! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! +} + +""" +A card in a project. +""" +type ProjectCard implements Node { + """ + The project column this card is associated under. A card may only belong to one + project column at a time. The column field will be null if the card is created + in a pending state and has yet to be associated with a column. Once cards are + associated with a column, they will not become pending in the future. + """ + column: ProjectColumn + + """ + The card content item + """ + content: ProjectCardItem + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this card + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Whether the card is archived + """ + isArchived: Boolean! + + """ + The card note + """ + note: String + + """ + The project that contains this card. + """ + project: Project! + + """ + The HTTP path for this card + """ + resourcePath: URI! + + """ + The state of ProjectCard + """ + state: ProjectCardState + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this card + """ + url: URI! +} + +""" +The possible archived states of a project card. +""" +enum ProjectCardArchivedState { + """ + A project card that is archived + """ + ARCHIVED + + """ + A project card that is not archived + """ + NOT_ARCHIVED +} + +""" +The connection type for ProjectCard. +""" +type ProjectCardConnection { + """ + A list of edges. + """ + edges: [ProjectCardEdge] + + """ + A list of nodes. + """ + nodes: [ProjectCard] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectCardEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectCard +} + +""" +An issue or PR and its owning repository to be used in a project card. +""" +input ProjectCardImport { + """ + The issue or pull request number. + """ + number: Int! + + """ + Repository name with owner (owner/repository). + """ + repository: String! +} + +""" +Types that can be inside Project Cards. +""" +union ProjectCardItem = Issue | PullRequest + +""" +Various content states of a ProjectCard +""" +enum ProjectCardState { + """ + The card has content only. + """ + CONTENT_ONLY + + """ + The card has a note only. + """ + NOTE_ONLY + + """ + The card is redacted. + """ + REDACTED +} + +""" +A column inside a project. +""" +type ProjectColumn implements Node { + """ + List of cards in the column + """ + cards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The project column's name. + """ + name: String! + + """ + The project that contains this column. + """ + project: Project! + + """ + The semantic purpose of the column + """ + purpose: ProjectColumnPurpose + + """ + The HTTP path for this project column + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project column + """ + url: URI! +} + +""" +The connection type for ProjectColumn. +""" +type ProjectColumnConnection { + """ + A list of edges. + """ + edges: [ProjectColumnEdge] + + """ + A list of nodes. + """ + nodes: [ProjectColumn] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectColumnEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectColumn +} + +""" +A project column and a list of its issues and PRs. +""" +input ProjectColumnImport { + """ + The name of the column. + """ + columnName: String! + + """ + A list of issues and pull requests in the column. + """ + issues: [ProjectCardImport!] + + """ + The position of the column, starting from 0. + """ + position: Int! +} + +""" +The semantic purpose of the column - todo, in progress, or done. +""" +enum ProjectColumnPurpose { + """ + The column contains cards which are complete + """ + DONE + + """ + The column contains cards which are currently being worked on + """ + IN_PROGRESS + + """ + The column contains cards still to be worked on + """ + TODO +} + +""" +A list of projects associated with the owner. +""" +type ProjectConnection { + """ + A list of edges. + """ + edges: [ProjectEdge] + + """ + A list of nodes. + """ + nodes: [Project] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Project +} + +""" +Ways in which lists of projects can be ordered upon return. +""" +input ProjectOrder { + """ + The direction in which to order projects by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order projects by. + """ + field: ProjectOrderField! +} + +""" +Properties by which project connections can be ordered. +""" +enum ProjectOrderField { + """ + Order projects by creation time + """ + CREATED_AT + + """ + Order projects by name + """ + NAME + + """ + Order projects by update time + """ + UPDATED_AT +} + +""" +Represents an owner of a Project. +""" +interface ProjectOwner { + id: ID! + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + The HTTP path listing owners projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing owners projects + """ + projectsUrl: URI! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! +} + +""" +Project progress stats. +""" +type ProjectProgress { + """ + The number of done cards. + """ + doneCount: Int! + + """ + The percentage of done cards. + """ + donePercentage: Float! + + """ + Whether progress tracking is enabled and cards with purpose exist for this project + """ + enabled: Boolean! + + """ + The number of in-progress cards. + """ + inProgressCount: Int! + + """ + The percentage of in-progress cards. + """ + inProgressPercentage: Float! + + """ + The number of to do cards. + """ + todoCount: Int! + + """ + The percentage of to do cards. + """ + todoPercentage: Float! +} + +""" +State of the project; either 'open' or 'closed' +""" +enum ProjectState { + """ + The project is closed. + """ + CLOSED + + """ + The project is open. + """ + OPEN +} + +""" +GitHub-provided templates for Projects +""" +enum ProjectTemplate { + """ + Create a board with v2 triggers to automatically move cards across To do, In progress and Done columns. + """ + AUTOMATED_KANBAN_V2 + + """ + Create a board with triggers to automatically move cards across columns with review automation. + """ + AUTOMATED_REVIEWS_KANBAN + + """ + Create a board with columns for To do, In progress and Done. + """ + BASIC_KANBAN + + """ + Create a board to triage and prioritize bugs with To do, priority, and Done columns. + """ + BUG_TRIAGE +} + +""" +A user's public key. +""" +type PublicKey implements Node { + """ + The last time this authorization was used to perform an action. Values will be null for keys not owned by the user. + """ + accessedAt: DateTime + + """ + Identifies the date and time when the key was created. Keys created before + March 5th, 2014 have inaccurate values. Values will be null for keys not owned by the user. + """ + createdAt: DateTime + + """ + The fingerprint for this PublicKey. + """ + fingerprint: String! + id: ID! + + """ + Whether this PublicKey is read-only or not. Values will be null for keys not owned by the user. + """ + isReadOnly: Boolean + + """ + The public key string. + """ + key: String! + + """ + Identifies the date and time when the key was updated. Keys created before + March 5th, 2014 may have inaccurate values. Values will be null for keys not + owned by the user. + """ + updatedAt: DateTime +} + +""" +The connection type for PublicKey. +""" +type PublicKeyConnection { + """ + A list of edges. + """ + edges: [PublicKeyEdge] + + """ + A list of nodes. + """ + nodes: [PublicKey] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PublicKeyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PublicKey +} + +""" +A repository pull request. +""" +type PullRequest implements Assignable & Closable & Comment & Labelable & Lockable & Node & Reactable & RepositoryNode & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + The number of additions in this pull request. + """ + additions: Int! + + """ + A list of Users assigned to this object. + """ + assignees( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the base Ref associated with the pull request. + """ + baseRef: Ref + + """ + Identifies the name of the base Ref associated with the pull request, even if the ref has been deleted. + """ + baseRefName: String! + + """ + Identifies the oid of the base ref associated with the pull request, even if the ref has been deleted. + """ + baseRefOid: GitObjectID! + + """ + The repository associated with this pull request's base Ref. + """ + baseRepository: Repository + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Whether or not the pull request is rebaseable. + """ + canBeRebased: Boolean! @preview(toggledBy: "merge-info-preview") + + """ + The number of changed files in this pull request. + """ + changedFiles: Int! + + """ + The HTTP path for the checks of this pull request. + """ + checksResourcePath: URI! + + """ + The HTTP URL for the checks of this pull request. + """ + checksUrl: URI! + + """ + `true` if the pull request is closed + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + A list of comments associated with the pull request. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issue comments returned from the connection. + """ + orderBy: IssueCommentOrder + ): IssueCommentConnection! + + """ + A list of commits present in this pull request's head branch not present in the base branch. + """ + commits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestCommitConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The number of deletions in this pull request. + """ + deletions: Int! + + """ + The actor who edited this pull request's body. + """ + editor: Actor + + """ + Lists the files changed within this pull request. + """ + files( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestChangedFileConnection + + """ + Identifies the head Ref associated with the pull request. + """ + headRef: Ref + + """ + Identifies the name of the head Ref associated with the pull request, even if the ref has been deleted. + """ + headRefName: String! + + """ + Identifies the oid of the head ref associated with the pull request, even if the ref has been deleted. + """ + headRefOid: GitObjectID! + + """ + The repository associated with this pull request's head Ref. + """ + headRepository: Repository + + """ + The owner of the repository associated with this pull request's head Ref. + """ + headRepositoryOwner: RepositoryOwner + + """ + The hovercard information for this issue + """ + hovercard( + """ + Whether or not to include notification contexts + """ + includeNotificationContexts: Boolean = true + ): Hovercard! + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The head and base repositories are different. + """ + isCrossRepository: Boolean! + + """ + Identifies if the pull request is a draft. + """ + isDraft: Boolean! + + """ + Is this pull request read by the viewer + """ + isReadByViewer: Boolean + + """ + A list of labels associated with the object. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + ): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + A list of latest reviews per user associated with the pull request. + """ + latestOpinionatedReviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Only return reviews from user who have write access to the repository + """ + writersOnly: Boolean = false + ): PullRequestReviewConnection + + """ + A list of latest reviews per user associated with the pull request that are not also pending review. + """ + latestReviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestReviewConnection + + """ + `true` if the pull request is locked + """ + locked: Boolean! + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean! + + """ + The commit that was created when this pull request was merged. + """ + mergeCommit: Commit + + """ + Detailed information about the current pull request merge state status. + """ + mergeStateStatus: MergeStateStatus! @preview(toggledBy: "merge-info-preview") + + """ + Whether or not the pull request can be merged based on the existence of merge conflicts. + """ + mergeable: MergeableState! + + """ + Whether or not the pull request was merged. + """ + merged: Boolean! + + """ + The date and time that the pull request was merged. + """ + mergedAt: DateTime + + """ + The actor who merged the pull request. + """ + mergedBy: Actor + + """ + Identifies the milestone associated with the pull request. + """ + milestone: Milestone + + """ + Identifies the pull request number. + """ + number: Int! + + """ + A list of Users that are participating in the Pull Request conversation. + """ + participants( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! + + """ + The permalink to the pull request. + """ + permalink: URI! + + """ + The commit that GitHub automatically generated to test if this pull request + could be merged. This field will not return a value if the pull request is + merged, or if the test merge commit is still being generated. See the + `mergeable` field for more details on the mergeability of the pull request. + """ + potentialMergeCommit: Commit + + """ + List of project cards associated with this pull request. + """ + projectCards( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + A list of archived states to filter the cards by + """ + archivedStates: [ProjectCardArchivedState] = [ARCHIVED, NOT_ARCHIVED] + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ProjectCardConnection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this pull request. + """ + resourcePath: URI! + + """ + The HTTP path for reverting this pull request. + """ + revertResourcePath: URI! + + """ + The HTTP URL for reverting this pull request. + """ + revertUrl: URI! + + """ + The current status of this pull request with respect to code review. + """ + reviewDecision: PullRequestReviewDecision + + """ + A list of review requests associated with the pull request. + """ + reviewRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReviewRequestConnection + + """ + The list of all review threads for this pull request. + """ + reviewThreads( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestReviewThreadConnection! + + """ + A list of reviews associated with the pull request. + """ + reviews( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Filter by author of the review. + """ + author: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A list of states to filter the reviews. + """ + states: [PullRequestReviewState!] + ): PullRequestReviewConnection + + """ + Identifies the state of the pull request. + """ + state: PullRequestState! + + """ + A list of reviewer suggestions based on commit history and past review comments. + """ + suggestedReviewers: [SuggestedReviewer]! + + """ + A list of events, comments, commits, etc. associated with the pull request. + """ + timeline( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows filtering timeline events by a `since` timestamp. + """ + since: DateTime + ): PullRequestTimelineConnection! @deprecated(reason: "`timeline` will be removed Use PullRequest.timelineItems instead. Removal on 2020-10-01 UTC.") + + """ + A list of events, comments, commits, etc. associated with the pull request. + """ + timelineItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter timeline items by type. + """ + itemTypes: [PullRequestTimelineItemsItemType!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter timeline items by a `since` timestamp. + """ + since: DateTime + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): PullRequestTimelineItemsConnection! + + """ + Identifies the pull request title. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this pull request. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Whether or not the viewer can apply suggestion. + """ + viewerCanApplySuggestion: Boolean! + + """ + Check if the viewer can restore the deleted head ref. + """ + viewerCanDeleteHeadRef: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + The merge body text for the viewer and method. + """ + viewerMergeBodyText( + """ + The merge method for the message. + """ + mergeType: PullRequestMergeMethod + ): String! + + """ + The merge headline text for the viewer and method. + """ + viewerMergeHeadlineText( + """ + The merge method for the message. + """ + mergeType: PullRequestMergeMethod + ): String! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A file changed in a pull request. +""" +type PullRequestChangedFile { + """ + The number of additions to the file. + """ + additions: Int! + + """ + The number of deletions to the file. + """ + deletions: Int! + + """ + The path of the file. + """ + path: String! + + """ + The state of the file for the viewer. + """ + viewerViewedState: FileViewedState! +} + +""" +The connection type for PullRequestChangedFile. +""" +type PullRequestChangedFileConnection { + """ + A list of edges. + """ + edges: [PullRequestChangedFileEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestChangedFile] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestChangedFileEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestChangedFile +} + +""" +Represents a Git commit part of a pull request. +""" +type PullRequestCommit implements Node & UniformResourceLocatable { + """ + The Git commit object + """ + commit: Commit! + id: ID! + + """ + The pull request this commit belongs to + """ + pullRequest: PullRequest! + + """ + The HTTP path for this pull request commit + """ + resourcePath: URI! + + """ + The HTTP URL for this pull request commit + """ + url: URI! +} + +""" +Represents a commit comment thread part of a pull request. +""" +type PullRequestCommitCommentThread implements Node & RepositoryNode { + """ + The comments that exist in this thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The commit the comments were made on. + """ + commit: Commit! + id: ID! + + """ + The file the comments were made on. + """ + path: String + + """ + The position in the diff for the commit that the comment was made on. + """ + position: Int + + """ + The pull request this commit comment thread belongs to + """ + pullRequest: PullRequest! + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +The connection type for PullRequestCommit. +""" +type PullRequestCommitConnection { + """ + A list of edges. + """ + edges: [PullRequestCommitEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestCommit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestCommitEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestCommit +} + +""" +The connection type for PullRequest. +""" +type PullRequestConnection { + """ + A list of edges. + """ + edges: [PullRequestEdge] + + """ + A list of nodes. + """ + nodes: [PullRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates pull requests opened by a user within one repository. +""" +type PullRequestContributionsByRepository { + """ + The pull request contributions. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestContributionConnection! + + """ + The repository in which the pull requests were opened. + """ + repository: Repository! +} + +""" +An edge in a connection. +""" +type PullRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequest +} + +""" +Represents available types of methods to use when merging a pull request. +""" +enum PullRequestMergeMethod { + """ + Add all commits from the head branch to the base branch with a merge commit. + """ + MERGE + + """ + Add all commits from the head branch onto the base branch individually. + """ + REBASE + + """ + Combine all commits from the head branch into a single commit in the base branch. + """ + SQUASH +} + +""" +Ways in which lists of issues can be ordered upon return. +""" +input PullRequestOrder { + """ + The direction in which to order pull requests by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order pull requests by. + """ + field: PullRequestOrderField! +} + +""" +Properties by which pull_requests connections can be ordered. +""" +enum PullRequestOrderField { + """ + Order pull_requests by creation time + """ + CREATED_AT + + """ + Order pull_requests by update time + """ + UPDATED_AT +} + +""" +A review object for a given pull request. +""" +type PullRequestReview implements Comment & Deletable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Indicates whether the author of this review has push access to the repository. + """ + authorCanPushToRepository: Boolean! + + """ + Identifies the pull request review body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body of this review rendered as plain text. + """ + bodyText: String! + + """ + A list of review comments for the current pull request review. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PullRequestReviewCommentConnection! + + """ + Identifies the commit associated with this pull request review. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + A list of teams that this review was made on behalf of. + """ + onBehalfOf( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): TeamConnection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the pull request associated with this pull request review. + """ + pullRequest: PullRequest! + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this PullRequestReview. + """ + resourcePath: URI! + + """ + Identifies the current state of the pull request review. + """ + state: PullRequestReviewState! + + """ + Identifies when the Pull Request Review was submitted + """ + submittedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this PullRequestReview. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +A review comment associated with a given repository pull request. +""" +type PullRequestReviewComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The comment body of this review comment. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The comment body of this review comment rendered as plain text. + """ + bodyText: String! + + """ + Identifies the commit associated with the comment. + """ + commit: Commit + + """ + Identifies when the comment was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The diff hunk to which the comment applies. + """ + diffHunk: String! + + """ + Identifies when the comment was created in a draft state. + """ + draftedAt: DateTime! + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. + """ + minimizedReason: String + + """ + Identifies the original commit associated with the comment. + """ + originalCommit: Commit + + """ + The original line index in the diff to which the comment applies. + """ + originalPosition: Int! + + """ + Identifies when the comment body is outdated + """ + outdated: Boolean! + + """ + The path to which the comment applies. + """ + path: String! + + """ + The line index in the diff to which the comment applies. + """ + position: Int + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + The pull request associated with this review comment. + """ + pullRequest: PullRequest! + + """ + The pull request review associated with this review comment. + """ + pullRequestReview: PullRequestReview + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The comment this is a reply to. + """ + replyTo: PullRequestReviewComment + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this review comment. + """ + resourcePath: URI! + + """ + Identifies the state of the comment. + """ + state: PullRequestReviewCommentState! + + """ + Identifies when the comment was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this review comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for PullRequestReviewComment. +""" +type PullRequestReviewCommentConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewCommentEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReviewComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestReviewCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReviewComment +} + +""" +The possible states of a pull request review comment. +""" +enum PullRequestReviewCommentState { + """ + A comment that is part of a pending review + """ + PENDING + + """ + A comment that is part of a submitted review + """ + SUBMITTED +} + +""" +The connection type for PullRequestReview. +""" +type PullRequestReviewConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReview] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates pull request reviews made by a user within one repository. +""" +type PullRequestReviewContributionsByRepository { + """ + The pull request review contributions. + """ + contributions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for contributions returned from the connection. + """ + orderBy: ContributionOrder = {direction: DESC} + ): CreatedPullRequestReviewContributionConnection! + + """ + The repository in which the pull request reviews were made. + """ + repository: Repository! +} + +""" +The review status of a pull request. +""" +enum PullRequestReviewDecision { + """ + The pull request has received an approving review. + """ + APPROVED + + """ + Changes have been requested on the pull request. + """ + CHANGES_REQUESTED + + """ + A review is required before the pull request can be merged. + """ + REVIEW_REQUIRED +} + +""" +An edge in a connection. +""" +type PullRequestReviewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReview +} + +""" +The possible events to perform on a pull request review. +""" +enum PullRequestReviewEvent { + """ + Submit feedback and approve merging these changes. + """ + APPROVE + + """ + Submit general feedback without explicit approval. + """ + COMMENT + + """ + Dismiss review so it now longer effects merging. + """ + DISMISS + + """ + Submit feedback that must be addressed before merging. + """ + REQUEST_CHANGES +} + +""" +The possible states of a pull request review. +""" +enum PullRequestReviewState { + """ + A review allowing the pull request to merge. + """ + APPROVED + + """ + A review blocking the pull request from merging. + """ + CHANGES_REQUESTED + + """ + An informational review. + """ + COMMENTED + + """ + A review that has been dismissed. + """ + DISMISSED + + """ + A review that has not yet been submitted. + """ + PENDING +} + +""" +A threaded list of comments for a given pull request. +""" +type PullRequestReviewThread implements Node { + """ + A list of pull request comments associated with the thread. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Skips the first _n_ elements in the list. + """ + skip: Int + ): PullRequestReviewCommentConnection! + + """ + The side of the diff on which this thread was placed. + """ + diffSide: DiffSide! + id: ID! + + """ + Whether or not the thread has been collapsed (outdated or resolved) + """ + isCollapsed: Boolean! + + """ + Indicates whether this thread was outdated by newer changes. + """ + isOutdated: Boolean! + + """ + Whether this thread has been resolved + """ + isResolved: Boolean! + + """ + The line in the file to which this thread refers + """ + line: Int + + """ + The original line in the file to which this thread refers. + """ + originalLine: Int + + """ + The original start line in the file to which this thread refers (multi-line only). + """ + originalStartLine: Int + + """ + Identifies the file path of this thread. + """ + path: String! + + """ + Identifies the pull request associated with this thread. + """ + pullRequest: PullRequest! + + """ + Identifies the repository associated with this thread. + """ + repository: Repository! + + """ + The user who resolved this thread + """ + resolvedBy: User + + """ + The side of the diff that the first line of the thread starts on (multi-line only) + """ + startDiffSide: DiffSide + + """ + The start line in the file to which this thread refers (multi-line only) + """ + startLine: Int + + """ + Indicates whether the current viewer can reply to this thread. + """ + viewerCanReply: Boolean! + + """ + Whether or not the viewer can resolve this thread + """ + viewerCanResolve: Boolean! + + """ + Whether or not the viewer can unresolve this thread + """ + viewerCanUnresolve: Boolean! +} + +""" +Review comment threads for a pull request review. +""" +type PullRequestReviewThreadConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewThreadEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReviewThread] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestReviewThreadEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReviewThread +} + +""" +Represents the latest point in the pull request timeline for which the viewer has seen the pull request's commits. +""" +type PullRequestRevisionMarker { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The last commit the viewer has seen. + """ + lastSeenCommit: Commit! + + """ + The pull request to which the marker belongs. + """ + pullRequest: PullRequest! +} + +""" +The possible states of a pull request. +""" +enum PullRequestState { + """ + A pull request that has been closed without being merged. + """ + CLOSED + + """ + A pull request that has been closed by being merged. + """ + MERGED + + """ + A pull request that is still open. + """ + OPEN +} + +""" +The connection type for PullRequestTimelineItem. +""" +type PullRequestTimelineConnection { + """ + A list of edges. + """ + edges: [PullRequestTimelineItemEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestTimelineItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An item in a pull request timeline +""" +union PullRequestTimelineItem = AssignedEvent | BaseRefDeletedEvent | BaseRefForcePushedEvent | ClosedEvent | Commit | CommitCommentThread | CrossReferencedEvent | DemilestonedEvent | DeployedEvent | DeploymentEnvironmentChangedEvent | HeadRefDeletedEvent | HeadRefForcePushedEvent | HeadRefRestoredEvent | IssueComment | LabeledEvent | LockedEvent | MergedEvent | MilestonedEvent | PullRequestReview | PullRequestReviewComment | PullRequestReviewThread | ReferencedEvent | RenamedTitleEvent | ReopenedEvent | ReviewDismissedEvent | ReviewRequestRemovedEvent | ReviewRequestedEvent | SubscribedEvent | UnassignedEvent | UnlabeledEvent | UnlockedEvent | UnsubscribedEvent | UserBlockedEvent + +""" +An edge in a connection. +""" +type PullRequestTimelineItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestTimelineItem +} + +""" +An item in a pull request timeline +""" +union PullRequestTimelineItems = AddedToProjectEvent | AssignedEvent | AutoMergeDisabledEvent | AutoMergeEnabledEvent | AutoRebaseEnabledEvent | AutoSquashEnabledEvent | AutomaticBaseChangeFailedEvent | AutomaticBaseChangeSucceededEvent | BaseRefChangedEvent | BaseRefDeletedEvent | BaseRefForcePushedEvent | ClosedEvent | CommentDeletedEvent | ConnectedEvent | ConvertToDraftEvent | ConvertedNoteToIssueEvent | CrossReferencedEvent | DemilestonedEvent | DeployedEvent | DeploymentEnvironmentChangedEvent | DisconnectedEvent | HeadRefDeletedEvent | HeadRefForcePushedEvent | HeadRefRestoredEvent | IssueComment | LabeledEvent | LockedEvent | MarkedAsDuplicateEvent | MentionedEvent | MergedEvent | MilestonedEvent | MovedColumnsInProjectEvent | PinnedEvent | PullRequestCommit | PullRequestCommitCommentThread | PullRequestReview | PullRequestReviewThread | PullRequestRevisionMarker | ReadyForReviewEvent | ReferencedEvent | RemovedFromProjectEvent | RenamedTitleEvent | ReopenedEvent | ReviewDismissedEvent | ReviewRequestRemovedEvent | ReviewRequestedEvent | SubscribedEvent | TransferredEvent | UnassignedEvent | UnlabeledEvent | UnlockedEvent | UnmarkedAsDuplicateEvent | UnpinnedEvent | UnsubscribedEvent | UserBlockedEvent + +""" +The connection type for PullRequestTimelineItems. +""" +type PullRequestTimelineItemsConnection { + """ + A list of edges. + """ + edges: [PullRequestTimelineItemsEdge] + + """ + Identifies the count of items after applying `before` and `after` filters. + """ + filteredCount: Int! + + """ + A list of nodes. + """ + nodes: [PullRequestTimelineItems] + + """ + Identifies the count of items after applying `before`/`after` filters and `first`/`last`/`skip` slicing. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the date and time when the timeline was last updated. + """ + updatedAt: DateTime! +} + +""" +An edge in a connection. +""" +type PullRequestTimelineItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestTimelineItems +} + +""" +The possible item types found in a timeline. +""" +enum PullRequestTimelineItemsItemType { + """ + Represents a 'added_to_project' event on a given issue or pull request. + """ + ADDED_TO_PROJECT_EVENT + + """ + Represents an 'assigned' event on any assignable object. + """ + ASSIGNED_EVENT + + """ + Represents a 'automatic_base_change_failed' event on a given pull request. + """ + AUTOMATIC_BASE_CHANGE_FAILED_EVENT + + """ + Represents a 'automatic_base_change_succeeded' event on a given pull request. + """ + AUTOMATIC_BASE_CHANGE_SUCCEEDED_EVENT + + """ + Represents a 'auto_merge_disabled' event on a given pull request. + """ + AUTO_MERGE_DISABLED_EVENT + + """ + Represents a 'auto_merge_enabled' event on a given pull request. + """ + AUTO_MERGE_ENABLED_EVENT + + """ + Represents a 'auto_rebase_enabled' event on a given pull request. + """ + AUTO_REBASE_ENABLED_EVENT + + """ + Represents a 'auto_squash_enabled' event on a given pull request. + """ + AUTO_SQUASH_ENABLED_EVENT + + """ + Represents a 'base_ref_changed' event on a given issue or pull request. + """ + BASE_REF_CHANGED_EVENT + + """ + Represents a 'base_ref_deleted' event on a given pull request. + """ + BASE_REF_DELETED_EVENT + + """ + Represents a 'base_ref_force_pushed' event on a given pull request. + """ + BASE_REF_FORCE_PUSHED_EVENT + + """ + Represents a 'closed' event on any `Closable`. + """ + CLOSED_EVENT + + """ + Represents a 'comment_deleted' event on a given issue or pull request. + """ + COMMENT_DELETED_EVENT + + """ + Represents a 'connected' event on a given issue or pull request. + """ + CONNECTED_EVENT + + """ + Represents a 'converted_note_to_issue' event on a given issue or pull request. + """ + CONVERTED_NOTE_TO_ISSUE_EVENT + + """ + Represents a 'convert_to_draft' event on a given pull request. + """ + CONVERT_TO_DRAFT_EVENT + + """ + Represents a mention made by one issue or pull request to another. + """ + CROSS_REFERENCED_EVENT + + """ + Represents a 'demilestoned' event on a given issue or pull request. + """ + DEMILESTONED_EVENT + + """ + Represents a 'deployed' event on a given pull request. + """ + DEPLOYED_EVENT + + """ + Represents a 'deployment_environment_changed' event on a given pull request. + """ + DEPLOYMENT_ENVIRONMENT_CHANGED_EVENT + + """ + Represents a 'disconnected' event on a given issue or pull request. + """ + DISCONNECTED_EVENT + + """ + Represents a 'head_ref_deleted' event on a given pull request. + """ + HEAD_REF_DELETED_EVENT + + """ + Represents a 'head_ref_force_pushed' event on a given pull request. + """ + HEAD_REF_FORCE_PUSHED_EVENT + + """ + Represents a 'head_ref_restored' event on a given pull request. + """ + HEAD_REF_RESTORED_EVENT + + """ + Represents a comment on an Issue. + """ + ISSUE_COMMENT + + """ + Represents a 'labeled' event on a given issue or pull request. + """ + LABELED_EVENT + + """ + Represents a 'locked' event on a given issue or pull request. + """ + LOCKED_EVENT + + """ + Represents a 'marked_as_duplicate' event on a given issue or pull request. + """ + MARKED_AS_DUPLICATE_EVENT + + """ + Represents a 'mentioned' event on a given issue or pull request. + """ + MENTIONED_EVENT + + """ + Represents a 'merged' event on a given pull request. + """ + MERGED_EVENT + + """ + Represents a 'milestoned' event on a given issue or pull request. + """ + MILESTONED_EVENT + + """ + Represents a 'moved_columns_in_project' event on a given issue or pull request. + """ + MOVED_COLUMNS_IN_PROJECT_EVENT + + """ + Represents a 'pinned' event on a given issue or pull request. + """ + PINNED_EVENT + + """ + Represents a Git commit part of a pull request. + """ + PULL_REQUEST_COMMIT + + """ + Represents a commit comment thread part of a pull request. + """ + PULL_REQUEST_COMMIT_COMMENT_THREAD + + """ + A review object for a given pull request. + """ + PULL_REQUEST_REVIEW + + """ + A threaded list of comments for a given pull request. + """ + PULL_REQUEST_REVIEW_THREAD + + """ + Represents the latest point in the pull request timeline for which the viewer has seen the pull request's commits. + """ + PULL_REQUEST_REVISION_MARKER + + """ + Represents a 'ready_for_review' event on a given pull request. + """ + READY_FOR_REVIEW_EVENT + + """ + Represents a 'referenced' event on a given `ReferencedSubject`. + """ + REFERENCED_EVENT + + """ + Represents a 'removed_from_project' event on a given issue or pull request. + """ + REMOVED_FROM_PROJECT_EVENT + + """ + Represents a 'renamed' event on a given issue or pull request + """ + RENAMED_TITLE_EVENT + + """ + Represents a 'reopened' event on any `Closable`. + """ + REOPENED_EVENT + + """ + Represents a 'review_dismissed' event on a given issue or pull request. + """ + REVIEW_DISMISSED_EVENT + + """ + Represents an 'review_requested' event on a given pull request. + """ + REVIEW_REQUESTED_EVENT + + """ + Represents an 'review_request_removed' event on a given pull request. + """ + REVIEW_REQUEST_REMOVED_EVENT + + """ + Represents a 'subscribed' event on a given `Subscribable`. + """ + SUBSCRIBED_EVENT + + """ + Represents a 'transferred' event on a given issue or pull request. + """ + TRANSFERRED_EVENT + + """ + Represents an 'unassigned' event on any assignable object. + """ + UNASSIGNED_EVENT + + """ + Represents an 'unlabeled' event on a given issue or pull request. + """ + UNLABELED_EVENT + + """ + Represents an 'unlocked' event on a given issue or pull request. + """ + UNLOCKED_EVENT + + """ + Represents an 'unmarked_as_duplicate' event on a given issue or pull request. + """ + UNMARKED_AS_DUPLICATE_EVENT + + """ + Represents an 'unpinned' event on a given issue or pull request. + """ + UNPINNED_EVENT + + """ + Represents an 'unsubscribed' event on a given `Subscribable`. + """ + UNSUBSCRIBED_EVENT + + """ + Represents a 'user_blocked' event on a given user. + """ + USER_BLOCKED_EVENT +} + +""" +The possible target states when updating a pull request. +""" +enum PullRequestUpdateState { + """ + A pull request that has been closed without being merged. + """ + CLOSED + + """ + A pull request that is still open. + """ + OPEN +} + +""" +A Git push. +""" +type Push implements Node { + id: ID! + + """ + The SHA after the push + """ + nextSha: GitObjectID + + """ + The permalink for this push. + """ + permalink: URI! + + """ + The SHA before the push + """ + previousSha: GitObjectID + + """ + The user who pushed + """ + pusher: User! + + """ + The repository that was pushed to + """ + repository: Repository! +} + +""" +A team, user or app who has the ability to push to a protected branch. +""" +type PushAllowance implements Node { + """ + The actor that can push. + """ + actor: PushAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user or team. + """ + branchProtectionRule: BranchProtectionRule + id: ID! +} + +""" +Types that can be an actor. +""" +union PushAllowanceActor = App | Team | User + +""" +The connection type for PushAllowance. +""" +type PushAllowanceConnection { + """ + A list of edges. + """ + edges: [PushAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [PushAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PushAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PushAllowance +} + +""" +The query root of GitHub's GraphQL interface. +""" +type Query { + """ + Look up a code of conduct by its key + """ + codeOfConduct( + """ + The code of conduct's key + """ + key: String! + ): CodeOfConduct + + """ + Look up a code of conduct by its key + """ + codesOfConduct: [CodeOfConduct] + + """ + Look up an enterprise by URL slug. + """ + enterprise( + """ + The enterprise invitation token. + """ + invitationToken: String + + """ + The enterprise URL slug. + """ + slug: String! + ): Enterprise + + """ + Look up a pending enterprise administrator invitation by invitee, enterprise and role. + """ + enterpriseAdministratorInvitation( + """ + The slug of the enterprise the user was invited to join. + """ + enterpriseSlug: String! + + """ + The role for the business member invitation. + """ + role: EnterpriseAdministratorRole! + + """ + The login of the user invited to join the business. + """ + userLogin: String! + ): EnterpriseAdministratorInvitation + + """ + Look up a pending enterprise administrator invitation by invitation token. + """ + enterpriseAdministratorInvitationByToken( + """ + The invitation token sent with the invitation email. + """ + invitationToken: String! + ): EnterpriseAdministratorInvitation + + """ + Look up an open source license by its key + """ + license( + """ + The license's downcased SPDX ID + """ + key: String! + ): License + + """ + Return a list of known open source licenses + """ + licenses: [License]! + + """ + Get alphabetically sorted list of Marketplace categories + """ + marketplaceCategories( + """ + Exclude categories with no listings. + """ + excludeEmpty: Boolean + + """ + Returns top level categories only, excluding any subcategories. + """ + excludeSubcategories: Boolean + + """ + Return only the specified categories. + """ + includeCategories: [String!] + ): [MarketplaceCategory!]! + + """ + Look up a Marketplace category by its slug. + """ + marketplaceCategory( + """ + The URL slug of the category. + """ + slug: String! + + """ + Also check topic aliases for the category slug + """ + useTopicAliases: Boolean + ): MarketplaceCategory + + """ + Look up a single Marketplace listing + """ + marketplaceListing( + """ + Select the listing that matches this slug. It's the short name of the listing used in its URL. + """ + slug: String! + ): MarketplaceListing + + """ + Look up Marketplace listings + """ + marketplaceListings( + """ + Select listings that can be administered by the specified user. + """ + adminId: ID + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Select listings visible to the viewer even if they are not approved. If omitted or + false, only approved listings will be returned. + """ + allStates: Boolean + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Select only listings with the given category. + """ + categorySlug: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Select listings for products owned by the specified organization. + """ + organizationId: ID + + """ + Select only listings where the primary category matches the given category slug. + """ + primaryCategoryOnly: Boolean = false + + """ + Select the listings with these slugs, if they are visible to the viewer. + """ + slugs: [String] + + """ + Also check topic aliases for the category slug + """ + useTopicAliases: Boolean + + """ + Select listings to which user has admin access. If omitted, listings visible to the + viewer are returned. + """ + viewerCanAdmin: Boolean + + """ + Select only listings that offer a free trial. + """ + withFreeTrialsOnly: Boolean = false + ): MarketplaceListingConnection! + + """ + Return information about the GitHub instance + """ + meta: GitHubMetadata! + + """ + Fetches an object given its ID. + """ + node( + """ + ID of the object. + """ + id: ID! + ): Node + + """ + Lookup nodes by a list of IDs. + """ + nodes( + """ + The list of node IDs. + """ + ids: [ID!]! + ): [Node]! + + """ + Lookup a organization by login. + """ + organization( + """ + The organization's login. + """ + login: String! + ): Organization + + """ + The client's rate limit information. + """ + rateLimit( + """ + If true, calculate the cost for the query without evaluating it + """ + dryRun: Boolean = false + ): RateLimit + + """ + Hack to workaround https://github.com/facebook/relay/issues/112 re-exposing the root query object + """ + relay: Query! + + """ + Lookup a given repository by the owner and repository name. + """ + repository( + """ + The name of the repository + """ + name: String! + + """ + The login field of a user or organization + """ + owner: String! + ): Repository + + """ + Lookup a repository owner (ie. either a User or an Organization) by login. + """ + repositoryOwner( + """ + The username to lookup the owner by. + """ + login: String! + ): RepositoryOwner + + """ + Lookup resource by a URL. + """ + resource( + """ + The URL. + """ + url: URI! + ): UniformResourceLocatable + + """ + Perform a search across resources. + """ + search( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The search string to look for. + """ + query: String! + + """ + The types of search items to search within. + """ + type: SearchType! + ): SearchResultItemConnection! + + """ + GitHub Security Advisories + """ + securityAdvisories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Filter advisories by identifier, e.g. GHSA or CVE. + """ + identifier: SecurityAdvisoryIdentifierFilter + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned topics. + """ + orderBy: SecurityAdvisoryOrder = {field: UPDATED_AT, direction: DESC} + + """ + Filter advisories to those published since a time in the past. + """ + publishedSince: DateTime + + """ + Filter advisories to those updated since a time in the past. + """ + updatedSince: DateTime + ): SecurityAdvisoryConnection! + + """ + Fetch a Security Advisory by its GHSA ID + """ + securityAdvisory( + """ + GitHub Security Advisory ID. + """ + ghsaId: String! + ): SecurityAdvisory + + """ + Software Vulnerabilities documented by GitHub Security Advisories + """ + securityVulnerabilities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + An ecosystem to filter vulnerabilities by. + """ + ecosystem: SecurityAdvisoryEcosystem + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned topics. + """ + orderBy: SecurityVulnerabilityOrder = {field: UPDATED_AT, direction: DESC} + + """ + A package name to filter vulnerabilities by. + """ + package: String + + """ + A list of severities to filter vulnerabilities by. + """ + severities: [SecurityAdvisorySeverity!] + ): SecurityVulnerabilityConnection! + + """ + Look up a single Sponsors Listing + """ + sponsorsListing( + """ + Select the Sponsors listing which matches this slug + """ + slug: String! + ): SponsorsListing @deprecated(reason: "`Query.sponsorsListing` will be removed. Use `Sponsorable.sponsorsListing` instead. Removal on 2020-04-01 UTC.") + + """ + Look up a topic by name. + """ + topic( + """ + The topic's name. + """ + name: String! + ): Topic + + """ + Lookup a user by login. + """ + user( + """ + The user's login. + """ + login: String! + ): User + + """ + The currently authenticated user. + """ + viewer: User! +} + +""" +Represents the client's rate limit. +""" +type RateLimit { + """ + The point cost for the current query counting against the rate limit. + """ + cost: Int! + + """ + The maximum number of points the client is permitted to consume in a 60 minute window. + """ + limit: Int! + + """ + The maximum number of nodes this query may return + """ + nodeCount: Int! + + """ + The number of points remaining in the current rate limit window. + """ + remaining: Int! + + """ + The time at which the current rate limit window resets in UTC epoch seconds. + """ + resetAt: DateTime! + + """ + The number of points used in the current rate limit window. + """ + used: Int! +} + +""" +Represents a subject that can be reacted on. +""" +interface Reactable { + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! +} + +""" +The connection type for User. +""" +type ReactingUserConnection { + """ + A list of edges. + """ + edges: [ReactingUserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user that's made a reaction. +""" +type ReactingUserEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: User! + + """ + The moment when the user made the reaction. + """ + reactedAt: DateTime! +} + +""" +An emoji reaction to a particular piece of content. +""" +type Reaction implements Node { + """ + Identifies the emoji reaction. + """ + content: ReactionContent! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The reactable piece of content + """ + reactable: Reactable! + + """ + Identifies the user who created this reaction. + """ + user: User +} + +""" +A list of reactions that have been left on the subject. +""" +type ReactionConnection { + """ + A list of edges. + """ + edges: [ReactionEdge] + + """ + A list of nodes. + """ + nodes: [Reaction] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Whether or not the authenticated user has left a reaction on the subject. + """ + viewerHasReacted: Boolean! +} + +""" +Emojis that can be attached to Issues, Pull Requests and Comments. +""" +enum ReactionContent { + """ + Represents the `:confused:` emoji. + """ + CONFUSED + + """ + Represents the `:eyes:` emoji. + """ + EYES + + """ + Represents the `:heart:` emoji. + """ + HEART + + """ + Represents the `:hooray:` emoji. + """ + HOORAY + + """ + Represents the `:laugh:` emoji. + """ + LAUGH + + """ + Represents the `:rocket:` emoji. + """ + ROCKET + + """ + Represents the `:-1:` emoji. + """ + THUMBS_DOWN + + """ + Represents the `:+1:` emoji. + """ + THUMBS_UP +} + +""" +An edge in a connection. +""" +type ReactionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Reaction +} + +""" +A group of emoji reactions to a particular piece of content. +""" +type ReactionGroup { + """ + Identifies the emoji reaction. + """ + content: ReactionContent! + + """ + Identifies when the reaction was created. + """ + createdAt: DateTime + + """ + The subject that was reacted to. + """ + subject: Reactable! + + """ + Users who have reacted to the reaction subject with the emotion represented by this reaction group + """ + users( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ReactingUserConnection! + + """ + Whether or not the authenticated user has left a reaction on the subject. + """ + viewerHasReacted: Boolean! +} + +""" +Ways in which lists of reactions can be ordered upon return. +""" +input ReactionOrder { + """ + The direction in which to order reactions by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order reactions by. + """ + field: ReactionOrderField! +} + +""" +A list of fields that reactions can be ordered by. +""" +enum ReactionOrderField { + """ + Allows ordering a list of reactions by when they were created. + """ + CREATED_AT +} + +""" +Represents a 'ready_for_review' event on a given pull request. +""" +type ReadyForReviewEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this ready for review event. + """ + resourcePath: URI! + + """ + The HTTP URL for this ready for review event. + """ + url: URI! +} + +""" +Represents a Git reference. +""" +type Ref implements Node { + """ + A list of pull requests with this ref as the head ref. + """ + associatedPullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + Branch protection rules for this ref + """ + branchProtectionRule: BranchProtectionRule + id: ID! + + """ + The ref name. + """ + name: String! + + """ + The ref's prefix, such as `refs/heads/` or `refs/tags/`. + """ + prefix: String! + + """ + Branch protection rules that are viewable by non-admins + """ + refUpdateRule: RefUpdateRule + + """ + The repository the ref belongs to. + """ + repository: Repository! + + """ + The object the ref points to. Returns null when object does not exist. + """ + target: GitObject +} + +""" +The connection type for Ref. +""" +type RefConnection { + """ + A list of edges. + """ + edges: [RefEdge] + + """ + A list of nodes. + """ + nodes: [Ref] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RefEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Ref +} + +""" +Ways in which lists of git refs can be ordered upon return. +""" +input RefOrder { + """ + The direction in which to order refs by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order refs by. + """ + field: RefOrderField! +} + +""" +Properties by which ref connections can be ordered. +""" +enum RefOrderField { + """ + Order refs by their alphanumeric name + """ + ALPHABETICAL + + """ + Order refs by underlying commit date if the ref prefix is refs/tags/ + """ + TAG_COMMIT_DATE +} + +""" +A ref update +""" +input RefUpdate @preview(toggledBy: "update-refs-preview") { + """ + The value this ref should be updated to. + """ + afterOid: GitObjectID! + + """ + The value this ref needs to point to before the update. + """ + beforeOid: GitObjectID + + """ + Force a non fast-forward update. + """ + force: Boolean = false + + """ + The fully qualified name of the ref to be update. For example `refs/heads/branch-name` + """ + name: GitRefname! +} + +""" +A ref update rules for a viewer. +""" +type RefUpdateRule { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean! + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean! + + """ + Identifies the protection rule pattern. + """ + pattern: String! + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String] + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean! + + """ + Are commits required to be signed. + """ + requiresSignatures: Boolean! + + """ + Can the viewer push to the branch + """ + viewerCanPush: Boolean! +} + +""" +Represents a 'referenced' event on a given `ReferencedSubject`. +""" +type ReferencedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the commit associated with the 'referenced' event. + """ + commit: Commit + + """ + Identifies the repository associated with the 'referenced' event. + """ + commitRepository: Repository! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Checks if the commit message itself references the subject. Can be false in the case of a commit comment reference. + """ + isDirectReference: Boolean! + + """ + Object referenced by event. + """ + subject: ReferencedSubject! +} + +""" +Any referencable object +""" +union ReferencedSubject = Issue | PullRequest + +""" +Autogenerated input type of RegenerateEnterpriseIdentityProviderRecoveryCodes +""" +input RegenerateEnterpriseIdentityProviderRecoveryCodesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set an identity provider. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) +} + +""" +Autogenerated return type of RegenerateEnterpriseIdentityProviderRecoveryCodes +""" +type RegenerateEnterpriseIdentityProviderRecoveryCodesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider for the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of RegenerateVerifiableDomainToken +""" +input RegenerateVerifiableDomainTokenInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to regenerate the verification token of. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of RegenerateVerifiableDomainToken +""" +type RegenerateVerifiableDomainTokenPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verification token that was generated. + """ + verificationToken: String +} + +""" +A release contains the content for a release. +""" +type Release implements Node & UniformResourceLocatable { + """ + The author of the release + """ + author: User + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the release. + """ + description: String + + """ + The description of this release rendered to HTML. + """ + descriptionHTML: HTML + id: ID! + + """ + Whether or not the release is a draft + """ + isDraft: Boolean! + + """ + Whether or not the release is the latest releast + """ + isLatest: Boolean! + + """ + Whether or not the release is a prerelease + """ + isPrerelease: Boolean! + + """ + The title of the release. + """ + name: String + + """ + Identifies the date and time when the release was created. + """ + publishedAt: DateTime + + """ + List of releases assets which are dependent on this release. + """ + releaseAssets( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + A list of names to filter the assets by. + """ + name: String + ): ReleaseAssetConnection! + + """ + The HTTP path for this issue + """ + resourcePath: URI! + + """ + A description of the release, rendered to HTML without any links in it. + """ + shortDescriptionHTML( + """ + How many characters to return. + """ + limit: Int = 200 + ): HTML + + """ + The Git tag the release points to + """ + tag: Ref + + """ + The name of the release's Git tag + """ + tagName: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue + """ + url: URI! +} + +""" +A release asset contains the content for a release asset. +""" +type ReleaseAsset implements Node { + """ + The asset's content-type + """ + contentType: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The number of times this asset was downloaded + """ + downloadCount: Int! + + """ + Identifies the URL where you can download the release asset via the browser. + """ + downloadUrl: URI! + id: ID! + + """ + Identifies the title of the release asset. + """ + name: String! + + """ + Release that the asset is associated with + """ + release: Release + + """ + The size (in bytes) of the asset + """ + size: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user that performed the upload + """ + uploadedBy: User! + + """ + Identifies the URL of the release asset. + """ + url: URI! +} + +""" +The connection type for ReleaseAsset. +""" +type ReleaseAssetConnection { + """ + A list of edges. + """ + edges: [ReleaseAssetEdge] + + """ + A list of nodes. + """ + nodes: [ReleaseAsset] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReleaseAssetEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReleaseAsset +} + +""" +The connection type for Release. +""" +type ReleaseConnection { + """ + A list of edges. + """ + edges: [ReleaseEdge] + + """ + A list of nodes. + """ + nodes: [Release] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReleaseEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Release +} + +""" +Ways in which lists of releases can be ordered upon return. +""" +input ReleaseOrder { + """ + The direction in which to order releases by the specified field. + """ + direction: OrderDirection! + + """ + The field in which to order releases by. + """ + field: ReleaseOrderField! +} + +""" +Properties by which release connections can be ordered. +""" +enum ReleaseOrderField { + """ + Order releases by creation time + """ + CREATED_AT + + """ + Order releases alphabetically by name + """ + NAME +} + +""" +Autogenerated input type of RemoveAssigneesFromAssignable +""" +input RemoveAssigneesFromAssignableInput { + """ + The id of the assignable object to remove assignees from. + """ + assignableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Assignable") + + """ + The id of users to remove as assignees. + """ + assigneeIds: [ID!]! @possibleTypes(concreteTypes: ["User"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of RemoveAssigneesFromAssignable +""" +type RemoveAssigneesFromAssignablePayload { + """ + The item that was unassigned. + """ + assignable: Assignable + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of RemoveEnterpriseAdmin +""" +input RemoveEnterpriseAdminInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Enterprise ID from which to remove the administrator. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of the user to remove as an administrator. + """ + login: String! +} + +""" +Autogenerated return type of RemoveEnterpriseAdmin +""" +type RemoveEnterpriseAdminPayload { + """ + The user who was removed as an administrator. + """ + admin: User + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + A message confirming the result of removing an administrator. + """ + message: String + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseIdentityProvider +""" +input RemoveEnterpriseIdentityProviderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which to remove the identity provider. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) +} + +""" +Autogenerated return type of RemoveEnterpriseIdentityProvider +""" +type RemoveEnterpriseIdentityProviderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider that was removed from the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of RemoveEnterpriseOrganization +""" +input RemoveEnterpriseOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which the organization should be removed. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The ID of the organization to remove from the enterprise. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of RemoveEnterpriseOrganization +""" +type RemoveEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + The organization that was removed from the enterprise. + """ + organization: Organization + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseSupportEntitlement +""" +input RemoveEnterpriseSupportEntitlementInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a member who will lose the support entitlement. + """ + login: String! +} + +""" +Autogenerated return type of RemoveEnterpriseSupportEntitlement +""" +type RemoveEnterpriseSupportEntitlementPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of removing the support entitlement. + """ + message: String +} + +""" +Autogenerated input type of RemoveLabelsFromLabelable +""" +input RemoveLabelsFromLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ids of labels to remove. + """ + labelIds: [ID!]! @possibleTypes(concreteTypes: ["Label"]) + + """ + The id of the Labelable to remove labels from. + """ + labelableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Labelable") +} + +""" +Autogenerated return type of RemoveLabelsFromLabelable +""" +type RemoveLabelsFromLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Labelable the labels were removed from. + """ + labelable: Labelable +} + +""" +Autogenerated input type of RemoveOutsideCollaborator +""" +input RemoveOutsideCollaboratorInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization to remove the outside collaborator from. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) + + """ + The ID of the outside collaborator to remove. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of RemoveOutsideCollaborator +""" +type RemoveOutsideCollaboratorPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was removed as an outside collaborator. + """ + removedUser: User +} + +""" +Autogenerated input type of RemoveReaction +""" +input RemoveReactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the emoji reaction to remove. + """ + content: ReactionContent! + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["CommitComment", "Issue", "IssueComment", "PullRequest", "PullRequestReview", "PullRequestReviewComment", "TeamDiscussion", "TeamDiscussionComment"], abstractType: "Reactable") +} + +""" +Autogenerated return type of RemoveReaction +""" +type RemoveReactionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reaction object. + """ + reaction: Reaction + + """ + The reactable subject. + """ + subject: Reactable +} + +""" +Autogenerated input type of RemoveStar +""" +input RemoveStarInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Starrable ID to unstar. + """ + starrableId: ID! @possibleTypes(concreteTypes: ["Gist", "Repository", "Topic"], abstractType: "Starrable") +} + +""" +Autogenerated return type of RemoveStar +""" +type RemoveStarPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The starrable. + """ + starrable: Starrable +} + +""" +Represents a 'removed_from_project' event on a given issue or pull request. +""" +type RemovedFromProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Project referenced by event. + """ + project: Project @preview(toggledBy: "starfox-preview") + + """ + Column name referenced by this project event. + """ + projectColumnName: String! @preview(toggledBy: "starfox-preview") +} + +""" +Represents a 'renamed' event on a given issue or pull request +""" +type RenamedTitleEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the current title of the issue or pull request. + """ + currentTitle: String! + id: ID! + + """ + Identifies the previous title of the issue or pull request. + """ + previousTitle: String! + + """ + Subject that was renamed. + """ + subject: RenamedTitleSubject! +} + +""" +An object which has a renamable title +""" +union RenamedTitleSubject = Issue | PullRequest + +""" +Autogenerated input type of ReopenIssue +""" +input ReopenIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to be opened. + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of ReopenIssue +""" +type ReopenIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was opened. + """ + issue: Issue +} + +""" +Autogenerated input type of ReopenPullRequest +""" +input ReopenPullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be reopened. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of ReopenPullRequest +""" +type ReopenPullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was reopened. + """ + pullRequest: PullRequest +} + +""" +Represents a 'reopened' event on any `Closable`. +""" +type ReopenedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Object that was reopened. + """ + closable: Closable! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! +} + +""" +Audit log entry for a repo.access event. +""" +type RepoAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoAccessAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoAccessAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.add_member event. +""" +type RepoAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoAddMemberAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoAddMemberAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.add_topic event. +""" +type RepoAddTopicAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TopicAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.archived event. +""" +type RepoArchivedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoArchivedAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoArchivedAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.change_merge_setting event. +""" +type RepoChangeMergeSettingAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the change was to enable (true) or disable (false) the merge type + """ + isEnabled: Boolean + + """ + The merge method affected by the change + """ + mergeType: RepoChangeMergeSettingAuditEntryMergeType + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The merge options available for pull requests to this repository. +""" +enum RepoChangeMergeSettingAuditEntryMergeType { + """ + The pull request is added to the base branch in a merge commit. + """ + MERGE + + """ + Commits from the pull request are added onto the base branch individually without a merge commit. + """ + REBASE + + """ + The pull request's commits are squashed into a single commit before they are merged to the base branch. + """ + SQUASH +} + +""" +Audit log entry for a repo.config.disable_anonymous_git_access event. +""" +type RepoConfigDisableAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_collaborators_only event. +""" +type RepoConfigDisableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_contributors_only event. +""" +type RepoConfigDisableContributorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_sockpuppet_disallowed event. +""" +type RepoConfigDisableSockpuppetDisallowedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_anonymous_git_access event. +""" +type RepoConfigEnableAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_collaborators_only event. +""" +type RepoConfigEnableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_contributors_only event. +""" +type RepoConfigEnableContributorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_sockpuppet_disallowed event. +""" +type RepoConfigEnableSockpuppetDisallowedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.lock_anonymous_git_access event. +""" +type RepoConfigLockAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.unlock_anonymous_git_access event. +""" +type RepoConfigUnlockAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.create event. +""" +type RepoCreateAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The name of the parent repository for this forked repository. + """ + forkParentName: String + + """ + The name of the root repository for this network. + """ + forkSourceName: String + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoCreateAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoCreateAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.destroy event. +""" +type RepoDestroyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoDestroyAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoDestroyAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.remove_member event. +""" +type RepoRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoRemoveMemberAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoRemoveMemberAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.remove_topic event. +""" +type RepoRemoveTopicAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TopicAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The reasons a piece of content can be reported or minimized. +""" +enum ReportedContentClassifiers { + """ + An abusive or harassing piece of content + """ + ABUSE + + """ + A duplicated piece of content + """ + DUPLICATE + + """ + An irrelevant piece of content + """ + OFF_TOPIC + + """ + An outdated piece of content + """ + OUTDATED + + """ + The content has been resolved + """ + RESOLVED + + """ + A spammy piece of content + """ + SPAM +} + +""" +A repository contains the content for a project. +""" +type Repository implements Node & PackageOwner & ProjectOwner & RepositoryInfo & Starrable & Subscribable & UniformResourceLocatable { + """ + A list of users that can be assigned to issues in this repository. + """ + assignableUsers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters users with query on user name and login + """ + query: String + ): UserConnection! + + """ + A list of branch protection rules for this repository. + """ + branchProtectionRules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): BranchProtectionRuleConnection! + + """ + Returns the code of conduct for this repository + """ + codeOfConduct: CodeOfConduct + + """ + A list of collaborators associated with the repository. + """ + collaborators( + """ + Collaborators affiliation level with a repository. + """ + affiliation: CollaboratorAffiliation + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters users with query on user name and login + """ + query: String + ): RepositoryCollaboratorConnection + + """ + A list of commit comments associated with the repository. + """ + commitComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + Returns a list of contact links associated to the repository + """ + contactLinks: [RepositoryContactLink!] + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Ref associated with the repository's default branch. + """ + defaultBranchRef: Ref + + """ + Whether or not branches are automatically deleted when merged in this repository. + """ + deleteBranchOnMerge: Boolean! + + """ + A list of dependency manifests contained in the repository + """ + dependencyGraphManifests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Cursor to paginate dependencies + """ + dependenciesAfter: String + + """ + Number of dependencies to fetch + """ + dependenciesFirst: Int + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Flag to scope to only manifests with dependencies + """ + withDependencies: Boolean + ): DependencyGraphManifestConnection @preview(toggledBy: "hawkgirl-preview") + + """ + A list of deploy keys that are on this repository. + """ + deployKeys( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): DeployKeyConnection! + + """ + Deployments associated with the repository + """ + deployments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Environments to list deployments for + """ + environments: [String!] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for deployments returned from the connection. + """ + orderBy: DeploymentOrder = {field: CREATED_AT, direction: ASC} + ): DeploymentConnection! + + """ + The description of the repository. + """ + description: String + + """ + The description of the repository rendered to HTML. + """ + descriptionHTML: HTML! + + """ + The number of kilobytes this repository occupies on disk. + """ + diskUsage: Int + + """ + Returns how many forks there are of this repository in the whole network. + """ + forkCount: Int! + + """ + A list of direct forked repositories. + """ + forks( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + The funding links for this repository + """ + fundingLinks: [FundingLink!]! + + """ + Indicates if the repository has issues feature enabled. + """ + hasIssuesEnabled: Boolean! + + """ + Indicates if the repository has the Projects feature enabled. + """ + hasProjectsEnabled: Boolean! + + """ + Indicates if the repository has wiki feature enabled. + """ + hasWikiEnabled: Boolean! + + """ + The repository's URL. + """ + homepageUrl: URI + id: ID! + + """ + The interaction ability settings for this repository. + """ + interactionAbility: RepositoryInteractionAbility + + """ + Indicates if the repository is unmaintained. + """ + isArchived: Boolean! + + """ + Returns true if blank issue creation is allowed + """ + isBlankIssuesEnabled: Boolean! + + """ + Returns whether or not this repository disabled. + """ + isDisabled: Boolean! + + """ + Returns whether or not this repository is empty. + """ + isEmpty: Boolean! + + """ + Identifies if the repository is a fork. + """ + isFork: Boolean! + + """ + Indicates if a repository is either owned by an organization, or is a private fork of an organization repository. + """ + isInOrganization: Boolean! + + """ + Indicates if the repository has been locked or not. + """ + isLocked: Boolean! + + """ + Identifies if the repository is a mirror. + """ + isMirror: Boolean! + + """ + Identifies if the repository is private. + """ + isPrivate: Boolean! + + """ + Returns true if this repository has a security policy + """ + isSecurityPolicyEnabled: Boolean + + """ + Identifies if the repository is a template that can be used to generate new repositories. + """ + isTemplate: Boolean! + + """ + Is this repository a user configuration repository? + """ + isUserConfigurationRepository: Boolean! + + """ + Returns a single issue from the current repository by number. + """ + issue( + """ + The number for the issue to be returned. + """ + number: Int! + ): Issue + + """ + Returns a single issue-like object from the current repository by number. + """ + issueOrPullRequest( + """ + The number for the issue to be returned. + """ + number: Int! + ): IssueOrPullRequest + + """ + Returns a list of issue templates associated to the repository + """ + issueTemplates: [IssueTemplate!] + + """ + A list of issues that have been opened in the repository. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Returns a single label by name + """ + label( + """ + Label name + """ + name: String! + ): Label + + """ + A list of labels associated with the repository. + """ + labels( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for labels returned from the connection. + """ + orderBy: LabelOrder = {field: CREATED_AT, direction: ASC} + + """ + If provided, searches labels by name and description. + """ + query: String + ): LabelConnection + + """ + A list containing a breakdown of the language composition of the repository. + """ + languages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: LanguageOrder + ): LanguageConnection + + """ + Get the latest release for the repository if one exists. + """ + latestRelease: Release + + """ + The license associated with the repository + """ + licenseInfo: License + + """ + The reason the repository has been locked. + """ + lockReason: RepositoryLockReason + + """ + A list of Users that can be mentioned in the context of the repository. + """ + mentionableUsers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filters users with query on user name and login + """ + query: String + ): UserConnection! + + """ + Whether or not PRs are merged with a merge commit on this repository. + """ + mergeCommitAllowed: Boolean! + + """ + Returns a single milestone from the current repository by number. + """ + milestone( + """ + The number for the milestone to be returned. + """ + number: Int! + ): Milestone + + """ + A list of milestones associated with the repository. + """ + milestones( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for milestones. + """ + orderBy: MilestoneOrder + + """ + Filters milestones with a query on the title + """ + query: String + + """ + Filter by the state of the milestones. + """ + states: [MilestoneState!] + ): MilestoneConnection + + """ + The repository's original mirror URL. + """ + mirrorUrl: URI + + """ + The name of the repository. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! + + """ + A Git object in the repository + """ + object( + """ + A Git revision expression suitable for rev-parse + """ + expression: String + + """ + The Git object ID + """ + oid: GitObjectID + ): GitObject + + """ + The image used to represent this repository in Open Graph data. + """ + openGraphImageUrl: URI! + + """ + The User owner of the repository. + """ + owner: RepositoryOwner! + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! + + """ + The repository parent, if this is a fork. + """ + parent: Repository + + """ + A list of pinned issues for this repository. + """ + pinnedIssues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PinnedIssueConnection @preview(toggledBy: "elektra-preview") + + """ + The primary language of the repository's code. + """ + primaryLanguage: Language + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + The HTTP path listing the repository's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing the repository's projects + """ + projectsUrl: URI! + + """ + Returns a single pull request from the current repository by number. + """ + pullRequest( + """ + The number for the pull request to be returned. + """ + number: Int! + ): PullRequest + + """ + A list of pull requests that have been opened in the repository. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + Identifies when the repository was last pushed to. + """ + pushedAt: DateTime + + """ + Whether or not rebase-merging is enabled on this repository. + """ + rebaseMergeAllowed: Boolean! + + """ + Fetch a given ref from the repository + """ + ref( + """ + The ref to retrieve. Fully qualified matches are checked in order + (`refs/heads/master`) before falling back onto checks for short name matches (`master`). + """ + qualifiedName: String! + ): Ref + + """ + Fetch a list of refs from the repository + """ + refs( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + DEPRECATED: use orderBy. The ordering direction. + """ + direction: OrderDirection + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for refs returned from the connection. + """ + orderBy: RefOrder + + """ + Filters refs with query on name + """ + query: String + + """ + A ref name prefix like `refs/heads/`, `refs/tags/`, etc. + """ + refPrefix: String! + ): RefConnection + + """ + Lookup a single release given various criteria. + """ + release( + """ + The name of the Tag the Release was created from + """ + tagName: String! + ): Release + + """ + List of releases which are dependent on this repository. + """ + releases( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: ReleaseOrder + ): ReleaseConnection! + + """ + A list of applied repository-topic associations for this repository. + """ + repositoryTopics( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): RepositoryTopicConnection! + + """ + The HTTP path for this repository + """ + resourcePath: URI! + + """ + The security policy URL. + """ + securityPolicyUrl: URI + + """ + A description of the repository, rendered to HTML without any links in it. + """ + shortDescriptionHTML( + """ + How many characters to return. + """ + limit: Int = 200 + ): HTML! + + """ + Whether or not squash-merging is enabled on this repository. + """ + squashMergeAllowed: Boolean! + + """ + The SSH URL to clone this repository + """ + sshUrl: GitSSHRemote! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Returns a list of all submodules in this repository parsed from the + .gitmodules file as of the default branch's HEAD commit. + """ + submodules( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): SubmoduleConnection! + + """ + Temporary authentication token for cloning this repository. + """ + tempCloneToken: String + + """ + The repository from which this repository was generated, if any. + """ + templateRepository: Repository + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this repository + """ + url: URI! + + """ + Whether this repository has a custom image to use with Open Graph as opposed to being represented by the owner's avatar. + """ + usesCustomOpenGraphImage: Boolean! + + """ + Indicates whether the viewer has admin permissions on this repository. + """ + viewerCanAdminister: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Indicates whether the viewer can update the topics of this repository. + """ + viewerCanUpdateTopics: Boolean! + + """ + The last commit email for the viewer. + """ + viewerDefaultCommitEmail: String + + """ + The last used merge method by the viewer or the default for the repository. + """ + viewerDefaultMergeMethod: PullRequestMergeMethod! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! + + """ + The users permission level on the repository. Will return null if authenticated as an GitHub App. + """ + viewerPermission: RepositoryPermission + + """ + A list of emails this viewer can commit with. + """ + viewerPossibleCommitEmails: [String!] + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + A list of vulnerability alerts that are on this repository. + """ + vulnerabilityAlerts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): RepositoryVulnerabilityAlertConnection + + """ + A list of users watching the repository. + """ + watchers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserConnection! +} + +""" +The affiliation of a user to a repository +""" +enum RepositoryAffiliation { + """ + Repositories that the user has been added to as a collaborator. + """ + COLLABORATOR + + """ + Repositories that the user has access to through being a member of an + organization. This includes every repository on every team that the user is on. + """ + ORGANIZATION_MEMBER + + """ + Repositories that are owned by the authenticated user. + """ + OWNER +} + +""" +Metadata for an audit entry with action repo.* +""" +interface RepositoryAuditEntryData { + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI +} + +""" +The connection type for User. +""" +type RepositoryCollaboratorConnection { + """ + A list of edges. + """ + edges: [RepositoryCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user who is a collaborator of a repository. +""" +type RepositoryCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: User! + + """ + The permission the user has on the repository. + """ + permission: RepositoryPermission! + + """ + A list of sources for the user's access to the repository. + """ + permissionSources: [PermissionSource!] +} + +""" +A list of repositories owned by the subject. +""" +type RepositoryConnection { + """ + A list of edges. + """ + edges: [RepositoryEdge] + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total size in kilobytes of all repositories in the connection. + """ + totalDiskUsage: Int! +} + +""" +A repository contact link. +""" +type RepositoryContactLink { + """ + The contact link purpose. + """ + about: String! + + """ + The contact link name. + """ + name: String! + + """ + The contact link URL. + """ + url: URI! +} + +""" +The reason a repository is listed as 'contributed'. +""" +enum RepositoryContributionType { + """ + Created a commit + """ + COMMIT + + """ + Created an issue + """ + ISSUE + + """ + Created a pull request + """ + PULL_REQUEST + + """ + Reviewed a pull request + """ + PULL_REQUEST_REVIEW + + """ + Created the repository + """ + REPOSITORY +} + +""" +An edge in a connection. +""" +type RepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Repository +} + +""" +A subset of repository info. +""" +interface RepositoryInfo { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the repository. + """ + description: String + + """ + The description of the repository rendered to HTML. + """ + descriptionHTML: HTML! + + """ + Returns how many forks there are of this repository in the whole network. + """ + forkCount: Int! + + """ + Indicates if the repository has issues feature enabled. + """ + hasIssuesEnabled: Boolean! + + """ + Indicates if the repository has the Projects feature enabled. + """ + hasProjectsEnabled: Boolean! + + """ + Indicates if the repository has wiki feature enabled. + """ + hasWikiEnabled: Boolean! + + """ + The repository's URL. + """ + homepageUrl: URI + + """ + Indicates if the repository is unmaintained. + """ + isArchived: Boolean! + + """ + Identifies if the repository is a fork. + """ + isFork: Boolean! + + """ + Indicates if a repository is either owned by an organization, or is a private fork of an organization repository. + """ + isInOrganization: Boolean! + + """ + Indicates if the repository has been locked or not. + """ + isLocked: Boolean! + + """ + Identifies if the repository is a mirror. + """ + isMirror: Boolean! + + """ + Identifies if the repository is private. + """ + isPrivate: Boolean! + + """ + Identifies if the repository is a template that can be used to generate new repositories. + """ + isTemplate: Boolean! + + """ + The license associated with the repository + """ + licenseInfo: License + + """ + The reason the repository has been locked. + """ + lockReason: RepositoryLockReason + + """ + The repository's original mirror URL. + """ + mirrorUrl: URI + + """ + The name of the repository. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! + + """ + The image used to represent this repository in Open Graph data. + """ + openGraphImageUrl: URI! + + """ + The User owner of the repository. + """ + owner: RepositoryOwner! + + """ + Identifies when the repository was last pushed to. + """ + pushedAt: DateTime + + """ + The HTTP path for this repository + """ + resourcePath: URI! + + """ + A description of the repository, rendered to HTML without any links in it. + """ + shortDescriptionHTML( + """ + How many characters to return. + """ + limit: Int = 200 + ): HTML! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this repository + """ + url: URI! + + """ + Whether this repository has a custom image to use with Open Graph as opposed to being represented by the owner's avatar. + """ + usesCustomOpenGraphImage: Boolean! +} + +""" +Repository interaction limit that applies to this object. +""" +type RepositoryInteractionAbility { + """ + The time the currently active limit expires. + """ + expiresAt: DateTime + + """ + The current limit that is enabled on this object. + """ + limit: RepositoryInteractionLimit! + + """ + The origin of the currently active interaction limit. + """ + origin: RepositoryInteractionLimitOrigin! +} + +""" +A repository interaction limit. +""" +enum RepositoryInteractionLimit { + """ + Users that are not collaborators will not be able to interact with the repository. + """ + COLLABORATORS_ONLY + + """ + Users that have not previously committed to a repository’s default branch will be unable to interact with the repository. + """ + CONTRIBUTORS_ONLY + + """ + Users that have recently created their account will be unable to interact with the repository. + """ + EXISTING_USERS + + """ + No interaction limits are enabled. + """ + NO_LIMIT +} + +""" +The length for a repository interaction limit to be enabled for. +""" +enum RepositoryInteractionLimitExpiry { + """ + The interaction limit will expire after 1 day. + """ + ONE_DAY + + """ + The interaction limit will expire after 1 month. + """ + ONE_MONTH + + """ + The interaction limit will expire after 1 week. + """ + ONE_WEEK + + """ + The interaction limit will expire after 6 months. + """ + SIX_MONTHS + + """ + The interaction limit will expire after 3 days. + """ + THREE_DAYS +} + +""" +Indicates where an interaction limit is configured. +""" +enum RepositoryInteractionLimitOrigin { + """ + A limit that is configured at the organization level. + """ + ORGANIZATION + + """ + A limit that is configured at the repository level. + """ + REPOSITORY + + """ + A limit that is configured at the user-wide level. + """ + USER +} + +""" +An invitation for a user to be added to a repository. +""" +type RepositoryInvitation implements Node { + """ + The email address that received the invitation. + """ + email: String + id: ID! + + """ + The user who received the invitation. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User! + + """ + The permalink for this repository invitation. + """ + permalink: URI! + + """ + The permission granted on this repository by this invitation. + """ + permission: RepositoryPermission! + + """ + The Repository the user is invited to. + """ + repository: RepositoryInfo +} + +""" +The connection type for RepositoryInvitation. +""" +type RepositoryInvitationConnection { + """ + A list of edges. + """ + edges: [RepositoryInvitationEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryInvitation +} + +""" +Ordering options for repository invitation connections. +""" +input RepositoryInvitationOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repository invitations by. + """ + field: RepositoryInvitationOrderField! +} + +""" +Properties by which repository invitation connections can be ordered. +""" +enum RepositoryInvitationOrderField { + """ + Order repository invitations by creation time + """ + CREATED_AT + + """ + Order repository invitations by invitee login + """ + INVITEE_LOGIN @deprecated(reason: "`INVITEE_LOGIN` is no longer a valid field value. Repository invitations can now be associated with an email, not only an invitee. Removal on 2020-10-01 UTC.") +} + +""" +The possible reasons a given repository could be in a locked state. +""" +enum RepositoryLockReason { + """ + The repository is locked due to a billing related reason. + """ + BILLING + + """ + The repository is locked due to a migration. + """ + MIGRATING + + """ + The repository is locked due to a move. + """ + MOVING + + """ + The repository is locked due to a rename. + """ + RENAME +} + +""" +Represents a object that belongs to a repository. +""" +interface RepositoryNode { + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +Ordering options for repository connections +""" +input RepositoryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repositories by. + """ + field: RepositoryOrderField! +} + +""" +Properties by which repository connections can be ordered. +""" +enum RepositoryOrderField { + """ + Order repositories by creation time + """ + CREATED_AT + + """ + Order repositories by name + """ + NAME + + """ + Order repositories by push time + """ + PUSHED_AT + + """ + Order repositories by number of stargazers + """ + STARGAZERS + + """ + Order repositories by update time + """ + UPDATED_AT +} + +""" +Represents an owner of a Repository. +""" +interface RepositoryOwner { + """ + A URL pointing to the owner's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + id: ID! + + """ + The username used to login. + """ + login: String! + + """ + A list of repositories that the user owns. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they are forks of another repository + """ + isFork: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + Find Repository. + """ + repository( + """ + Name of Repository to find. + """ + name: String! + ): Repository + + """ + The HTTP URL for the owner. + """ + resourcePath: URI! + + """ + The HTTP URL for the owner. + """ + url: URI! +} + +""" +The access level to a repository +""" +enum RepositoryPermission { + """ + Can read, clone, and push to this repository. Can also manage issues, pull + requests, and repository settings, including adding collaborators + """ + ADMIN + + """ + Can read, clone, and push to this repository. They can also manage issues, pull requests, and some repository settings + """ + MAINTAIN + + """ + Can read and clone this repository. Can also open and comment on issues and pull requests + """ + READ + + """ + Can read and clone this repository. Can also manage issues and pull requests + """ + TRIAGE + + """ + Can read, clone, and push to this repository. Can also manage issues and pull requests + """ + WRITE +} + +""" +The privacy of a repository +""" +enum RepositoryPrivacy { + """ + Private + """ + PRIVATE + + """ + Public + """ + PUBLIC +} + +""" +A repository-topic connects a repository to a topic. +""" +type RepositoryTopic implements Node & UniformResourceLocatable { + id: ID! + + """ + The HTTP path for this repository-topic. + """ + resourcePath: URI! + + """ + The topic. + """ + topic: Topic! + + """ + The HTTP URL for this repository-topic. + """ + url: URI! +} + +""" +The connection type for RepositoryTopic. +""" +type RepositoryTopicConnection { + """ + A list of edges. + """ + edges: [RepositoryTopicEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryTopic] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryTopicEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryTopic +} + +""" +The repository's visibility level. +""" +enum RepositoryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repository_visibility_change.disable event. +""" +type RepositoryVisibilityChangeDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repository_visibility_change.enable event. +""" +type RepositoryVisibilityChangeEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +A alert for a repository with an affected vulnerability. +""" +type RepositoryVulnerabilityAlert implements Node & RepositoryNode { + """ + When was the alert created? + """ + createdAt: DateTime! + + """ + The reason the alert was dismissed + """ + dismissReason: String + + """ + When was the alert dismissed? + """ + dismissedAt: DateTime + + """ + The user who dismissed the alert + """ + dismisser: User + id: ID! + + """ + The associated repository + """ + repository: Repository! + + """ + The associated security advisory + """ + securityAdvisory: SecurityAdvisory + + """ + The associated security vulnerability + """ + securityVulnerability: SecurityVulnerability + + """ + The vulnerable manifest filename + """ + vulnerableManifestFilename: String! + + """ + The vulnerable manifest path + """ + vulnerableManifestPath: String! + + """ + The vulnerable requirements + """ + vulnerableRequirements: String +} + +""" +The connection type for RepositoryVulnerabilityAlert. +""" +type RepositoryVulnerabilityAlertConnection { + """ + A list of edges. + """ + edges: [RepositoryVulnerabilityAlertEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryVulnerabilityAlert] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryVulnerabilityAlertEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryVulnerabilityAlert +} + +""" +Autogenerated input type of RequestReviews +""" +input RequestReviewsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request to modify. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Node IDs of the team to request. + """ + teamIds: [ID!] @possibleTypes(concreteTypes: ["Team"]) + + """ + Add users to the set rather than replace. + """ + union: Boolean + + """ + The Node IDs of the user to request. + """ + userIds: [ID!] @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of RequestReviews +""" +type RequestReviewsPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is getting requests. + """ + pullRequest: PullRequest + + """ + The edge from the pull request to the requested reviewers. + """ + requestedReviewersEdge: UserEdge +} + +""" +The possible states that can be requested when creating a check run. +""" +enum RequestableCheckStatusState { + """ + The check suite or run has been completed. + """ + COMPLETED + + """ + The check suite or run is in progress. + """ + IN_PROGRESS + + """ + The check suite or run has been queued. + """ + QUEUED + + """ + The check suite or run is in waiting state. + """ + WAITING +} + +""" +Types that can be requested reviewers. +""" +union RequestedReviewer = Mannequin | Team | User + +""" +Autogenerated input type of RerequestCheckSuite +""" +input RerequestCheckSuiteInput { + """ + The Node ID of the check suite. + """ + checkSuiteId: ID! @possibleTypes(concreteTypes: ["CheckSuite"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of RerequestCheckSuite +""" +type RerequestCheckSuitePayload { + """ + The requested check suite. + """ + checkSuite: CheckSuite + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of ResolveReviewThread +""" +input ResolveReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the thread to resolve + """ + threadId: ID! @possibleTypes(concreteTypes: ["PullRequestReviewThread"]) +} + +""" +Autogenerated return type of ResolveReviewThread +""" +type ResolveReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The thread to resolve. + """ + thread: PullRequestReviewThread +} + +""" +Represents a private contribution a user made on GitHub. +""" +type RestrictedContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A team or user who has the ability to dismiss a review on a protected branch. +""" +type ReviewDismissalAllowance implements Node { + """ + The actor that can dismiss. + """ + actor: ReviewDismissalAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user or team. + """ + branchProtectionRule: BranchProtectionRule + id: ID! +} + +""" +Types that can be an actor. +""" +union ReviewDismissalAllowanceActor = Team | User + +""" +The connection type for ReviewDismissalAllowance. +""" +type ReviewDismissalAllowanceConnection { + """ + A list of edges. + """ + edges: [ReviewDismissalAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [ReviewDismissalAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReviewDismissalAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReviewDismissalAllowance +} + +""" +Represents a 'review_dismissed' event on a given issue or pull request. +""" +type ReviewDismissedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Identifies the optional message associated with the 'review_dismissed' event. + """ + dismissalMessage: String + + """ + Identifies the optional message associated with the event, rendered to HTML. + """ + dismissalMessageHTML: String + id: ID! + + """ + Identifies the previous state of the review with the 'review_dismissed' event. + """ + previousReviewState: PullRequestReviewState! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the commit which caused the review to become stale. + """ + pullRequestCommit: PullRequestCommit + + """ + The HTTP path for this review dismissed event. + """ + resourcePath: URI! + + """ + Identifies the review associated with the 'review_dismissed' event. + """ + review: PullRequestReview + + """ + The HTTP URL for this review dismissed event. + """ + url: URI! +} + +""" +A request for a user to review a pull request. +""" +type ReviewRequest implements Node { + """ + Whether this request was created for a code owner + """ + asCodeOwner: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + Identifies the pull request associated with this review request. + """ + pullRequest: PullRequest! + + """ + The reviewer that is requested. + """ + requestedReviewer: RequestedReviewer +} + +""" +The connection type for ReviewRequest. +""" +type ReviewRequestConnection { + """ + A list of edges. + """ + edges: [ReviewRequestEdge] + + """ + A list of nodes. + """ + nodes: [ReviewRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReviewRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReviewRequest +} + +""" +Represents an 'review_request_removed' event on a given pull request. +""" +type ReviewRequestRemovedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the reviewer whose review request was removed. + """ + requestedReviewer: RequestedReviewer +} + +""" +Represents an 'review_requested' event on a given pull request. +""" +type ReviewRequestedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the reviewer whose review was requested. + """ + requestedReviewer: RequestedReviewer +} + +""" +A hovercard context with a message describing the current code review state of the pull +request. +""" +type ReviewStatusHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + The current status of the pull request with respect to code review. + """ + reviewDecision: PullRequestReviewDecision +} + +""" +The possible digest algorithms used to sign SAML requests for an identity provider. +""" +enum SamlDigestAlgorithm { + """ + SHA1 + """ + SHA1 + + """ + SHA256 + """ + SHA256 + + """ + SHA384 + """ + SHA384 + + """ + SHA512 + """ + SHA512 +} + +""" +The possible signature algorithms used to sign SAML requests for a Identity Provider. +""" +enum SamlSignatureAlgorithm { + """ + RSA-SHA1 + """ + RSA_SHA1 + + """ + RSA-SHA256 + """ + RSA_SHA256 + + """ + RSA-SHA384 + """ + RSA_SHA384 + + """ + RSA-SHA512 + """ + RSA_SHA512 +} + +""" +A Saved Reply is text a user can use to reply quickly. +""" +type SavedReply implements Node { + """ + The body of the saved reply. + """ + body: String! + + """ + The saved reply body rendered to HTML. + """ + bodyHTML: HTML! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + id: ID! + + """ + The title of the saved reply. + """ + title: String! + + """ + The user that saved this reply. + """ + user: Actor +} + +""" +The connection type for SavedReply. +""" +type SavedReplyConnection { + """ + A list of edges. + """ + edges: [SavedReplyEdge] + + """ + A list of nodes. + """ + nodes: [SavedReply] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SavedReplyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SavedReply +} + +""" +Ordering options for saved reply connections. +""" +input SavedReplyOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order saved replies by. + """ + field: SavedReplyOrderField! +} + +""" +Properties by which saved reply connections can be ordered. +""" +enum SavedReplyOrderField { + """ + Order saved reply by when they were updated. + """ + UPDATED_AT +} + +""" +The results of a search. +""" +union SearchResultItem = App | Issue | MarketplaceListing | Organization | PullRequest | Repository | User + +""" +A list of results that matched against a search query. +""" +type SearchResultItemConnection { + """ + The number of pieces of code that matched the search query. + """ + codeCount: Int! + + """ + A list of edges. + """ + edges: [SearchResultItemEdge] + + """ + The number of issues that matched the search query. + """ + issueCount: Int! + + """ + A list of nodes. + """ + nodes: [SearchResultItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + The number of repositories that matched the search query. + """ + repositoryCount: Int! + + """ + The number of users that matched the search query. + """ + userCount: Int! + + """ + The number of wiki pages that matched the search query. + """ + wikiCount: Int! +} + +""" +An edge in a connection. +""" +type SearchResultItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SearchResultItem + + """ + Text matches on the result found. + """ + textMatches: [TextMatch] +} + +""" +Represents the individual results of a search. +""" +enum SearchType { + """ + Returns results matching issues in repositories. + """ + ISSUE + + """ + Returns results matching repositories. + """ + REPOSITORY + + """ + Returns results matching users and organizations on GitHub. + """ + USER +} + +""" +A GitHub Security Advisory +""" +type SecurityAdvisory implements Node { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + This is a long plaintext description of the advisory + """ + description: String! + + """ + The GitHub Security Advisory ID + """ + ghsaId: String! + id: ID! + + """ + A list of identifiers for this advisory + """ + identifiers: [SecurityAdvisoryIdentifier!]! + + """ + The organization that originated the advisory + """ + origin: String! + + """ + The permalink for the advisory + """ + permalink: URI + + """ + When the advisory was published + """ + publishedAt: DateTime! + + """ + A list of references for this advisory + """ + references: [SecurityAdvisoryReference!]! + + """ + The severity of the advisory + """ + severity: SecurityAdvisorySeverity! + + """ + A short plaintext summary of the advisory + """ + summary: String! + + """ + When the advisory was last updated + """ + updatedAt: DateTime! + + """ + Vulnerabilities associated with this Advisory + """ + vulnerabilities( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + An ecosystem to filter vulnerabilities by. + """ + ecosystem: SecurityAdvisoryEcosystem + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for the returned topics. + """ + orderBy: SecurityVulnerabilityOrder = {field: UPDATED_AT, direction: DESC} + + """ + A package name to filter vulnerabilities by. + """ + package: String + + """ + A list of severities to filter vulnerabilities by. + """ + severities: [SecurityAdvisorySeverity!] + ): SecurityVulnerabilityConnection! + + """ + When the advisory was withdrawn, if it has been withdrawn + """ + withdrawnAt: DateTime +} + +""" +The connection type for SecurityAdvisory. +""" +type SecurityAdvisoryConnection { + """ + A list of edges. + """ + edges: [SecurityAdvisoryEdge] + + """ + A list of nodes. + """ + nodes: [SecurityAdvisory] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The possible ecosystems of a security vulnerability's package. +""" +enum SecurityAdvisoryEcosystem { + """ + PHP packages hosted at packagist.org + """ + COMPOSER + + """ + Java artifacts hosted at the Maven central repository + """ + MAVEN + + """ + JavaScript packages hosted at npmjs.com + """ + NPM + + """ + .NET packages hosted at the NuGet Gallery + """ + NUGET + + """ + Python packages hosted at PyPI.org + """ + PIP + + """ + Ruby gems hosted at RubyGems.org + """ + RUBYGEMS +} + +""" +An edge in a connection. +""" +type SecurityAdvisoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SecurityAdvisory +} + +""" +A GitHub Security Advisory Identifier +""" +type SecurityAdvisoryIdentifier { + """ + The identifier type, e.g. GHSA, CVE + """ + type: String! + + """ + The identifier + """ + value: String! +} + +""" +An advisory identifier to filter results on. +""" +input SecurityAdvisoryIdentifierFilter { + """ + The identifier type. + """ + type: SecurityAdvisoryIdentifierType! + + """ + The identifier string. Supports exact or partial matching. + """ + value: String! +} + +""" +Identifier formats available for advisories. +""" +enum SecurityAdvisoryIdentifierType { + """ + Common Vulnerabilities and Exposures Identifier. + """ + CVE + + """ + GitHub Security Advisory ID. + """ + GHSA +} + +""" +Ordering options for security advisory connections +""" +input SecurityAdvisoryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order security advisories by. + """ + field: SecurityAdvisoryOrderField! +} + +""" +Properties by which security advisory connections can be ordered. +""" +enum SecurityAdvisoryOrderField { + """ + Order advisories by publication time + """ + PUBLISHED_AT + + """ + Order advisories by update time + """ + UPDATED_AT +} + +""" +An individual package +""" +type SecurityAdvisoryPackage { + """ + The ecosystem the package belongs to, e.g. RUBYGEMS, NPM + """ + ecosystem: SecurityAdvisoryEcosystem! + + """ + The package name + """ + name: String! +} + +""" +An individual package version +""" +type SecurityAdvisoryPackageVersion { + """ + The package name or version + """ + identifier: String! +} + +""" +A GitHub Security Advisory Reference +""" +type SecurityAdvisoryReference { + """ + A publicly accessible reference + """ + url: URI! +} + +""" +Severity of the vulnerability. +""" +enum SecurityAdvisorySeverity { + """ + Critical. + """ + CRITICAL + + """ + High. + """ + HIGH + + """ + Low. + """ + LOW + + """ + Moderate. + """ + MODERATE +} + +""" +An individual vulnerability within an Advisory +""" +type SecurityVulnerability { + """ + The Advisory associated with this Vulnerability + """ + advisory: SecurityAdvisory! + + """ + The first version containing a fix for the vulnerability + """ + firstPatchedVersion: SecurityAdvisoryPackageVersion + + """ + A description of the vulnerable package + """ + package: SecurityAdvisoryPackage! + + """ + The severity of the vulnerability within this package + """ + severity: SecurityAdvisorySeverity! + + """ + When the vulnerability was last updated + """ + updatedAt: DateTime! + + """ + A string that describes the vulnerable package versions. + This string follows a basic syntax with a few forms. + + `= 0.2.0` denotes a single vulnerable version. + + `<= 1.0.8` denotes a version range up to and including the specified version + + `< 0.1.11` denotes a version range up to, but excluding, the specified version + + `>= 4.3.0, < 4.3.5` denotes a version range with a known minimum and maximum version. + + `>= 0.0.1` denotes a version range with a known minimum, but no known maximum + """ + vulnerableVersionRange: String! +} + +""" +The connection type for SecurityVulnerability. +""" +type SecurityVulnerabilityConnection { + """ + A list of edges. + """ + edges: [SecurityVulnerabilityEdge] + + """ + A list of nodes. + """ + nodes: [SecurityVulnerability] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SecurityVulnerabilityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SecurityVulnerability +} + +""" +Ordering options for security vulnerability connections +""" +input SecurityVulnerabilityOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order security vulnerabilities by. + """ + field: SecurityVulnerabilityOrderField! +} + +""" +Properties by which security vulnerability connections can be ordered. +""" +enum SecurityVulnerabilityOrderField { + """ + Order vulnerability by update time + """ + UPDATED_AT +} + +""" +Autogenerated input type of SetEnterpriseIdentityProvider +""" +input SetEnterpriseIdentityProviderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The digest algorithm used to sign SAML requests for the identity provider. + """ + digestMethod: SamlDigestAlgorithm! + + """ + The ID of the enterprise on which to set an identity provider. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The x509 certificate used by the identity provider to sign assertions and responses. + """ + idpCertificate: String! + + """ + The Issuer Entity ID for the SAML identity provider + """ + issuer: String + + """ + The signature algorithm used to sign SAML requests for the identity provider. + """ + signatureMethod: SamlSignatureAlgorithm! + + """ + The URL endpoint for the identity provider's SAML SSO. + """ + ssoUrl: URI! +} + +""" +Autogenerated return type of SetEnterpriseIdentityProvider +""" +type SetEnterpriseIdentityProviderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider for the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of SetOrganizationInteractionLimit +""" +input SetOrganizationInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + The ID of the organization to set a limit for. + """ + organizationId: ID! @possibleTypes(concreteTypes: ["Organization"]) +} + +""" +Autogenerated return type of SetOrganizationInteractionLimit +""" +type SetOrganizationInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that the interaction limit was set for. + """ + organization: Organization +} + +""" +Autogenerated input type of SetRepositoryInteractionLimit +""" +input SetRepositoryInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + The ID of the repository to set a limit for. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of SetRepositoryInteractionLimit +""" +type SetRepositoryInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that the interaction limit was set for. + """ + repository: Repository +} + +""" +Autogenerated input type of SetUserInteractionLimit +""" +input SetUserInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + The ID of the user to set a limit for. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of SetUserInteractionLimit +""" +type SetUserInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that the interaction limit was set for. + """ + user: User +} + +""" +Represents an S/MIME signature on a Commit or Tag. +""" +type SmimeSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Entities that can sponsor others via GitHub Sponsors +""" +union Sponsor = Organization | User + +""" +Entities that can be sponsored through GitHub Sponsors +""" +interface Sponsorable { + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + This object's sponsorships as the maintainer. + """ + sponsorshipsAsMaintainer( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + This object's sponsorships as the sponsor. + """ + sponsorshipsAsSponsor( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! +} + +""" +A GitHub Sponsors listing. +""" +type SponsorsListing implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The full description of the listing. + """ + fullDescription: String! + + """ + The full description of the listing rendered to HTML. + """ + fullDescriptionHTML: HTML! + id: ID! + + """ + The listing's full name. + """ + name: String! + + """ + The short description of the listing. + """ + shortDescription: String! + + """ + The short name of the listing. + """ + slug: String! + + """ + The published tiers for this GitHub Sponsors listing. + """ + tiers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for Sponsors tiers returned from the connection. + """ + orderBy: SponsorsTierOrder = {field: MONTHLY_PRICE_IN_CENTS, direction: ASC} + ): SponsorsTierConnection +} + +""" +A GitHub Sponsors tier associated with a GitHub Sponsors listing. +""" +type SponsorsTier implements Node { + """ + SponsorsTier information only visible to users that can administer the associated Sponsors listing. + """ + adminInfo: SponsorsTierAdminInfo + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the tier. + """ + description: String! + + """ + The tier description rendered to HTML + """ + descriptionHTML: HTML! + id: ID! + + """ + How much this tier costs per month in cents. + """ + monthlyPriceInCents: Int! + + """ + How much this tier costs per month in dollars. + """ + monthlyPriceInDollars: Int! + + """ + The name of the tier. + """ + name: String! + + """ + The sponsors listing that this tier belongs to. + """ + sponsorsListing: SponsorsListing! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +SponsorsTier information only visible to users that can administer the associated Sponsors listing. +""" +type SponsorsTierAdminInfo { + """ + The sponsorships associated with this tier. + """ + sponsorships( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! +} + +""" +The connection type for SponsorsTier. +""" +type SponsorsTierConnection { + """ + A list of edges. + """ + edges: [SponsorsTierEdge] + + """ + A list of nodes. + """ + nodes: [SponsorsTier] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorsTierEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorsTier +} + +""" +Ordering options for Sponsors tiers connections. +""" +input SponsorsTierOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order tiers by. + """ + field: SponsorsTierOrderField! +} + +""" +Properties by which Sponsors tiers connections can be ordered. +""" +enum SponsorsTierOrderField { + """ + Order tiers by creation time. + """ + CREATED_AT + + """ + Order tiers by their monthly price in cents + """ + MONTHLY_PRICE_IN_CENTS +} + +""" +A sponsorship relationship between a sponsor and a maintainer +""" +type Sponsorship implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The entity that is being sponsored + """ + maintainer: User! @deprecated(reason: "`Sponsorship.maintainer` will be removed. Use `Sponsorship.sponsorable` instead. Removal on 2020-04-01 UTC.") + + """ + The privacy level for this sponsorship. + """ + privacyLevel: SponsorshipPrivacy! + + """ + The user that is sponsoring. Returns null if the sponsorship is private or if sponsor is not a user. + """ + sponsor: User @deprecated(reason: "`Sponsorship.sponsor` will be removed. Use `Sponsorship.sponsorEntity` instead. Removal on 2020-10-01 UTC.") + + """ + The user or organization that is sponsoring, if you have permission to view them. + """ + sponsorEntity: Sponsor + + """ + The entity that is being sponsored + """ + sponsorable: Sponsorable! + + """ + The associated sponsorship tier + """ + tier: SponsorsTier +} + +""" +The connection type for Sponsorship. +""" +type SponsorshipConnection { + """ + A list of edges. + """ + edges: [SponsorshipEdge] + + """ + A list of nodes. + """ + nodes: [Sponsorship] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorshipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Sponsorship +} + +""" +Ordering options for sponsorship connections. +""" +input SponsorshipOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order sponsorship by. + """ + field: SponsorshipOrderField! +} + +""" +Properties by which sponsorship connections can be ordered. +""" +enum SponsorshipOrderField { + """ + Order sponsorship by creation time. + """ + CREATED_AT +} + +""" +The privacy of a sponsorship +""" +enum SponsorshipPrivacy { + """ + Private + """ + PRIVATE + + """ + Public + """ + PUBLIC +} + +""" +Ways in which star connections can be ordered. +""" +input StarOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field in which to order nodes by. + """ + field: StarOrderField! +} + +""" +Properties by which star connections can be ordered. +""" +enum StarOrderField { + """ + Allows ordering a list of stars by when they were created. + """ + STARRED_AT +} + +""" +The connection type for User. +""" +type StargazerConnection { + """ + A list of edges. + """ + edges: [StargazerEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user that's starred a repository. +""" +type StargazerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: User! + + """ + Identifies when the item was starred. + """ + starredAt: DateTime! +} + +""" +Things that can be starred. +""" +interface Starrable { + id: ID! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +The connection type for Repository. +""" +type StarredRepositoryConnection { + """ + A list of edges. + """ + edges: [StarredRepositoryEdge] + + """ + Is the list of stars for this user truncated? This is true for users that have many stars. + """ + isOverLimit: Boolean! + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a starred repository. +""" +type StarredRepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: Repository! + + """ + Identifies when the item was starred. + """ + starredAt: DateTime! +} + +""" +Represents a commit status. +""" +type Status implements Node { + """ + A list of status contexts and check runs for this commit. + """ + combinedContexts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): StatusCheckRollupContextConnection! + + """ + The commit this status is attached to. + """ + commit: Commit + + """ + Looks up an individual status context by context name. + """ + context( + """ + The context name. + """ + name: String! + ): StatusContext + + """ + The individual status contexts for this commit. + """ + contexts: [StatusContext!]! + id: ID! + + """ + The combined commit status. + """ + state: StatusState! +} + +""" +Represents the rollup for both the check runs and status for a commit. +""" +type StatusCheckRollup implements Node { + """ + The commit the status and check runs are attached to. + """ + commit: Commit + + """ + A list of status contexts and check runs for this commit. + """ + contexts( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): StatusCheckRollupContextConnection! + id: ID! + + """ + The combined status for the commit. + """ + state: StatusState! +} + +""" +Types that can be inside a StatusCheckRollup context. +""" +union StatusCheckRollupContext = CheckRun | StatusContext + +""" +The connection type for StatusCheckRollupContext. +""" +type StatusCheckRollupContextConnection { + """ + A list of edges. + """ + edges: [StatusCheckRollupContextEdge] + + """ + A list of nodes. + """ + nodes: [StatusCheckRollupContext] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type StatusCheckRollupContextEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: StatusCheckRollupContext +} + +""" +Represents an individual commit status context +""" +type StatusContext implements Node { + """ + The avatar of the OAuth application or the user that created the status + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int = 40 + ): URI + + """ + This commit this status context is attached to. + """ + commit: Commit + + """ + The name of this status context. + """ + context: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this status context. + """ + creator: Actor + + """ + The description for this status context. + """ + description: String + id: ID! + + """ + The state of this status context. + """ + state: StatusState! + + """ + The URL for this status context. + """ + targetUrl: URI +} + +""" +The possible commit status states. +""" +enum StatusState { + """ + Status is errored. + """ + ERROR + + """ + Status is expected. + """ + EXPECTED + + """ + Status is failing. + """ + FAILURE + + """ + Status is pending. + """ + PENDING + + """ + Status is successful. + """ + SUCCESS +} + +""" +Autogenerated input type of SubmitPullRequestReview +""" +input SubmitPullRequestReviewInput { + """ + The text field to set on the Pull Request Review. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The event to send to the Pull Request Review. + """ + event: PullRequestReviewEvent! + + """ + The Pull Request ID to submit any pending reviews. + """ + pullRequestId: ID @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The Pull Request Review ID to submit. + """ + pullRequestReviewId: ID @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of SubmitPullRequestReview +""" +type SubmitPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The submitted pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +A pointer to a repository at a specific revision embedded inside another repository. +""" +type Submodule { + """ + The branch of the upstream submodule for tracking updates + """ + branch: String + + """ + The git URL of the submodule repository + """ + gitUrl: URI! + + """ + The name of the submodule in .gitmodules + """ + name: String! + + """ + The path in the superproject that this submodule is located in + """ + path: String! + + """ + The commit revision of the subproject repository being tracked by the submodule + """ + subprojectCommitOid: GitObjectID +} + +""" +The connection type for Submodule. +""" +type SubmoduleConnection { + """ + A list of edges. + """ + edges: [SubmoduleEdge] + + """ + A list of nodes. + """ + nodes: [Submodule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SubmoduleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Submodule +} + +""" +Entities that can be subscribed to for web and email notifications. +""" +interface Subscribable { + id: ID! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Represents a 'subscribed' event on a given `Subscribable`. +""" +type SubscribedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Object referenced by event. + """ + subscribable: Subscribable! +} + +""" +The possible states of a subscription. +""" +enum SubscriptionState { + """ + The User is never notified. + """ + IGNORED + + """ + The User is notified of all conversations. + """ + SUBSCRIBED + + """ + The User is only notified when participating or @mentioned. + """ + UNSUBSCRIBED +} + +""" +A suggestion to review a pull request based on a user's commit history and review comments. +""" +type SuggestedReviewer { + """ + Is this suggestion based on past commits? + """ + isAuthor: Boolean! + + """ + Is this suggestion based on past review comments? + """ + isCommenter: Boolean! + + """ + Identifies the user suggested to review the pull request. + """ + reviewer: User! +} + +""" +Represents a Git tag. +""" +type Tag implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + id: ID! + + """ + The Git tag message. + """ + message: String + + """ + The Git tag name. + """ + name: String! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! + + """ + Details about the tag author. + """ + tagger: GitActor + + """ + The Git object the tag points to. + """ + target: GitObject! +} + +""" +A team of users in an organization. +""" +type Team implements MemberStatusable & Node & Subscribable { + """ + A list of teams that are ancestors of this team. + """ + ancestors( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): TeamConnection! + + """ + A URL pointing to the team's avatar. + """ + avatarUrl( + """ + The size in pixels of the resulting square image. + """ + size: Int = 400 + ): URI + + """ + List of child teams belonging to this team + """ + childTeams( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether to list immediate child teams or all descendant child teams. + """ + immediateOnly: Boolean = true + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: TeamOrder + + """ + User logins to filter by + """ + userLogins: [String!] + ): TeamConnection! + + """ + The slug corresponding to the organization and team. + """ + combinedSlug: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the team. + """ + description: String + + """ + Find a team discussion by its number. + """ + discussion( + """ + The sequence number of the discussion to find. + """ + number: Int! + ): TeamDiscussion + + """ + A list of team discussions. + """ + discussions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If provided, filters discussions according to whether or not they are pinned. + """ + isPinned: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: TeamDiscussionOrder + ): TeamDiscussionConnection! + + """ + The HTTP path for team discussions + """ + discussionsResourcePath: URI! + + """ + The HTTP URL for team discussions + """ + discussionsUrl: URI! + + """ + The HTTP path for editing this team + """ + editTeamResourcePath: URI! + + """ + The HTTP URL for editing this team + """ + editTeamUrl: URI! + id: ID! + + """ + A list of pending invitations for users to this team + """ + invitations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationInvitationConnection + + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for user statuses returned from the connection. + """ + orderBy: UserStatusOrder = {field: UPDATED_AT, direction: DESC} + ): UserStatusConnection! + + """ + A list of users who are members of this team. + """ + members( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter by membership type + """ + membership: TeamMembershipType = ALL + + """ + Order for the connection. + """ + orderBy: TeamMemberOrder + + """ + The search string to look for. + """ + query: String + + """ + Filter by team member role + """ + role: TeamMemberRole + ): TeamMemberConnection! + + """ + The HTTP path for the team' members + """ + membersResourcePath: URI! + + """ + The HTTP URL for the team' members + """ + membersUrl: URI! + + """ + The name of the team. + """ + name: String! + + """ + The HTTP path creating a new team + """ + newTeamResourcePath: URI! + + """ + The HTTP URL creating a new team + """ + newTeamUrl: URI! + + """ + The organization that owns this team. + """ + organization: Organization! + + """ + The parent team of the team. + """ + parentTeam: Team + + """ + The level of privacy the team has. + """ + privacy: TeamPrivacy! + + """ + A list of repositories this team has access to. + """ + repositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for the connection. + """ + orderBy: TeamRepositoryOrder + + """ + The search string to look for. + """ + query: String + ): TeamRepositoryConnection! + + """ + The HTTP path for this team's repositories + """ + repositoriesResourcePath: URI! + + """ + The HTTP URL for this team's repositories + """ + repositoriesUrl: URI! + + """ + The HTTP path for this team + """ + resourcePath: URI! + + """ + What algorithm is used for review assignment for this team + """ + reviewRequestDelegationAlgorithm: TeamReviewAssignmentAlgorithm @preview(toggledBy: "stone-crop-preview") + + """ + True if review assignment is enabled for this team + """ + reviewRequestDelegationEnabled: Boolean! @preview(toggledBy: "stone-crop-preview") + + """ + How many team members are required for review assignment for this team + """ + reviewRequestDelegationMemberCount: Int @preview(toggledBy: "stone-crop-preview") + + """ + When assigning team members via delegation, whether the entire team should be notified as well. + """ + reviewRequestDelegationNotifyTeam: Boolean! @preview(toggledBy: "stone-crop-preview") + + """ + The slug corresponding to the team. + """ + slug: String! + + """ + The HTTP path for this team's teams + """ + teamsResourcePath: URI! + + """ + The HTTP URL for this team's teams + """ + teamsUrl: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this team + """ + url: URI! + + """ + Team is adminable by the viewer. + """ + viewerCanAdminister: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Audit log entry for a team.add_member event. +""" +type TeamAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a team.add_repository event. +""" +type TeamAddRepositoryAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Metadata for an audit entry with action team.* +""" +interface TeamAuditEntryData { + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI +} + +""" +Audit log entry for a team.change_parent_team event. +""" +type TeamChangeParentTeamAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new parent team. + """ + parentTeam: Team + + """ + The name of the new parent team + """ + parentTeamName: String + + """ + The name of the former parent team + """ + parentTeamNameWas: String + + """ + The HTTP path for the parent team + """ + parentTeamResourcePath: URI + + """ + The HTTP URL for the parent team + """ + parentTeamUrl: URI + + """ + The former parent team. + """ + parentTeamWas: Team + + """ + The HTTP path for the previous parent team + """ + parentTeamWasResourcePath: URI + + """ + The HTTP URL for the previous parent team + """ + parentTeamWasUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The connection type for Team. +""" +type TeamConnection { + """ + A list of edges. + """ + edges: [TeamEdge] + + """ + A list of nodes. + """ + nodes: [Team] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A team discussion. +""" +type TeamDiscussion implements Comment & Deletable & Node & Reactable & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the discussion's team. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the discussion body hash. + """ + bodyVersion: String! + + """ + A list of comments on this discussion. + """ + comments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + When provided, filters the connection such that results begin with the comment with this number. + """ + fromComment: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: TeamDiscussionCommentOrder + ): TeamDiscussionCommentConnection! + + """ + The HTTP path for discussion comments + """ + commentsResourcePath: URI! + + """ + The HTTP URL for discussion comments + """ + commentsUrl: URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Whether or not the discussion is pinned. + """ + isPinned: Boolean! + + """ + Whether or not the discussion is only visible to team members and org admins. + """ + isPrivate: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies the discussion within its team. + """ + number: Int! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The HTTP path for this discussion + """ + resourcePath: URI! + + """ + The team that defines the context of this discussion. + """ + team: Team! + + """ + The title of the discussion + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this discussion + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Whether or not the current viewer can pin this discussion. + """ + viewerCanPin: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A comment on a team discussion. +""" +type TeamDiscussionComment implements Comment & Deletable & Node & Reactable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the comment's team. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + The current version of the body content. + """ + bodyVersion: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The discussion this comment is about. + """ + discussion: TeamDiscussion! + + """ + The actor who edited the comment. + """ + editor: Actor + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies the comment number. + """ + number: Int! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Allows filtering Reactions by emoji. + """ + content: ReactionContent + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Allows specifying the order in which reactions are returned. + """ + orderBy: ReactionOrder + ): ReactionConnection! + + """ + The HTTP path for this comment + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this comment + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for TeamDiscussionComment. +""" +type TeamDiscussionCommentConnection { + """ + A list of edges. + """ + edges: [TeamDiscussionCommentEdge] + + """ + A list of nodes. + """ + nodes: [TeamDiscussionComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type TeamDiscussionCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: TeamDiscussionComment +} + +""" +Ways in which team discussion comment connections can be ordered. +""" +input TeamDiscussionCommentOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field by which to order nodes. + """ + field: TeamDiscussionCommentOrderField! +} + +""" +Properties by which team discussion comment connections can be ordered. +""" +enum TeamDiscussionCommentOrderField { + """ + Allows sequential ordering of team discussion comments (which is equivalent to chronological ordering). + """ + NUMBER +} + +""" +The connection type for TeamDiscussion. +""" +type TeamDiscussionConnection { + """ + A list of edges. + """ + edges: [TeamDiscussionEdge] + + """ + A list of nodes. + """ + nodes: [TeamDiscussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type TeamDiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: TeamDiscussion +} + +""" +Ways in which team discussion connections can be ordered. +""" +input TeamDiscussionOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field by which to order nodes. + """ + field: TeamDiscussionOrderField! +} + +""" +Properties by which team discussion connections can be ordered. +""" +enum TeamDiscussionOrderField { + """ + Allows chronological ordering of team discussions. + """ + CREATED_AT +} + +""" +An edge in a connection. +""" +type TeamEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Team +} + +""" +The connection type for User. +""" +type TeamMemberConnection { + """ + A list of edges. + """ + edges: [TeamMemberEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user who is a member of a team. +""" +type TeamMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The HTTP path to the organization's member access page. + """ + memberAccessResourcePath: URI! + + """ + The HTTP URL to the organization's member access page. + """ + memberAccessUrl: URI! + node: User! + + """ + The role the member has on the team. + """ + role: TeamMemberRole! +} + +""" +Ordering options for team member connections +""" +input TeamMemberOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order team members by. + """ + field: TeamMemberOrderField! +} + +""" +Properties by which team member connections can be ordered. +""" +enum TeamMemberOrderField { + """ + Order team members by creation time + """ + CREATED_AT + + """ + Order team members by login + """ + LOGIN +} + +""" +The possible team member roles; either 'maintainer' or 'member'. +""" +enum TeamMemberRole { + """ + A team maintainer has permission to add and remove team members. + """ + MAINTAINER + + """ + A team member has no administrative permissions on the team. + """ + MEMBER +} + +""" +Defines which types of team members are included in the returned list. Can be one of IMMEDIATE, CHILD_TEAM or ALL. +""" +enum TeamMembershipType { + """ + Includes immediate and child team members for the team. + """ + ALL + + """ + Includes only child team members for the team. + """ + CHILD_TEAM + + """ + Includes only immediate members of the team. + """ + IMMEDIATE +} + +""" +Ways in which team connections can be ordered. +""" +input TeamOrder { + """ + The direction in which to order nodes. + """ + direction: OrderDirection! + + """ + The field in which to order nodes by. + """ + field: TeamOrderField! +} + +""" +Properties by which team connections can be ordered. +""" +enum TeamOrderField { + """ + Allows ordering a list of teams by name. + """ + NAME +} + +""" +The possible team privacy values. +""" +enum TeamPrivacy { + """ + A secret team can only be seen by its members. + """ + SECRET + + """ + A visible team can be seen and @mentioned by every member of the organization. + """ + VISIBLE +} + +""" +Audit log entry for a team.remove_member event. +""" +type TeamRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a team.remove_repository event. +""" +type TeamRemoveRepositoryAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The connection type for Repository. +""" +type TeamRepositoryConnection { + """ + A list of edges. + """ + edges: [TeamRepositoryEdge] + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a team repository. +""" +type TeamRepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + node: Repository! + + """ + The permission level the team has on the repository + """ + permission: RepositoryPermission! +} + +""" +Ordering options for team repository connections +""" +input TeamRepositoryOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order repositories by. + """ + field: TeamRepositoryOrderField! +} + +""" +Properties by which team repository connections can be ordered. +""" +enum TeamRepositoryOrderField { + """ + Order repositories by creation time + """ + CREATED_AT + + """ + Order repositories by name + """ + NAME + + """ + Order repositories by permission + """ + PERMISSION + + """ + Order repositories by push time + """ + PUSHED_AT + + """ + Order repositories by number of stargazers + """ + STARGAZERS + + """ + Order repositories by update time + """ + UPDATED_AT +} + +""" +The possible team review assignment algorithms +""" +enum TeamReviewAssignmentAlgorithm @preview(toggledBy: "stone-crop-preview") { + """ + Balance review load across the entire team + """ + LOAD_BALANCE + + """ + Alternate reviews between each team member + """ + ROUND_ROBIN +} + +""" +The role of a user on a team. +""" +enum TeamRole { + """ + User has admin rights on the team. + """ + ADMIN + + """ + User is a member of the team. + """ + MEMBER +} + +""" +A text match within a search result. +""" +type TextMatch { + """ + The specific text fragment within the property matched on. + """ + fragment: String! + + """ + Highlights within the matched fragment. + """ + highlights: [TextMatchHighlight!]! + + """ + The property matched on. + """ + property: String! +} + +""" +Represents a single highlight in a search result match. +""" +type TextMatchHighlight { + """ + The indice in the fragment where the matched text begins. + """ + beginIndice: Int! + + """ + The indice in the fragment where the matched text ends. + """ + endIndice: Int! + + """ + The text matched. + """ + text: String! +} + +""" +A topic aggregates entities that are related to a subject. +""" +type Topic implements Node & Starrable { + id: ID! + + """ + The topic's name. + """ + name: String! + + """ + A list of related topics, including aliases of this topic, sorted with the most relevant + first. Returns up to 10 Topics. + """ + relatedTopics( + """ + How many topics to return. + """ + first: Int = 3 + ): [Topic!]! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + ): StargazerConnection! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +Metadata for an audit entry with a topic. +""" +interface TopicAuditEntryData { + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String +} + +""" +Reason that the suggested topic is declined. +""" +enum TopicSuggestionDeclineReason { + """ + The suggested topic is not relevant to the repository. + """ + NOT_RELEVANT + + """ + The viewer does not like the suggested topic. + """ + PERSONAL_PREFERENCE + + """ + The suggested topic is too general for the repository. + """ + TOO_GENERAL + + """ + The suggested topic is too specific for the repository (e.g. #ruby-on-rails-version-4-2-1). + """ + TOO_SPECIFIC +} + +""" +Autogenerated input type of TransferIssue +""" +input TransferIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the issue to be transferred + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) + + """ + The Node ID of the repository the issue should be transferred to + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of TransferIssue +""" +type TransferIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was transferred + """ + issue: Issue +} + +""" +Represents a 'transferred' event on a given issue or pull request. +""" +type TransferredEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The repository this came from + """ + fromRepository: Repository + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +Represents a Git tree. +""" +type Tree implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + A list of tree entries. + """ + entries: [TreeEntry!] + id: ID! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! +} + +""" +Represents a Git tree entry. +""" +type TreeEntry { + """ + The extension of the file + """ + extension: String + + """ + Whether or not this tree entry is generated + """ + isGenerated: Boolean! + + """ + Entry file mode. + """ + mode: Int! + + """ + Entry file name. + """ + name: String! + + """ + Entry file object. + """ + object: GitObject + + """ + Entry file Git object ID. + """ + oid: GitObjectID! + + """ + The full path of the file. + """ + path: String + + """ + The Repository the tree entry belongs to + """ + repository: Repository! + + """ + If the TreeEntry is for a directory occupied by a submodule project, this returns the corresponding submodule + """ + submodule: Submodule + + """ + Entry file type. + """ + type: String! +} + +""" +An RFC 3986, RFC 3987, and RFC 6570 (level 4) compliant URI string. +""" +scalar URI + +""" +Autogenerated input type of UnarchiveRepository +""" +input UnarchiveRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to unarchive. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UnarchiveRepository +""" +type UnarchiveRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was unarchived. + """ + repository: Repository +} + +""" +Represents an 'unassigned' event on any assignable object. +""" +type UnassignedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the assignable associated with the event. + """ + assignable: Assignable! + + """ + Identifies the user or mannequin that was unassigned. + """ + assignee: Assignee + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the subject (user) who was unassigned. + """ + user: User @deprecated(reason: "Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC.") +} + +""" +Autogenerated input type of UnfollowUser +""" +input UnfollowUserInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the user to unfollow. + """ + userId: ID! @possibleTypes(concreteTypes: ["User"]) +} + +""" +Autogenerated return type of UnfollowUser +""" +type UnfollowUserPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was unfollowed. + """ + user: User +} + +""" +Represents a type that can be retrieved by a URL. +""" +interface UniformResourceLocatable { + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + The URL to this resource. + """ + url: URI! +} + +""" +Represents an unknown signature on a Commit or Tag. +""" +type UnknownSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by + GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Represents an 'unlabeled' event on a given issue or pull request. +""" +type UnlabeledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the label associated with the 'unlabeled' event. + """ + label: Label! + + """ + Identifies the `Labelable` associated with the event. + """ + labelable: Labelable! +} + +""" +Autogenerated input type of UnlinkRepositoryFromProject +""" +input UnlinkRepositoryFromProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project linked to the Repository. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + The ID of the Repository linked to the Project. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UnlinkRepositoryFromProject +""" +type UnlinkRepositoryFromProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The linked Project. + """ + project: Project + + """ + The linked Repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UnlockLockable +""" +input UnlockLockableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the item to be unlocked. + """ + lockableId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "Lockable") +} + +""" +Autogenerated return type of UnlockLockable +""" +type UnlockLockablePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was unlocked. + """ + unlockedRecord: Lockable +} + +""" +Represents an 'unlocked' event on a given issue or pull request. +""" +type UnlockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Object that was unlocked. + """ + lockable: Lockable! +} + +""" +Autogenerated input type of UnmarkFileAsViewed +""" +input UnmarkFileAsViewedInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The path of the file to mark as unviewed + """ + path: String! + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) +} + +""" +Autogenerated return type of UnmarkFileAsViewed +""" +type UnmarkFileAsViewedPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UnmarkIssueAsDuplicate +""" +input UnmarkIssueAsDuplicateInput { + """ + ID of the issue or pull request currently considered canonical/authoritative/original. + """ + canonicalId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "IssueOrPullRequest") + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue or pull request currently marked as a duplicate. + """ + duplicateId: ID! @possibleTypes(concreteTypes: ["Issue", "PullRequest"], abstractType: "IssueOrPullRequest") +} + +""" +Autogenerated return type of UnmarkIssueAsDuplicate +""" +type UnmarkIssueAsDuplicatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue or pull request that was marked as a duplicate. + """ + duplicate: IssueOrPullRequest +} + +""" +Represents an 'unmarked_as_duplicate' event on a given issue or pull request. +""" +type UnmarkedAsDuplicateEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + The authoritative issue or pull request which has been duplicated by another. + """ + canonical: IssueOrPullRequest + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The issue or pull request which has been marked as a duplicate of another. + """ + duplicate: IssueOrPullRequest + id: ID! + + """ + Canonical and duplicate belong to different repositories. + """ + isCrossRepository: Boolean! +} + +""" +Autogenerated input type of UnminimizeComment +""" +input UnminimizeCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! @possibleTypes(concreteTypes: ["CommitComment", "GistComment", "IssueComment", "PullRequestReviewComment"], abstractType: "Minimizable") +} + +""" +Autogenerated return type of UnminimizeComment +""" +type UnminimizeCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The comment that was unminimized. + """ + unminimizedComment: Minimizable +} + +""" +Autogenerated input type of UnpinIssue +""" +input UnpinIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to be unpinned + """ + issueId: ID! @possibleTypes(concreteTypes: ["Issue"]) +} + +""" +Autogenerated return type of UnpinIssue +""" +type UnpinIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was unpinned + """ + issue: Issue +} + +""" +Represents an 'unpinned' event on a given issue or pull request. +""" +type UnpinnedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +Autogenerated input type of UnresolveReviewThread +""" +input UnresolveReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the thread to unresolve + """ + threadId: ID! @possibleTypes(concreteTypes: ["PullRequestReviewThread"]) +} + +""" +Autogenerated return type of UnresolveReviewThread +""" +type UnresolveReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The thread to resolve. + """ + thread: PullRequestReviewThread +} + +""" +Represents an 'unsubscribed' event on a given `Subscribable`. +""" +type UnsubscribedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + Object referenced by event. + """ + subscribable: Subscribable! +} + +""" +Entities that can be updated. +""" +interface Updatable { + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! +} + +""" +Comments that can be updated. +""" +interface UpdatableComment { + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! +} + +""" +Autogenerated input type of UpdateBranchProtectionRule +""" +input UpdateBranchProtectionRuleInput { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean + + """ + The global relay id of the branch protection rule to be updated. + """ + branchProtectionRuleId: ID! @possibleTypes(concreteTypes: ["BranchProtectionRule"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean + + """ + Can admins overwrite branch protection. + """ + isAdminEnforced: Boolean + + """ + The glob-like pattern used to determine matching branches. + """ + pattern: String + + """ + A list of User, Team or App IDs allowed to push to matching branches. + """ + pushActorIds: [ID!] + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String!] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean + + """ + A list of User or Team IDs allowed to dismiss reviews on pull requests targeting matching branches. + """ + reviewDismissalActorIds: [ID!] +} + +""" +Autogenerated return type of UpdateBranchProtectionRule +""" +type UpdateBranchProtectionRulePayload { + """ + The newly created BranchProtectionRule. + """ + branchProtectionRule: BranchProtectionRule + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateCheckRun +""" +input UpdateCheckRunInput { + """ + Possible further actions the integrator can perform, which a user may trigger. + """ + actions: [CheckRunAction!] + + """ + The node of the check. + """ + checkRunId: ID! @possibleTypes(concreteTypes: ["CheckRun"]) + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The time that the check run finished. + """ + completedAt: DateTime + + """ + The final conclusion of the check. + """ + conclusion: CheckConclusionState + + """ + The URL of the integrator's site that has the full details of the check. + """ + detailsUrl: URI + + """ + A reference for the run on the integrator's system. + """ + externalId: String + + """ + The name of the check. + """ + name: String + + """ + Descriptive details about the run. + """ + output: CheckRunOutput + + """ + The node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + The time that the check run began. + """ + startedAt: DateTime + + """ + The current status. + """ + status: RequestableCheckStatusState +} + +""" +Autogenerated return type of UpdateCheckRun +""" +type UpdateCheckRunPayload { + """ + The updated check run. + """ + checkRun: CheckRun + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateCheckSuitePreferences +""" +input UpdateCheckSuitePreferencesInput { + """ + The check suite preferences to modify. + """ + autoTriggerPreferences: [CheckSuiteAutoTriggerPreference!]! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UpdateCheckSuitePreferences +""" +type UpdateCheckSuitePreferencesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateEnterpriseAdministratorRole +""" +input UpdateEnterpriseAdministratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The login of a administrator whose role is being changed. + """ + login: String! + + """ + The new role for the Enterprise administrator. + """ + role: EnterpriseAdministratorRole! +} + +""" +Autogenerated return type of UpdateEnterpriseAdministratorRole +""" +type UpdateEnterpriseAdministratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of changing the administrator's role. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseAllowPrivateRepositoryForkingSetting +""" +input UpdateEnterpriseAllowPrivateRepositoryForkingSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the allow private repository forking setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the allow private repository forking setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseAllowPrivateRepositoryForkingSetting +""" +type UpdateEnterpriseAllowPrivateRepositoryForkingSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated allow private repository forking setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the allow private repository forking setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseDefaultRepositoryPermissionSetting +""" +input UpdateEnterpriseDefaultRepositoryPermissionSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the default repository permission setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the default repository permission setting on the enterprise. + """ + settingValue: EnterpriseDefaultRepositoryPermissionSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseDefaultRepositoryPermissionSetting +""" +type UpdateEnterpriseDefaultRepositoryPermissionSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated default repository permission setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the default repository permission setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting +""" +input UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can change repository visibility setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can change repository visibility setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting +""" +type UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can change repository visibility setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can change repository visibility setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanCreateRepositoriesSetting +""" +input UpdateEnterpriseMembersCanCreateRepositoriesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can create repositories setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + Allow members to create internal repositories. Defaults to current value. + """ + membersCanCreateInternalRepositories: Boolean + + """ + Allow members to create private repositories. Defaults to current value. + """ + membersCanCreatePrivateRepositories: Boolean + + """ + Allow members to create public repositories. Defaults to current value. + """ + membersCanCreatePublicRepositories: Boolean + + """ + When false, allow member organizations to set their own repository creation member privileges. + """ + membersCanCreateRepositoriesPolicyEnabled: Boolean + + """ + Value for the members can create repositories setting on the enterprise. This + or the granular public/private/internal allowed fields (but not both) must be provided. + """ + settingValue: EnterpriseMembersCanCreateRepositoriesSettingValue +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanCreateRepositoriesSetting +""" +type UpdateEnterpriseMembersCanCreateRepositoriesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can create repositories setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can create repositories setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanDeleteIssuesSetting +""" +input UpdateEnterpriseMembersCanDeleteIssuesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can delete issues setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can delete issues setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanDeleteIssuesSetting +""" +type UpdateEnterpriseMembersCanDeleteIssuesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can delete issues setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can delete issues setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanDeleteRepositoriesSetting +""" +input UpdateEnterpriseMembersCanDeleteRepositoriesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can delete repositories setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can delete repositories setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanDeleteRepositoriesSetting +""" +type UpdateEnterpriseMembersCanDeleteRepositoriesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can delete repositories setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can delete repositories setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanInviteCollaboratorsSetting +""" +input UpdateEnterpriseMembersCanInviteCollaboratorsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can invite collaborators setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can invite collaborators setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanInviteCollaboratorsSetting +""" +type UpdateEnterpriseMembersCanInviteCollaboratorsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can invite collaborators setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can invite collaborators setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanMakePurchasesSetting +""" +input UpdateEnterpriseMembersCanMakePurchasesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can make purchases setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can make purchases setting on the enterprise. + """ + settingValue: EnterpriseMembersCanMakePurchasesSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanMakePurchasesSetting +""" +type UpdateEnterpriseMembersCanMakePurchasesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can make purchases setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can make purchases setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting +""" +input UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can update protected branches setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can update protected branches setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting +""" +type UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can update protected branches setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can update protected branches setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanViewDependencyInsightsSetting +""" +input UpdateEnterpriseMembersCanViewDependencyInsightsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can view dependency insights setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the members can view dependency insights setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanViewDependencyInsightsSetting +""" +type UpdateEnterpriseMembersCanViewDependencyInsightsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can view dependency insights setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can view dependency insights setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseOrganizationProjectsSetting +""" +input UpdateEnterpriseOrganizationProjectsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the organization projects setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the organization projects setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseOrganizationProjectsSetting +""" +type UpdateEnterpriseOrganizationProjectsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated organization projects setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the organization projects setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseProfile +""" +input UpdateEnterpriseProfileInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The description of the enterprise. + """ + description: String + + """ + The Enterprise ID to update. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The location of the enterprise. + """ + location: String + + """ + The name of the enterprise. + """ + name: String + + """ + The URL of the enterprise's website. + """ + websiteUrl: String +} + +""" +Autogenerated return type of UpdateEnterpriseProfile +""" +type UpdateEnterpriseProfilePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise +} + +""" +Autogenerated input type of UpdateEnterpriseRepositoryProjectsSetting +""" +input UpdateEnterpriseRepositoryProjectsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the repository projects setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the repository projects setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseRepositoryProjectsSetting +""" +type UpdateEnterpriseRepositoryProjectsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated repository projects setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the repository projects setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseTeamDiscussionsSetting +""" +input UpdateEnterpriseTeamDiscussionsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the team discussions setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the team discussions setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseTeamDiscussionsSetting +""" +type UpdateEnterpriseTeamDiscussionsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated team discussions setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the team discussions setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseTwoFactorAuthenticationRequiredSetting +""" +input UpdateEnterpriseTwoFactorAuthenticationRequiredSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the two factor authentication required setting. + """ + enterpriseId: ID! @possibleTypes(concreteTypes: ["Enterprise"]) + + """ + The value for the two factor authentication required setting on the enterprise. + """ + settingValue: EnterpriseEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseTwoFactorAuthenticationRequiredSetting +""" +type UpdateEnterpriseTwoFactorAuthenticationRequiredSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated two factor authentication required setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the two factor authentication required setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateIpAllowListEnabledSetting +""" +input UpdateIpAllowListEnabledSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner on which to set the IP allow list enabled setting. + """ + ownerId: ID! @possibleTypes(concreteTypes: ["Enterprise", "Organization"], abstractType: "IpAllowListOwner") + + """ + The value for the IP allow list enabled setting. + """ + settingValue: IpAllowListEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateIpAllowListEnabledSetting +""" +type UpdateIpAllowListEnabledSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list owner on which the setting was updated. + """ + owner: IpAllowListOwner +} + +""" +Autogenerated input type of UpdateIpAllowListEntry +""" +input UpdateIpAllowListEntryInput { + """ + An IP address or range of addresses in CIDR notation. + """ + allowListValue: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IP allow list entry to update. + """ + ipAllowListEntryId: ID! @possibleTypes(concreteTypes: ["IpAllowListEntry"]) + + """ + Whether the IP allow list entry is active when an IP allow list is enabled. + """ + isActive: Boolean! + + """ + An optional name for the IP allow list entry. + """ + name: String +} + +""" +Autogenerated return type of UpdateIpAllowListEntry +""" +type UpdateIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was updated. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of UpdateIssueComment +""" +input UpdateIssueCommentInput { + """ + The updated text of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IssueComment to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["IssueComment"]) +} + +""" +Autogenerated return type of UpdateIssueComment +""" +type UpdateIssueCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + issueComment: IssueComment +} + +""" +Autogenerated input type of UpdateIssue +""" +input UpdateIssueInput { + """ + An array of Node IDs of users for this issue. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The body for the issue description. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Issue to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["Issue"]) + + """ + An array of Node IDs of labels for this issue. + """ + labelIds: [ID!] @possibleTypes(concreteTypes: ["Label"]) + + """ + The Node ID of the milestone for this issue. + """ + milestoneId: ID @possibleTypes(concreteTypes: ["Milestone"]) + + """ + An array of Node IDs for projects associated with this issue. + """ + projectIds: [ID!] + + """ + The desired issue state. + """ + state: IssueState + + """ + The title for the issue. + """ + title: String +} + +""" +Autogenerated return type of UpdateIssue +""" +type UpdateIssuePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue. + """ + issue: Issue +} + +""" +Autogenerated input type of UpdateLabel +""" +input UpdateLabelInput @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A 6 character hex code, without the leading #, identifying the updated color of the label. + """ + color: String + + """ + A brief description of the label, such as its purpose. + """ + description: String + + """ + The Node ID of the label to be updated. + """ + id: ID! @possibleTypes(concreteTypes: ["Label"]) + + """ + The updated name of the label. + """ + name: String +} + +""" +Autogenerated return type of UpdateLabel +""" +type UpdateLabelPayload @preview(toggledBy: "bane-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated label. + """ + label: Label +} + +""" +Autogenerated input type of UpdateProjectCard +""" +input UpdateProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not the ProjectCard should be archived + """ + isArchived: Boolean + + """ + The note of ProjectCard. + """ + note: String + + """ + The ProjectCard ID to update. + """ + projectCardId: ID! @possibleTypes(concreteTypes: ["ProjectCard"]) +} + +""" +Autogenerated return type of UpdateProjectCard +""" +type UpdateProjectCardPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated ProjectCard. + """ + projectCard: ProjectCard +} + +""" +Autogenerated input type of UpdateProjectColumn +""" +input UpdateProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of project column. + """ + name: String! + + """ + The ProjectColumn ID to update. + """ + projectColumnId: ID! @possibleTypes(concreteTypes: ["ProjectColumn"]) +} + +""" +Autogenerated return type of UpdateProjectColumn +""" +type UpdateProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project column. + """ + projectColumn: ProjectColumn +} + +""" +Autogenerated input type of UpdateProject +""" +input UpdateProjectInput { + """ + The description of project. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of project. + """ + name: String + + """ + The Project ID to update. + """ + projectId: ID! @possibleTypes(concreteTypes: ["Project"]) + + """ + Whether the project is public or not. + """ + public: Boolean + + """ + Whether the project is open or closed. + """ + state: ProjectState +} + +""" +Autogenerated return type of UpdateProject +""" +type UpdateProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project. + """ + project: Project +} + +""" +Autogenerated input type of UpdatePullRequest +""" +input UpdatePullRequestInput { + """ + An array of Node IDs of users for this pull request. + """ + assigneeIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The name of the branch you want your changes pulled into. This should be an existing branch + on the current repository. + """ + baseRefName: String + + """ + The contents of the pull request. + """ + body: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + An array of Node IDs of labels for this pull request. + """ + labelIds: [ID!] @possibleTypes(concreteTypes: ["Label"]) + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean + + """ + The Node ID of the milestone for this pull request. + """ + milestoneId: ID @possibleTypes(concreteTypes: ["Milestone"]) + + """ + An array of Node IDs for projects associated with this pull request. + """ + projectIds: [ID!] + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! @possibleTypes(concreteTypes: ["PullRequest"]) + + """ + The target state of the pull request. + """ + state: PullRequestUpdateState + + """ + The title of the pull request. + """ + title: String +} + +""" +Autogenerated return type of UpdatePullRequest +""" +type UpdatePullRequestPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UpdatePullRequestReviewComment +""" +input UpdatePullRequestReviewCommentInput { + """ + The text of the comment. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the comment to modify. + """ + pullRequestReviewCommentId: ID! @possibleTypes(concreteTypes: ["PullRequestReviewComment"]) +} + +""" +Autogenerated return type of UpdatePullRequestReviewComment +""" +type UpdatePullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + pullRequestReviewComment: PullRequestReviewComment +} + +""" +Autogenerated input type of UpdatePullRequestReview +""" +input UpdatePullRequestReviewInput { + """ + The contents of the pull request review body. + """ + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to modify. + """ + pullRequestReviewId: ID! @possibleTypes(concreteTypes: ["PullRequestReview"]) +} + +""" +Autogenerated return type of UpdatePullRequestReview +""" +type UpdatePullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of UpdateRef +""" +input UpdateRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Permit updates of branch Refs that are not fast-forwards? + """ + force: Boolean = false + + """ + The GitObjectID that the Ref shall be updated to target. + """ + oid: GitObjectID! + + """ + The Node ID of the Ref to be updated. + """ + refId: ID! @possibleTypes(concreteTypes: ["Ref"]) +} + +""" +Autogenerated return type of UpdateRef +""" +type UpdateRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated Ref. + """ + ref: Ref +} + +""" +Autogenerated input type of UpdateRefs +""" +input UpdateRefsInput @preview(toggledBy: "update-refs-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A list of ref updates. + """ + refUpdates: [RefUpdate!]! + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) +} + +""" +Autogenerated return type of UpdateRefs +""" +type UpdateRefsPayload @preview(toggledBy: "update-refs-preview") { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateRepository +""" +input UpdateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A new description for the repository. Pass an empty string to erase the existing description. + """ + description: String + + """ + Indicates if the repository should have the issues feature enabled. + """ + hasIssuesEnabled: Boolean + + """ + Indicates if the repository should have the project boards feature enabled. + """ + hasProjectsEnabled: Boolean + + """ + Indicates if the repository should have the wiki feature enabled. + """ + hasWikiEnabled: Boolean + + """ + The URL for a web page about this repository. Pass an empty string to erase the existing URL. + """ + homepageUrl: URI + + """ + The new name of the repository. + """ + name: String + + """ + The ID of the repository to update. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + Whether this repository should be marked as a template such that anyone who + can access it can create new repositories with the same files and directory structure. + """ + template: Boolean +} + +""" +Autogenerated return type of UpdateRepository +""" +type UpdateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateSubscription +""" +input UpdateSubscriptionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new state of the subscription. + """ + state: SubscriptionState! + + """ + The Node ID of the subscribable object to modify. + """ + subscribableId: ID! @possibleTypes(concreteTypes: ["Commit", "Issue", "PullRequest", "Repository", "Team", "TeamDiscussion"], abstractType: "Subscribable") +} + +""" +Autogenerated return type of UpdateSubscription +""" +type UpdateSubscriptionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The input subscribable entity. + """ + subscribable: Subscribable +} + +""" +Autogenerated input type of UpdateTeamDiscussionComment +""" +input UpdateTeamDiscussionCommentInput { + """ + The updated text of the comment. + """ + body: String! + + """ + The current version of the body content. + """ + bodyVersion: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussionComment"]) +} + +""" +Autogenerated return type of UpdateTeamDiscussionComment +""" +type UpdateTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + teamDiscussionComment: TeamDiscussionComment +} + +""" +Autogenerated input type of UpdateTeamDiscussion +""" +input UpdateTeamDiscussionInput { + """ + The updated text of the discussion. + """ + body: String + + """ + The current version of the body content. If provided, this update operation + will be rejected if the given version does not match the latest version on the server. + """ + bodyVersion: String + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to modify. + """ + id: ID! @possibleTypes(concreteTypes: ["TeamDiscussion"]) + + """ + If provided, sets the pinned state of the updated discussion. + """ + pinned: Boolean + + """ + The updated title of the discussion. + """ + title: String +} + +""" +Autogenerated return type of UpdateTeamDiscussion +""" +type UpdateTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated discussion. + """ + teamDiscussion: TeamDiscussion +} + +""" +Autogenerated input type of UpdateTeamReviewAssignment +""" +input UpdateTeamReviewAssignmentInput @preview(toggledBy: "stone-crop-preview") { + """ + The algorithm to use for review assignment + """ + algorithm: TeamReviewAssignmentAlgorithm = ROUND_ROBIN + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Turn on or off review assignment + """ + enabled: Boolean! + + """ + An array of team member IDs to exclude + """ + excludedTeamMemberIds: [ID!] @possibleTypes(concreteTypes: ["User"]) + + """ + The Node ID of the team to update review assignments of + """ + id: ID! @possibleTypes(concreteTypes: ["Team"]) + + """ + Notify the entire team of the PR if it is delegated + """ + notifyTeam: Boolean = true + + """ + The number of team members to assign + """ + teamMemberCount: Int = 1 +} + +""" +Autogenerated return type of UpdateTeamReviewAssignment +""" +type UpdateTeamReviewAssignmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team that was modified + """ + team: Team +} + +""" +Autogenerated input type of UpdateTopics +""" +input UpdateTopicsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! @possibleTypes(concreteTypes: ["Repository"]) + + """ + An array of topic names. + """ + topicNames: [String!]! +} + +""" +Autogenerated return type of UpdateTopics +""" +type UpdateTopicsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Names of the provided topics that are not valid. + """ + invalidTopicNames: [String!] + + """ + The updated repository. + """ + repository: Repository +} + +""" +A user is an individual's account on GitHub that owns repositories and can make new content. +""" +type User implements Actor & Node & PackageOwner & ProfileOwner & ProjectOwner & RepositoryOwner & Sponsorable & UniformResourceLocatable { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems( + """ + Filter to only a particular kind of pinnable item. + """ + type: PinnableItemType + ): Boolean! + + """ + A URL pointing to the user's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The user's public profile bio. + """ + bio: String + + """ + The user's public profile bio as HTML. + """ + bioHTML: HTML! + + """ + A list of commit comments made by this user. + """ + commitComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CommitCommentConnection! + + """ + The user's public profile company. + """ + company: String + + """ + The user's public profile company as HTML. + """ + companyHTML: HTML! + + """ + The collection of contributions this user has made to different repositories. + """ + contributionsCollection( + """ + Only contributions made at this time or later will be counted. If omitted, defaults to a year ago. + """ + from: DateTime + + """ + The ID of the organization used to filter contributions. + """ + organizationID: ID + + """ + Only contributions made before and up to and including this time will be + counted. If omitted, defaults to the current time. + """ + to: DateTime + ): ContributionsCollection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The user's publicly visible profile email. + """ + email: String! + + """ + A list of users the given user is followed by. + """ + followers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): FollowerConnection! + + """ + A list of users the given user is following. + """ + following( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): FollowingConnection! + + """ + Find gist by repo name. + """ + gist( + """ + The gist name to find. + """ + name: String! + ): Gist + + """ + A list of gist comments made by this user. + """ + gistComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): GistCommentConnection! + + """ + A list of the Gists the user has created. + """ + gists( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for gists returned from the connection + """ + orderBy: GistOrder + + """ + Filters Gists according to privacy. + """ + privacy: GistPrivacy + ): GistConnection! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + The hovercard information for this user in a given context + """ + hovercard( + """ + The ID of the subject to get the hovercard in the context of + """ + primarySubjectId: ID + ): Hovercard! + id: ID! + + """ + The interaction ability settings for this user. + """ + interactionAbility: RepositoryInteractionAbility + + """ + Whether or not this user is a participant in the GitHub Security Bug Bounty. + """ + isBountyHunter: Boolean! + + """ + Whether or not this user is a participant in the GitHub Campus Experts Program. + """ + isCampusExpert: Boolean! + + """ + Whether or not this user is a GitHub Developer Program member. + """ + isDeveloperProgramMember: Boolean! + + """ + Whether or not this user is a GitHub employee. + """ + isEmployee: Boolean! + + """ + Whether or not the user has marked themselves as for hire. + """ + isHireable: Boolean! + + """ + Whether or not this user is a site administrator. + """ + isSiteAdmin: Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Whether or not this user is the viewing user. + """ + isViewer: Boolean! + + """ + A list of issue comments made by this user. + """ + issueComments( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issue comments returned from the connection. + """ + orderBy: IssueCommentOrder + ): IssueCommentConnection! + + """ + A list of issues associated with this user. + """ + issues( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Filtering options for issues returned from the connection. + """ + filterBy: IssueFilters + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for issues returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the issues by. + """ + states: [IssueState!] + ): IssueConnection! + + """ + Showcases a selection of repositories and gists that the profile owner has + either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The user's public profile location. + """ + location: String + + """ + The username used to login. + """ + login: String! + + """ + The user's public profile name. + """ + name: String + + """ + Find an organization by its login that the user belongs to. + """ + organization( + """ + The login of the organization to find. + """ + login: String! + ): Organization + + """ + Verified email addresses that match verified domains for a specified organization the user is a member of. + """ + organizationVerifiedDomainEmails( + """ + The login of the organization to match verified domains from. + """ + login: String! + ): [String!]! + + """ + A list of organizations the user belongs to. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): OrganizationConnection! + + """ + A list of packages under the owner. + """ + packages( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Find packages by their names. + """ + names: [String] + + """ + Ordering of the returned packages. + """ + orderBy: PackageOrder = {field: CREATED_AT, direction: DESC} + + """ + Filter registry package by type. + """ + packageType: PackageType + + """ + Find packages in a repository by ID. + """ + repositoryId: ID + ): PackageConnection! + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinnable items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Filter the types of pinned items that are returned. + """ + types: [PinnableItemType!] + ): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Find project by number. + """ + project( + """ + The project number to find. + """ + number: Int! + ): Project + + """ + A list of projects under the owner. + """ + projects( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for projects returned from the connection + """ + orderBy: ProjectOrder + + """ + Query to search projects by, currently only searching by name. + """ + search: String + + """ + A list of states to filter the projects by. + """ + states: [ProjectState!] + ): ProjectConnection! + + """ + The HTTP path listing user's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing user's projects + """ + projectsUrl: URI! + + """ + A list of public keys associated with this user. + """ + publicKeys( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): PublicKeyConnection! + + """ + A list of pull requests associated with this user. + """ + pullRequests( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + The base ref name to filter the pull requests by. + """ + baseRefName: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + The head ref name to filter the pull requests by. + """ + headRefName: String + + """ + A list of label names to filter the pull requests by. + """ + labels: [String!] + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for pull requests returned from the connection. + """ + orderBy: IssueOrder + + """ + A list of states to filter the pull requests by. + """ + states: [PullRequestState!] + ): PullRequestConnection! + + """ + A list of repositories that the user owns. + """ + repositories( + """ + Array of viewer's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + current viewer owns. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they are forks of another repository + """ + isFork: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + A list of repositories that the user recently contributed to. + """ + repositoriesContributedTo( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + If non-null, include only the specified types of contributions. The + GitHub.com UI uses [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY] + """ + contributionTypes: [RepositoryContributionType] + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If true, include user repositories + """ + includeUserRepositories: Boolean + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + Find Repository. + """ + repository( + """ + Name of Repository to find. + """ + name: String! + ): Repository + + """ + The HTTP path for this user + """ + resourcePath: URI! + + """ + Replies this user has saved + """ + savedReplies( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + The field to order saved replies by. + """ + orderBy: SavedReplyOrder = {field: UPDATED_AT, direction: DESC} + ): SavedReplyConnection + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + This object's sponsorships as the maintainer. + """ + sponsorshipsAsMaintainer( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Whether or not to include private sponsorships in the result set + """ + includePrivate: Boolean = false + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + This object's sponsorships as the sponsor. + """ + sponsorshipsAsSponsor( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for sponsorships returned from this connection. If left + blank, the sponsorships will be ordered based on relevancy to the viewer. + """ + orderBy: SponsorshipOrder + ): SponsorshipConnection! + + """ + Repositories the user has starred. + """ + starredRepositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Order for connection + """ + orderBy: StarOrder + + """ + Filters starred repositories to only return repositories owned by the viewer. + """ + ownedByViewer: Boolean + ): StarredRepositoryConnection! + + """ + The user's description of what they're currently doing. + """ + status: UserStatus + + """ + Repositories the user has contributed to, ordered by contribution rank, plus repositories the user has created + """ + topRepositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder! + + """ + How far back in time to fetch contributed repositories + """ + since: DateTime + ): RepositoryConnection! + + """ + The user's Twitter username. + """ + twitterUsername: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this user + """ + url: URI! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Whether or not the viewer is able to follow the user. + """ + viewerCanFollow: Boolean! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + Whether or not this user is followed by the viewer. + """ + viewerIsFollowing: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! + + """ + A list of repositories the given user is watching. + """ + watching( + """ + Affiliation options for repositories returned from the connection. If none + specified, the results will include repositories for which the current + viewer is an owner or collaborator, or member. + """ + affiliations: [RepositoryAffiliation] + + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + If non-null, filters repositories according to whether they have been locked + """ + isLocked: Boolean + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection + """ + orderBy: RepositoryOrder + + """ + Array of owner's affiliation options for repositories returned from the + connection. For example, OWNER will include only repositories that the + organization or user being viewed owns. + """ + ownerAffiliations: [RepositoryAffiliation] = [OWNER, COLLABORATOR] + + """ + If non-null, filters repositories according to privacy + """ + privacy: RepositoryPrivacy + ): RepositoryConnection! + + """ + A URL pointing to the user's public website/blog. + """ + websiteUrl: URI +} + +""" +The possible durations that a user can be blocked for. +""" +enum UserBlockDuration { + """ + The user was blocked for 1 day + """ + ONE_DAY + + """ + The user was blocked for 30 days + """ + ONE_MONTH + + """ + The user was blocked for 7 days + """ + ONE_WEEK + + """ + The user was blocked permanently + """ + PERMANENT + + """ + The user was blocked for 3 days + """ + THREE_DAYS +} + +""" +Represents a 'user_blocked' event on a given user. +""" +type UserBlockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Number of days that the user was blocked for. + """ + blockDuration: UserBlockDuration! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + id: ID! + + """ + The user who was blocked. + """ + subject: User +} + +""" +The connection type for User. +""" +type UserConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edit on user content +""" +type UserContentEdit implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the date and time when the object was deleted. + """ + deletedAt: DateTime + + """ + The actor who deleted this content + """ + deletedBy: Actor + + """ + A summary of the changes for this edit + """ + diff: String + + """ + When this content was edited + """ + editedAt: DateTime! + + """ + The actor who edited this content + """ + editor: Actor + id: ID! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +A list of edits to content. +""" +type UserContentEditConnection { + """ + A list of edges. + """ + edges: [UserContentEditEdge] + + """ + A list of nodes. + """ + nodes: [UserContentEdit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserContentEditEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserContentEdit +} + +""" +Represents a user. +""" +type UserEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User +} + +""" +Email attributes from External Identity +""" +type UserEmailMetadata { + """ + Boolean to identify primary emails + """ + primary: Boolean + + """ + Type of email + """ + type: String + + """ + Email id + """ + value: String! +} + +""" +The user's description of what they're currently doing. +""" +type UserStatus implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + An emoji summarizing the user's status. + """ + emoji: String + + """ + The status emoji as HTML. + """ + emojiHTML: HTML + + """ + If set, the status will not be shown after this date. + """ + expiresAt: DateTime + + """ + ID of the object. + """ + id: ID! + + """ + Whether this status indicates the user is not fully available on GitHub. + """ + indicatesLimitedAvailability: Boolean! + + """ + A brief message describing what the user is doing. + """ + message: String + + """ + The organization whose members can see this status. If null, this status is publicly visible. + """ + organization: Organization + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user who has this status. + """ + user: User! +} + +""" +The connection type for UserStatus. +""" +type UserStatusConnection { + """ + A list of edges. + """ + edges: [UserStatusEdge] + + """ + A list of nodes. + """ + nodes: [UserStatus] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserStatusEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserStatus +} + +""" +Ordering options for user status connections. +""" +input UserStatusOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order user statuses by. + """ + field: UserStatusOrderField! +} + +""" +Properties by which user status connections can be ordered. +""" +enum UserStatusOrderField { + """ + Order user statuses by when they were updated. + """ + UPDATED_AT +} + +""" +A domain that can be verified for an organization or an enterprise. +""" +type VerifiableDomain implements Node { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The DNS host name that should be used for verification. + """ + dnsHostName: URI + + """ + The unicode encoded domain. + """ + domain: URI! + + """ + Whether a TXT record for verification with the expected host name was found. + """ + hasFoundHostName: Boolean! + + """ + Whether a TXT record for verification with the expected verification token was found. + """ + hasFoundVerificationToken: Boolean! + id: ID! + + """ + Whether this domain is required to exist for an organization policy to be enforced. + """ + isRequiredForPolicyEnforcement: Boolean! + + """ + Whether or not the domain is verified. + """ + isVerified: Boolean! + + """ + The owner of the domain. + """ + owner: VerifiableDomainOwner! + + """ + The punycode encoded domain. + """ + punycodeEncodedDomain: URI! + + """ + The time that the current verification token will expire. + """ + tokenExpirationTime: DateTime + + """ + The current verification token for the domain. + """ + verificationToken: String +} + +""" +The connection type for VerifiableDomain. +""" +type VerifiableDomainConnection { + """ + A list of edges. + """ + edges: [VerifiableDomainEdge] + + """ + A list of nodes. + """ + nodes: [VerifiableDomain] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type VerifiableDomainEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: VerifiableDomain +} + +""" +Ordering options for verifiable domain connections. +""" +input VerifiableDomainOrder { + """ + The ordering direction. + """ + direction: OrderDirection! + + """ + The field to order verifiable domains by. + """ + field: VerifiableDomainOrderField! +} + +""" +Properties by which verifiable domain connections can be ordered. +""" +enum VerifiableDomainOrderField { + """ + Order verifiable domains by the domain name. + """ + DOMAIN +} + +""" +Types that can own a verifiable domain. +""" +union VerifiableDomainOwner = Enterprise | Organization + +""" +Autogenerated input type of VerifyVerifiableDomain +""" +input VerifyVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to verify. + """ + id: ID! @possibleTypes(concreteTypes: ["VerifiableDomain"]) +} + +""" +Autogenerated return type of VerifyVerifiableDomain +""" +type VerifyVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was verified. + """ + domain: VerifiableDomain +} + +""" +A hovercard context with a message describing how the viewer is related. +""" +type ViewerHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Identifies the user who is related to this context. + """ + viewer: User! +} + +""" +A valid x509 certificate string +""" +scalar X509Certificate diff --git a/cla-backend-go/github/protected_branch.go b/cla-backend-go/github/protected_branch.go index 97d0fce65..1f449d14a 100644 --- a/cla-backend-go/github/protected_branch.go +++ b/cla-backend-go/github/protected_branch.go @@ -9,13 +9,16 @@ import ( "fmt" "strings" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/swag" + "github.com/sirupsen/logrus" "github.com/jinzhu/copier" log "github.com/communitybridge/easycla/cla-backend-go/logging" githubpkg "github.com/google/go-github/v33/github" + "github.com/shurcooL/githubv4" "go.uber.org/ratelimit" "golang.org/x/time/rate" ) @@ -267,6 +270,189 @@ func (bp *BranchProtectionRepository) EnableBranchProtection(ctx context.Context return err } +// GetRepositoryIDFromName when provided the organization and repository name, returns the repository ID +func GetRepositoryIDFromName(ctx context.Context, installationID int64, repositoryOwner, repositoryName string) (string, error) { + f := logrus.Fields{ + "functionName": "github.GetRepositoryIDFromName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "installationID": installationID, + "repositoryOwner": repositoryOwner, + "repositoryName": repositoryName, + } + + log.WithFields(f).Debugf("loading GitHub v4 client using installation ID: %d", installationID) + client, clientErr := NewGithubV4AppClient(installationID) + if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warnf("problem creating GitHub v4 API client with installation ID: %d", installationID) + return "", clientErr + } + log.WithFields(f).Debugf("loaded GitHub v4 client using installation ID: %d", installationID) + + // Define the graphql query + //"query": "query{repository(name: \"test1\", owner: \"deal-test-org\") {id}}" + var query struct { + Viewer struct { + Login githubv4.String + } + Repository struct { + ID string + } `graphql:"repository(owner:$repositoryOwner, name:$repositoryName)"` + } + + // Define the variables for the query + variables := map[string]interface{}{ + "repositoryOwner": githubv4.String(repositoryOwner), + "repositoryName": githubv4.String(repositoryName), + } + + log.WithFields(f).Debug("executing the query...") + err := client.Query(ctx, &query, variables) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem executing GitHub v4 query using: %+v with variables: %+v", + query, variables) + return "", err + } + + log.WithFields(f).Debugf("User %s looked up repository ID: %s wth installation ID: %d using repository name: %s", + query.Viewer.Login, query.Repository.ID, installationID, repositoryName) + return query.Repository.ID, nil +} + +// GetRepositoryBranchProtection when provided the organization and repository name, returns the repository branch protection rules/info +func GetRepositoryBranchProtection(ctx context.Context, installationID int64, repositoryOwner, repositoryName string) error { + f := logrus.Fields{ + "functionName": "github.GetRepositoryBranchProtection", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "installationID": installationID, + "repositoryOwner": repositoryOwner, + "repositoryName": repositoryName, + } + + // NOTE: This function is not complete - does not return the values as we are still evaluating/testing this API + + log.WithFields(f).Debugf("loading GitHub v4 client using installation ID: %d", installationID) + client, clientErr := NewGithubV4AppClient(installationID) + if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warnf("problem creating GitHub v4 API client with installation ID: %d", installationID) + return clientErr + } + log.WithFields(f).Debugf("loaded GitHub v4 client using installation ID: %d", installationID) + + // Define the graphql query + /* + query { + repository(owner: "lee-dohm", name: "test-repo") { + branchProtectionRules(first: 10) { + nodes { + pattern + } + } + } + } + */ + var query struct { + Viewer struct { + Login githubv4.String + } + //Repository struct { + // BranchProtectionRepositoryOption struct { + // } + //} `graphql:"repository(owner:$repositoryOwner, name:$repositoryName)"` + } + + // Define the variables for the query + variables := map[string]interface{}{ + "repositoryOwner": githubv4.String(repositoryOwner), + "repositoryName": githubv4.String(repositoryName), + } + + log.WithFields(f).Debug("executing the query...") + err := client.Query(ctx, &query, variables) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem executing GitHub v4 query using: %+v with variables: %+v", + query, variables) + return err + } + + // NOTE: still need to implement logic above + return nil +} + +// EnableBranchProtectionForAll sets the branch protection for all branches for the specified repository +func EnableBranchProtectionForAll(ctx context.Context, installationID int64, repositoryOwner, repositoryName string, enforceAdmin bool, enableStatusChecks, disableStatusChecks []string) error { + f := logrus.Fields{ + "functionName": "github.EnableBranchProtectionForAll", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "installationID": installationID, + "repositoryOwner": repositoryOwner, + "repositoryName": repositoryName, + "enforceAdmin": enforceAdmin, + "enableStatusChecks": strings.Join(enableStatusChecks, ","), + "disableStatusChecks": strings.Join(disableStatusChecks, ","), + } + + log.WithFields(f).Debugf("loading GitHub v4 client using installation ID: %d", installationID) + client, clientErr := NewGithubV4AppClient(installationID) + if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warnf("problem creating GitHub v4 API client with installation ID: %d", installationID) + return clientErr + } + log.WithFields(f).Debugf("loaded GitHub v4 client using installation ID: %d", installationID) + + // Define the graphql mutation/update + // This is a sample, not implemented yet + var mutation struct { + AddReaction struct { + Reaction struct { + Content githubv4.ReactionContent + } + Subject struct { + ID githubv4.ID + } + } `graphql:"addReaction(input: $input)"` + Repository struct { + ID string + } `graphql:"repository(repositoryOwner:$repositoryOwner, name:$repositoryName)"` + } + + // Lookup the unique repository ID from the organization and repository name + repositoryID, lookupErr := GetRepositoryIDFromName(ctx, installationID, repositoryOwner, repositoryName) + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Warnf("problem loading repository ID from repository owner and repository name values using installation ID: %d", installationID) + return lookupErr + } + + input := githubv4.CreateBranchProtectionRuleInput{ + RepositoryID: repositoryID, + Pattern: "**/**", + RequiresApprovingReviews: nil, + RequiredApprovingReviewCount: nil, + RequiresCommitSignatures: nil, + RequiresLinearHistory: nil, + AllowsForcePushes: githubv4.NewBoolean(false), + AllowsDeletions: nil, + IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(enforceAdmin)), + RequiresStatusChecks: githubv4.NewBoolean(len(enableStatusChecks) > 0), + RequiresStrictStatusChecks: nil, + RequiresCodeOwnerReviews: nil, + DismissesStaleReviews: nil, + RestrictsReviewDismissals: nil, + ReviewDismissalActorIDs: nil, + RestrictsPushes: nil, + PushActorIDs: nil, + RequiredStatusCheckContexts: nil, + ClientMutationID: nil, + } + + // Define the variables for the query + variables := map[string]interface{}{ + "repositoryOwner": githubv4.String(repositoryOwner), + "repositoryName": githubv4.String(repositoryName), + } + + return client.Mutate(ctx, &mutation, input, variables) +} + // createBranchProtectionRequest creates a branch protection request from existing protection func createBranchProtectionRequest(protection *githubpkg.Protection, enableStatusChecks, disableStatusChecks []string, enforceAdmin bool) (*githubpkg.ProtectionRequest, error) { var currentChecks *githubpkg.RequiredStatusChecks diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 4621ac135..ae1fb4901 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -47,6 +47,8 @@ require ( github.com/pelletier/go-toml v1.8.0 // indirect github.com/rs/cors v1.7.0 github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 + github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa + github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a github.com/sirupsen/logrus v1.7.0 github.com/spf13/afero v1.3.0 // indirect github.com/spf13/cast v1.3.1 // indirect diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 4807b6946..a751a0aa4 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -427,6 +427,10 @@ github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wk github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= +github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= +github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/cla-backend-go/init/init.go b/cla-backend-go/init/init.go index d0b96041d..f0f4e5635 100644 --- a/cla-backend-go/init/init.go +++ b/cla-backend-go/init/init.go @@ -63,7 +63,7 @@ func GetStage() string { return stage } -// GetConfig returns the configration SSM based on stage, e.g. dev, test, stage or prod +// GetConfig returns the configuration SSM based on stage, e.g. dev, test, stage or prod func GetConfig() config.Config { return configVars } diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index 641f6be7c..4c7639699 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -20,7 +20,7 @@ import ( project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" ) -// Service contains functions of Github Repository service +// Service contains functions of GitHub Repository service type Service interface { AddGithubRepository(ctx context.Context, externalProjectID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) EnableRepository(ctx context.Context, repositoryID string) error diff --git a/cla-backend-go/swagger/cla.v1.yaml b/cla-backend-go/swagger/cla.v1.yaml index 0a5b85134..cff74f9bb 100644 --- a/cla-backend-go/swagger/cla.v1.yaml +++ b/cla-backend-go/swagger/cla.v1.yaml @@ -169,6 +169,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - users delete: @@ -200,6 +202,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - users @@ -365,6 +369,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures @@ -396,6 +402,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures @@ -429,6 +437,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures @@ -460,6 +470,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures @@ -492,6 +504,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures @@ -564,6 +578,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures delete: @@ -598,6 +614,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures post: @@ -632,6 +650,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - signatures @@ -1198,6 +1218,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - company @@ -1261,6 +1283,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - company @@ -1291,6 +1315,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - company @@ -1321,6 +1347,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - company @@ -1351,6 +1379,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - company @@ -1589,6 +1619,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' tags: @@ -1625,6 +1657,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' tags: @@ -1694,6 +1728,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' tags: @@ -1795,6 +1831,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' tags: @@ -2093,6 +2131,8 @@ paths: $ref: '#/responses/unauthorized' 403: $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - github-repositories get: @@ -2119,6 +2159,8 @@ paths: $ref: '#/responses/unauthorized' 403: $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - github-repositories /project/{projectSFID}/github/repositories/{repositoryID}: @@ -2259,10 +2301,10 @@ paths: $ref: '#/definitions/gerrit-repo-list' '400': $ref: '#/responses/invalid-request' - '404': - $ref: '#/responses/not-found' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '500': $ref: '#/responses/internal-server-error' tags: @@ -2293,6 +2335,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' tags: - gerrits @@ -2327,6 +2371,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' tags: diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 3207f8f7e..8498d73c2 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -2899,6 +2899,75 @@ paths: tags: - gerrits + /company/{companyID}: + get: + summary: Get Company By Internal ID + description: Returns the company by internal ID + operationId: getCompanyByInternalID + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-companyID" + produces: + - application/json + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/company' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + tags: + - company + + /company/external/{companySFID}: + get: + summary: Get Company by External SFID + description: Returns the company by external ID + operationId: getCompanyByExternalID + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: companySFID + in: path + type: string + required: true + produces: + - application/json + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/company' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + tags: + - company + /company/name/{companyName}: get: summary: Gets the company by name @@ -2936,8 +3005,8 @@ paths: /company/entityname/{signingEntityName}: get: - summary: Gets the company by name - description: Returns the matching company by name + summary: Gets the company by signing entity namename + description: Returns the matching company by signing entity name operationId: getCompanyBySigningEntityName parameters: - $ref: "#/parameters/x-request-id" diff --git a/cla-backend-go/tests/github_v4_test.go b/cla-backend-go/tests/github_v4_test.go new file mode 100644 index 000000000..5a5555da4 --- /dev/null +++ b/cla-backend-go/tests/github_v4_test.go @@ -0,0 +1,45 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "fmt" + "strconv" + "testing" + + ini "github.com/communitybridge/easycla/cla-backend-go/init" + "github.com/spf13/viper" + + "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestGetRepositoryIDFromName(t *testing.T) { + + ctx := utils.NewContext() + + // Need to initialize the system to load the configuration which contains a number of SSM parameters + viper.Set("STAGE", "dev") + viper.Set("DYNAMODB_AWS_REGION", "us-east-1") + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + github.Init(config.GitHub.AppID, config.GitHub.AppPrivateKey, config.GitHub.AccessToken) + installationID, int64Err := strconv.ParseInt(config.GitHub.TestOrganizationInstallationID, 10, 64) + if int64Err != nil { + assert.Fail(t, fmt.Sprintf("unable to convert installation ID to string: %s", config.GitHub.TestOrganizationInstallationID), int64Err) + } + + expectedValue := config.GitHub.TestRepositoryID + actualValue, err := github.GetRepositoryIDFromName(ctx, installationID, config.GitHub.TestOrganization, config.GitHub.TestRepository) + if err != nil { + assert.Fail(t, fmt.Sprintf("unable to create GitHub v4 client from installation ID: %d", installationID), err) + } + assert.Equal(t, expectedValue, actualValue, "Repository ID Lookup") +} diff --git a/cla-backend-go/users/handlers.go b/cla-backend-go/users/handlers.go index 9205285b7..fb97b3bb1 100644 --- a/cla-backend-go/users/handlers.go +++ b/cla-backend-go/users/handlers.go @@ -74,7 +74,7 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser } // Update supports two scenarios: // 1) user has LF login and their record has the LF login as part of their existing User record - should find and match - OK, otherwise permission denied - // 2) user has new LF login and their record does not have the LF login as part of their existing User record - need to lookup by other means, such as Github Username + // 2) user has new LF login and their record does not have the LF login as part of their existing User record - need to lookup by other means, such as GitHub Username // option 2 can happen when GH user gets a user record auto-created and later they need a login for v2 (create company, etc.) // option 2 will be called after they create their login to update their user record with the new login details // option 2 we will search by github username to find the old record - but we can't compare LF login with the existing record because it won't be set yet diff --git a/cla-backend-go/utils/conversion.go b/cla-backend-go/utils/conversion.go index 10f56725b..e2a0f3d32 100644 --- a/cla-backend-go/utils/conversion.go +++ b/cla-backend-go/utils/conversion.go @@ -31,3 +31,8 @@ func BoolValue(input *bool) bool { } return *input } + +// Bool function convert boolean to boolean pointer +func Bool(input bool) *bool { + return &input +} diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index 04fa0e205..de860c7a8 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -3,7 +3,10 @@ package utils -import "fmt" +import ( + "fmt" + "strings" +) // ConversionError is an error model for representing conversion errors type ConversionError struct { @@ -153,37 +156,6 @@ func (e *ProjectCLAGroupMappingNotFound) Unwrap() error { return e.Err } -// CompanyDoesNotExist is an error model for company does not exist errors -type CompanyDoesNotExist struct { - CompanyName string - CompanyID string - CompanySFID string - Err error -} - -// Error is an error string function for company does not exist errs -func (e *CompanyDoesNotExist) Error() string { - var errMsg = "company does not exist" - if e.CompanyName == "" { - errMsg = fmt.Sprintf("%s company name: %s", errMsg, e.CompanyName) - } - if e.CompanyID == "" { - errMsg = fmt.Sprintf("%s company id: %s", errMsg, e.CompanyID) - } - if e.CompanySFID == "" { - errMsg = fmt.Sprintf("%s company sfid: %s", errMsg, e.CompanySFID) - } - if e.Err != nil { - errMsg = fmt.Sprintf("%s error: %+v", errMsg, e.Err) - } - return errMsg -} - -// Unwrap method returns its contained error -func (e *CompanyDoesNotExist) Unwrap() error { - return e.Err -} - // GitHubOrgNotFound is an error model for GitHub Organization not found errors type GitHubOrgNotFound struct { ProjectSFID string @@ -219,3 +191,43 @@ func (e *CompanyAdminNotFound) Error() string { func (e *CompanyAdminNotFound) Unwrap() error { return e.Err } + +// CompanyNotFound is an error model for company not found errors +type CompanyNotFound struct { + Message string + CompanyID string + CompanySFID string + CompanyName string + CompanySigningEntityName string + Err error +} + +// Error is an error string function for Salesforce Project not found errors +func (e *CompanyNotFound) Error() string { + msg := "company does not exist " + if e.Message != "" { + msg = e.Message + } + if e.CompanyName != "" { + msg = fmt.Sprintf("%s - company name: %s ", msg, e.CompanyName) + } + if e.CompanySigningEntityName != "" { + msg = fmt.Sprintf("%s - company sigining entity name: %s ", msg, e.CompanySigningEntityName) + } + if e.CompanyID != "" { + msg = fmt.Sprintf("%s - company ID: %s ", msg, e.CompanyID) + } + if e.CompanySFID != "" { + msg = fmt.Sprintf("%s - company SFID: %s ", msg, e.CompanySFID) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *CompanyNotFound) Unwrap() error { + return e.Err +} diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 696e308d0..3e3a4e142 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -310,7 +310,8 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C "contactAdmin": params.Body.ContactAdmin, "userFullName": utils.StringValue(params.Body.FullName), "userEmail": params.Body.UserEmail.String(), - "authUser": *params.XUSERNAME, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } // Lookup the company by internal ID diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 48fb2fddf..fd4d822b3 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -16,7 +16,6 @@ import ( "github.com/sirupsen/logrus" "github.com/LF-Engineering/lfx-kit/auth" - v1Company "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/company" @@ -28,6 +27,94 @@ import ( // Configure sets up the middleware handlers func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo projects_cla_groups.Repository, LFXPortalURL, v1CorporateConsole string) { // nolint + api.CompanyGetCompanyByInternalIDHandler = company.GetCompanyByInternalIDHandlerFunc( + func(params company.GetCompanyByInternalIDParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "company.handlers.CompanyGetCompanyByInternalIDHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyID": params.CompanyID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), + } + + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by internal ID...") + v2CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) + if err != nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + if _, ok := err.(*utils.CompanyNotFound); ok { + return company.NewGetCompanyByInternalIDNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } + return company.NewGetCompanyByInternalIDBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + if v2CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyByInternalIDNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + log.WithFields(f).Debug("checking permissions") + if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to CompanyGetCompanyByInternalIDHandler with Organization scope of %s", + authUser.UserName, v2CompanyModel.CompanyExternalID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyByInternalIDForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + return company.NewGetCompanyByInternalIDOK().WithXRequestID(reqID).WithPayload(v2CompanyModel) + }) + + api.CompanyGetCompanyByExternalIDHandler = company.GetCompanyByExternalIDHandlerFunc( + func(params company.GetCompanyByExternalIDParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "company.handlers.CompanyGetCompanyByExternalIDHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companySFID": params.CompanySFID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), + } + + // Lookup the company by internal ID + log.WithFields(f).Debugf("looking up company by SFID...") + v2CompanyModel, err := service.GetCompanyBySFID(ctx, params.CompanySFID) + if err != nil { + msg := fmt.Sprintf("unable to lookup company by SFID: %s", params.CompanySFID) + log.WithFields(f).WithError(err).Warn(msg) + if _, ok := err.(*utils.CompanyNotFound); ok { + return company.NewGetCompanyByExternalIDNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } + if _, ok := err.(*organizations.GetOrgNotFound); ok { + return company.NewGetCompanyByExternalIDNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } + log.WithFields(f).Debugf("error type is: %T", err) + return company.NewGetCompanyByExternalIDBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + if v2CompanyModel == nil { + msg := fmt.Sprintf("unable to lookup company by SFID: %s", params.CompanySFID) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyByExternalIDNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + log.WithFields(f).Debug("checking permissions") + if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to CompanyGetCompanyByExternalIDHandler with Organization scope of %s", + authUser.UserName, v2CompanyModel.CompanyExternalID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyByExternalIDForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + return company.NewGetCompanyByExternalIDOK().WithXRequestID(reqID).WithPayload(v2CompanyModel) + }) + api.CompanyGetCompanyProjectClaManagersHandler = company.GetCompanyProjectClaManagersHandlerFunc( func(params company.GetCompanyProjectClaManagersParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) @@ -38,26 +125,28 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } // Lookup the company by internal ID log.WithFields(f).Debugf("looking up company by internal ID...") - v1CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) - if err != nil || v1CompanyModel == nil { + v2CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) + if err != nil || v2CompanyModel == nil { msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) log.WithFields(f).WithError(err).Warn(msg) return company.NewGetCompanyProjectClaManagersBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to GetCompanyProjectClaManagers with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, v1CompanyModel.CompanyExternalID) + authUser.UserName, params.ProjectSFID, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) return company.NewGetCompanyProjectClaManagersForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.GetCompanyProjectCLAManagers(ctx, v1CompanyModel, params.ProjectSFID) + result, err := service.GetCompanyProjectCLAManagers(ctx, v2CompanyModel, params.ProjectSFID) if err != nil { msg := "unable to load company project CLA managers" log.WithFields(f).WithError(err).Warn(msg) @@ -84,7 +173,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo if err != nil { msg := "problem loading company CLA group managers" log.WithFields(f).WithError(err).Warn(msg) - if err == v1Company.ErrCompanyDoesNotExist { + if _, ok := err.(*utils.CompanyNotFound); ok { return company.NewGetCompanyCLAGroupManagersNotFound().WithXRequestID(reqID).WithPayload( utils.ErrorResponseNotFoundWithError(reqID, msg, err)) } @@ -105,27 +194,29 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } // Lookup the company by internal ID log.WithFields(f).Debugf("looking up company by internal ID...") - v1CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) - if err != nil || v1CompanyModel == nil { + v2CompanyModel, err := service.GetCompanyByID(ctx, params.CompanyID) + if err != nil || v2CompanyModel == nil { msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) log.WithFields(f).WithError(err).Warn(msg) return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to GetCompanyProjectActiveCla with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, v1CompanyModel.CompanyExternalID) + authUser.UserName, params.ProjectSFID, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) return company.NewGetCompanyProjectActiveClaForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } log.WithFields(f).Debug("getting company project active CLAs...") - result, err := service.GetCompanyProjectActiveCLAs(ctx, v1CompanyModel.CompanyID, params.ProjectSFID) + result, err := service.GetCompanyProjectActiveCLAs(ctx, v2CompanyModel.CompanyID, params.ProjectSFID) if err != nil { if strings.ContainsAny(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("CLA Group not found with given project SFID: %s", params.ProjectSFID) @@ -133,7 +224,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo return company.NewGetCompanyProjectActiveClaNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbiddenWithError(reqID, msg, err)) } - msg := fmt.Sprintf("error looking up active project CLAs by internal company ID: %s and project SFID: %s", v1CompanyModel.CompanyID, params.ProjectSFID) + msg := fmt.Sprintf("error looking up active project CLAs by internal company ID: %s and project SFID: %s", v2CompanyModel.CompanyID, params.ProjectSFID) log.WithFields(f).Warn(msg) return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } @@ -151,6 +242,8 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } // Lookup the company by internal ID @@ -159,6 +252,9 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo if err != nil || v1CompanyModel == nil { msg := fmt.Sprintf("unable to lookup company by ID: %s", params.CompanyID) log.WithFields(f).WithError(err).Warn(msg) + if _, ok := err.(*utils.CompanyNotFound); ok { + return company.NewGetCompanyProjectActiveClaNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } @@ -167,16 +263,16 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo // CLA Manager - check if authorized by project|organization scope - allow if CLA Manager (for example) has project ID + org DI scope that matches log.WithFields(f).Debug("checking permissions") if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, projectClaGroupRepo) { + msg := fmt.Sprintf("user %s does not have access to get contributors with Project scope of %s or Project|Organization scope of %s | %s", + authUser.UserName, params.ProjectSFID, params.ProjectSFID, params.CompanyID) + log.WithFields(f).Warn(msg) return company.NewGetCompanyProjectContributorsForbidden().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseForbidden( - reqID, - fmt.Sprintf("user %s does not have access to get contributors with Project scope of %s or Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.ProjectSFID, params.CompanyID))) + utils.ErrorResponseForbidden(reqID, msg)) } result, err := service.GetCompanyProjectContributors(ctx, params.ProjectSFID, params.CompanyID, utils.StringValue(params.SearchTerm)) if err != nil { - if err == v1Company.ErrCompanyDoesNotExist { + if _, ok := err.(*utils.CompanyNotFound); ok { return company.NewGetCompanyProjectContributorsNotFound().WithXRequestID(reqID) } return company.NewGetCompanyProjectContributorsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) @@ -194,6 +290,8 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companySFID": params.CompanySFID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } log.WithFields(f).Debug("checking permissions") @@ -208,7 +306,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo if err != nil { msg := "unable to load project company CLAs" log.WithFields(f).WithError(err).Warn(msg) - if err == v1Company.ErrCompanyDoesNotExist { + if _, ok := err.(*utils.CompanyNotFound); ok { return company.NewGetCompanyProjectClaNotFound().WithXRequestID(reqID).WithPayload( utils.ErrorResponseNotFoundWithError(reqID, msg, err)) } @@ -329,6 +427,8 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo "functionName": "company.handlers.CompanyDeleteCompanyByIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": params.CompanyID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } // Attempt to locate the company by ID @@ -381,6 +481,8 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo "functionName": "company.handlers.CompanyDeleteCompanyBySFIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), } // Attempt to locate the company by external SFID diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 5a7f09f6f..19dd56f21 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -487,14 +487,37 @@ func (s *service) GetCompanyBySigningEntityName(ctx context.Context, signingEnti "signingEntityName": signingEntityName, } + log.WithFields(f).Warn("looking up company record by signing entity name...") companyModel, err := s.companyRepo.GetCompanyBySigningEntityName(ctx, signingEntityName) if err != nil { - return nil, err + if _, ok := err.(*utils.CompanyNotFound); ok { // nolint + // As a backup, in case the signing entity name was not set on the old records, lookup the company by it's normal name + log.WithFields(f).Debugf("signing entity name not found. as a backup, searching company by name using signing entity name value: %s", signingEntityName) + companyModel, err = s.companyRepo.GetCompanyByName(ctx, signingEntityName) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup company name by attempting to use the signing entity name") + return nil, err + } + } else { + log.WithFields(f).WithError(err).Warn("unable to lookup company by signing entity name") + return nil, err + } } if companyModel == nil { log.WithFields(f).Debugf("search by company signing entity name: %s didn't locate the record", signingEntityName) - return nil, nil + // As a backup, in case the signing entity name was not set on the old records, lookup the company by it's normal name + log.WithFields(f).Debugf("as a backup, searching company by name using signing entity name value: %s", signingEntityName) + companyModel, err = s.companyRepo.GetCompanyByName(ctx, signingEntityName) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup company name by attempting to use the signing entity name") + return nil, err + } + + if companyModel == nil { + log.WithFields(f).Debugf("search by company name: %s didn't locate the record", signingEntityName) + return nil, nil + } } // Convert from v1 to v2 model - use helper: Copy(toValue interface{}, fromValue interface{}) @@ -726,7 +749,7 @@ func (s *service) GetCompanyBySFID(ctx context.Context, companySFID string) (*mo if err != nil { // If we were unable to find the company/org in our local database, try to auto-create based // on the existing SF record - if err == company.ErrCompanyDoesNotExist { + if _, ok := err.(*utils.CompanyNotFound); ok { log.WithFields(f).Debug("company not found in EasyCLA database - attempting to auto-create from platform organization service record") newCompanyModel, createCompanyErr := s.autoCreateCompany(ctx, companySFID) if createCompanyErr != nil { @@ -736,7 +759,10 @@ func (s *service) GetCompanyBySFID(ctx context.Context, companySFID string) (*mo } if newCompanyModel == nil { log.WithFields(f).Warnf("problem creating company from SF records - created model is nil") - return nil, company.ErrCompanyDoesNotExist + return nil, &utils.CompanyNotFound{ + Message: "unable to auto-create company", + CompanySFID: companySFID, + } } // Success, fall through and continue processing companyModel = newCompanyModel @@ -798,21 +824,24 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, if companyErr != nil { // If we were unable to find the company/org in our local database, try to auto-create based // on the existing SF record - if companyErr == company.ErrCompanyDoesNotExist { - log.WithFields(f).Debug("company not found in EasyCLA database - attempting to auto-create from platform organization service record") + if _, ok := companyErr.(*utils.CompanyNotFound); ok { // nolint + log.WithFields(f).WithError(companyErr).Debug("company not found in EasyCLA database - attempting to auto-create from platform organization service record") var createCompanyErr error companyModel, createCompanyErr = s.autoCreateCompany(ctx, companySFID) if createCompanyErr != nil { - log.WithFields(f).Warnf("problem creating company from platform organization SF record, error: %+v", - createCompanyErr) + log.WithFields(f).WithError(createCompanyErr).Warn("problem creating company from platform organization SF record") return nil, createCompanyErr } if companyModel == nil { log.WithFields(f).Warnf("problem creating company from SF records - created model is nil") - return nil, company.ErrCompanyDoesNotExist + return nil, &utils.CompanyNotFound{ + Message: "unable to auto-create company", + CompanySFID: companySFID, + } } // Success, fall through and continue processing } else { + log.WithFields(f).WithError(companyErr).Warnf("problem fetching company by SFID") return nil, companyErr } } @@ -1401,8 +1430,12 @@ func (s service) autoCreateCompany(ctx context.Context, companySFID string) (*v1 // If we were unable to lookup the company record in SF - we tried our best - return not exist error if sfOrgModel == nil { - log.WithFields(f).Warn("unable to locate platform organization record by SF ID - record not found") - return nil, company.ErrCompanyDoesNotExist + msg := "unable to locate platform organization record by SF ID - record not found" + log.WithFields(f).Warn(msg) + return nil, &utils.CompanyNotFound{ + Message: msg, + CompanySFID: companySFID, + } } log.WithFields(f).Debug("found platform organization record in SF") diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index cf7ca2f16..dcf6f6bf5 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -54,7 +54,7 @@ func NewAutoEnableService(repositoryService repositories.Service, } } -// autoEnableServiceProvider is an abstraction helping with managing autoEnabled flag for Github Organization +// autoEnableServiceProvider is an abstraction helping with managing autoEnabled flag for GitHub Organization // having it separated in its own struct makes testing easier. type autoEnableServiceProvider struct { repositoryService repositories.Service diff --git a/cla-backend-go/v2/dynamo_events/github_organization.go b/cla-backend-go/v2/dynamo_events/github_organization.go index bba17a0e1..5b99176be 100644 --- a/cla-backend-go/v2/dynamo_events/github_organization.go +++ b/cla-backend-go/v2/dynamo_events/github_organization.go @@ -17,11 +17,13 @@ import ( // GitHubOrgAddedEvent github repository added event func (s *service) GitHubOrgAddedEvent(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "dynamodb_events.GitHubOrgAddedEvent", - "eventName": event.EventName, - "eventSource": event.EventSource, - "eventID": event.EventID, + "functionName": "dynamodb_events.github_organization.GitHubOrgAddedEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventName": event.EventName, + "eventSource": event.EventSource, + "eventID": event.EventID, } log.WithFields(f).Debug("processing event") @@ -35,7 +37,7 @@ func (s *service) GitHubOrgAddedEvent(event events.DynamoDBEventRecord) error { // If the branch protection value was updated from false to true.... if newGitHubOrg.BranchProtectionEnabled { log.WithFields(f).Debug("branchProtectionEnabled - processing...") - return s.enableBranchProtectionForGithubOrg(f, newGitHubOrg) + return s.enableBranchProtectionForGithubOrg(ctx, newGitHubOrg) } if newGitHubOrg.AutoEnabled { @@ -49,11 +51,13 @@ func (s *service) GitHubOrgAddedEvent(event events.DynamoDBEventRecord) error { // GitHubOrgUpdatedEvent github repository updated event func (s *service) GitHubOrgUpdatedEvent(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "dynamodb_events.GitHubOrgUpdatedEvent", - "eventName": event.EventName, - "eventSource": event.EventSource, - "eventID": event.EventID, + "functionName": "dynamodb_events.github_organization.GitHubOrgUpdatedEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventName": event.EventName, + "eventSource": event.EventSource, + "eventID": event.EventID, } log.WithFields(f).Debug("processing event") @@ -72,7 +76,7 @@ func (s *service) GitHubOrgUpdatedEvent(event events.DynamoDBEventRecord) error // If the branch protection value was updated from false to true.... if !oldGitHubOrg.BranchProtectionEnabled && newGitHubOrg.BranchProtectionEnabled { log.WithFields(f).Debug("transition of branchProtectionEnabled false => true - processing...") - return s.enableBranchProtectionForGithubOrg(f, newGitHubOrg) + return s.enableBranchProtectionForGithubOrg(ctx, newGitHubOrg) } if !oldGitHubOrg.AutoEnabled && newGitHubOrg.AutoEnabled { @@ -87,11 +91,11 @@ func (s *service) GitHubOrgUpdatedEvent(event events.DynamoDBEventRecord) error func (s *service) GitHubOrgDeletedEvent(event events.DynamoDBEventRecord) error { ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "dynamodb_events.GitHubOrgDeletedEvent", + "functionName": "dynamodb_events.github_organization.GitHubOrgDeletedEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "eventName": event.EventName, "eventSource": event.EventSource, "eventID": event.EventID, - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } log.WithFields(f).Debug("processing event") @@ -127,7 +131,19 @@ func (s *service) GitHubOrgDeletedEvent(event events.DynamoDBEventRecord) error return nil } -func (s *service) enableBranchProtectionForGithubOrg(f logrus.Fields, newGitHubOrg github_organizations.GithubOrganization) error { +func (s *service) enableBranchProtectionForGithubOrg(ctx context.Context, newGitHubOrg github_organizations.GithubOrganization) error { + f := logrus.Fields{ + "functionName": "dynamo_events.github_organization.enableBranchProtectionForGithubOrg", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": newGitHubOrg.ProjectSFID, + "organizationName": newGitHubOrg.OrganizationName, + "organizationSFID": newGitHubOrg.OrganizationSFID, + "organizationInstallationID": newGitHubOrg.OrganizationInstallationID, + "autoEnabled": newGitHubOrg.AutoEnabled, + "branchProtectionEnabled": newGitHubOrg.BranchProtectionEnabled, + "autoEnabledCLAGroupID": newGitHubOrg.AutoEnabledClaGroupID, + } + // Locate the repositories already saved under this organization log.WithFields(f).Debugf("loading repositories under the organization : %s", newGitHubOrg.OrganizationName) repos, err := s.repositoryService.GetRepositoriesByOrganizationName(context.Background(), newGitHubOrg.OrganizationName) @@ -136,10 +152,10 @@ func (s *service) enableBranchProtectionForGithubOrg(f logrus.Fields, newGitHubO return err } - ctx := context.Background() log.WithFields(f).Debugf("creating a new GitHub client object for org: %s...", newGitHubOrg.OrganizationName) gitHubClient, clientErr := githubutils.NewGithubAppClient(newGitHubOrg.OrganizationInstallationID) if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warnf("unable to create a new GitHub app client using the installation ID: %d", newGitHubOrg.OrganizationInstallationID) return clientErr } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index cadc50f80..fc97349d8 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -29,7 +29,7 @@ import ( v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" ) -// Service contains functions of Github Repositories service +// Service contains functions of GitHub Repositories service type Service interface { AddGithubRepository(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) (*v1Models.GithubRepository, error) EnableRepository(ctx context.Context, repositoryID string) error diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 54548d646..b2861b0e1 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -106,7 +106,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) log.Warn(msg) - if errors.Is(err, company.ErrCompanyDoesNotExist) { + if _, ok := err.(*utils.CompanyNotFound); ok { return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "EasyCLA - 404 Not Found - error getting company - " + msg, Code: "404", @@ -449,7 +449,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) log.Warn(msg) - if errors.Is(err, company.ErrCompanyDoesNotExist) { + if _, ok := err.(*utils.CompanyNotFound); ok { return signatures.NewGetProjectCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "EasyCLA - 404 Not Found - error getting company - " + msg, Code: "404", @@ -502,7 +502,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) log.Warn(msg) - if errors.Is(err, company.ErrCompanyDoesNotExist) { + if _, ok := err.(*utils.CompanyNotFound); ok { return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "EasyCLA - 404 Not Found - error getting company - " + msg, Code: "404", @@ -589,7 +589,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) log.Warn(msg) - if errors.Is(err, company.ErrCompanyDoesNotExist) { + if _, ok := err.(*utils.CompanyNotFound); ok { return signatures.NewGetCompanySignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "EasyCLA - 404 Not Found - error getting company - " + msg, Code: "404", @@ -745,7 +745,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) log.Warn(msg) - if errors.Is(err, company.ErrCompanyDoesNotExist) { + if _, ok := err.(*utils.CompanyNotFound); ok { return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "EasyCLA - 404 Not Found - error getting company - " + msg, Code: "404", @@ -871,7 +871,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", *params.CompanyID, err) log.Warn(msg) - if errors.Is(err, company.ErrCompanyDoesNotExist) { + if _, ok := err.(*utils.CompanyNotFound); ok { return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "EasyCLA - 404 Not Found - error getting company - " + msg, Code: "404", diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index d67b7a219..6cb023f1a 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -123,7 +123,7 @@ func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGro return nil, errors.New("not Found") } - b.WriteString(`Github ID,LF_ID,Name,Email,Date Signed`) + b.WriteString(`GitHub ID,LF_ID,Name,Email,Date Signed`) for _, sig := range result.List { b.WriteString(eclaSigCsvLine(sig)) } @@ -136,7 +136,7 @@ func (s service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID str if err != nil { return nil, err } - b.WriteString(`Github ID,LF_ID,Name,Email,Date Signed`) + b.WriteString(`GitHub ID,LF_ID,Name,Email,Date Signed`) for _, sig := range result.List { b.WriteString(iclaSigCsvLine(sig)) } From f0dbfc4861799c7b77152de4ac6f4e3b40b1dcd6 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 28 Jan 2021 16:55:59 -0800 Subject: [PATCH 0021/1276] Updated Allow List Domain Validation - Allow Wildcards - Updated ValidDomain utility method to accept allowWildcards flag - determins if wildcards are allowed or not - Updated unit tests for domain validation Signed-off-by: David Deal --- cla-backend-go/tests/utils_test.go | 36 ++++++++++++++++++++-- cla-backend-go/utils/utils.go | 30 +++++++++++++----- cla-backend-go/v2/signatures/validators.go | 4 +-- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/tests/utils_test.go b/cla-backend-go/tests/utils_test.go index 8ed9fb5f0..7b62da95f 100644 --- a/cla-backend-go/tests/utils_test.go +++ b/cla-backend-go/tests/utils_test.go @@ -242,20 +242,52 @@ func TestValidDomain(t *testing.T) { "slack.com", "slack-domain-with-dash.com", } + + validWildcardDomains := []string{ + "linuxfoundation.org", + "wikipedia.org", + "google.com", + "slack.com", + "slack-domain-with-dash.com", + "*.google.com", + "*.us.google.com", + } + inValidDomains := []string{ + "*.google.com", // test case with allowWildcards = false + "linuxfoundation_org", + "*.linuxfoundation_org", // test case with allowWildcards = false + "/linuxfoundation.org", + "linuxfoundation+fun.org", + "user_linuxfoundation.org", + } + + inWildcardValidDomains := []string{ "linuxfoundation_org", "/linuxfoundation.org", "linuxfoundation+fun.org", + "*.linuxfoundation+fun.org", "user_linuxfoundation.org", + "*.user_linuxfoundation.org", } for _, domain := range validDomains { - msg, valid := utils.ValidDomain(domain) + msg, valid := utils.ValidDomain(domain, false) + assert.True(t, valid, fmt.Sprintf("valid domain %s %s", domain, msg)) + } + + for _, domain := range validWildcardDomains { + msg, valid := utils.ValidDomain(domain, true) assert.True(t, valid, fmt.Sprintf("valid domain %s %s", domain, msg)) } for _, domain := range inValidDomains { - msg, valid := utils.ValidDomain(domain) + msg, valid := utils.ValidDomain(domain, false) + assert.False(t, valid, fmt.Sprintf("invalid domain %s %s", domain, msg)) + } + + for _, domain := range inWildcardValidDomains { + msg, valid := utils.ValidDomain(domain, true) assert.False(t, valid, fmt.Sprintf("invalid domain %s %s", domain, msg)) } } diff --git a/cla-backend-go/utils/utils.go b/cla-backend-go/utils/utils.go index fbfb18feb..eb7a98be0 100644 --- a/cla-backend-go/utils/utils.go +++ b/cla-backend-go/utils/utils.go @@ -186,7 +186,7 @@ func ValidEmail(email string) bool { } // ValidDomain tests the specified domain string, returns true if domain is valid, returns false otherwise -func ValidDomain(domain string) (string, bool) { +func ValidDomain(domain string, allowWildcard bool) (string, bool) { // nolint domain = strings.TrimSpace(domain) switch { @@ -213,14 +213,28 @@ func ValidDomain(domain string) (string, bool) { l = i + 1 continue } - // test label character validity, note: tests are ordered by decreasing validity frequency - if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') { - // show the printable unicode character starting at byte offset i - c, _ := utf8.DecodeRuneInString(domain[i:]) - if c == utf8.RuneError { - return fmt.Sprintf("invalid character at offset %d", i), false + + // If wildcard domains are allowed, e.g. *.linuxfoundation.org + if allowWildcard { + // test label character validity, note: tests are ordered by decreasing validity frequency + if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b == '*' || b >= 'A' && b <= 'Z') { + // show the printable unicode character starting at byte offset i + c, _ := utf8.DecodeRuneInString(domain[i:]) + if c == utf8.RuneError { + return fmt.Sprintf("invalid character at offset %d", i), false + } + return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false + } + } else { + // test label character validity, note: tests are ordered by decreasing validity frequency + if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') { + // show the printable unicode character starting at byte offset i + c, _ := utf8.DecodeRuneInString(domain[i:]) + if c == utf8.RuneError { + return fmt.Sprintf("invalid character at offset %d", i), false + } + return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false } - return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false } } diff --git a/cla-backend-go/v2/signatures/validators.go b/cla-backend-go/v2/signatures/validators.go index c34d7bad3..47ba14750 100644 --- a/cla-backend-go/v2/signatures/validators.go +++ b/cla-backend-go/v2/signatures/validators.go @@ -58,14 +58,14 @@ func entriesAreValid(params signatures.UpdateApprovalListParams) (string, bool) // Ensure the domains are valid for _, domain := range params.Body.AddDomainApprovalList { - msg, valid := utils.ValidDomain(domain) + msg, valid := utils.ValidDomain(domain, true) if !valid { isValid = false listOfErrors = append(listOfErrors, fmt.Sprintf("invalid add approval list domain %s - %s", domain, msg)) } } for _, domain := range params.Body.RemoveDomainApprovalList { - msg, valid := utils.ValidDomain(domain) + msg, valid := utils.ValidDomain(domain, true) if !valid { isValid = false listOfErrors = append(listOfErrors, fmt.Sprintf("invalid remove approval list domain %s - %s", domain, msg)) From 7ddb5df3349036906b74e6a7e8f235fe59dbd88d Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 29 Jan 2021 17:38:35 -0500 Subject: [PATCH 0022/1276] Added Auto-Create Signing Entity Name on Query (#2545) Signed-off-by: David Deal --- cla-backend-go/company/service.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index 7fc942d04..bc3490840 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -710,6 +710,14 @@ func (s service) SearchOrganizationByName(ctx context.Context, orgName string, w var signingEntityNames []string if len(org.SigningEntityName) > 0 { signingEntityNames = utils.TrimSpaceFromItems(org.SigningEntityName) + // Auto-create on-demand from SF + for _, signingEntityName := range signingEntityNames { + // By looking up the signing entity name in our own DB, we auto-create the record if it doesn't exist + _, lookupErr := s.GetCompanyBySigningEntityName(ctx, signingEntityName, org.ID) + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Warnf("problem locating company record using signing entity name: %s with SFID: %s", signingEntityName, org.ID) + } + } } result.List = append(result.List, &models.Org{ OrganizationID: org.ID, From 0bc5fffa22c93e73093381b1ea9367863992419e Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 29 Jan 2021 18:50:11 -0500 Subject: [PATCH 0023/1276] Added Check Prepare Sigs Logic to Support Signing Entity Name (#2546) Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 85 ++++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index c712a7dd5..3006c6fbc 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -416,9 +416,88 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic project_id=project_id ) if len(ccla_signatures) < 1: - cla.log.warning(f'{fn} - project {project.get_project_name()} and ' - f'company {company.get_company_name()} does not have CCLA for: {request_info}') - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} + # Save our message + msg = (f'{fn} - project {project.get_project_name()} and ' + f'company {company.get_company_name()} does not have CCLA for: {request_info}') + + # Ok - long story here, we could have the tricky situation where now that we've added a concept of Signing + # Entity Names we have, basically, a set of 'child' companies all under a common external_id (SFID). This + # would have been so much simpler if SF supported Parent/Child company relationships to model things like + # Subsidiary and Patten holding companies. + # + # Scenario: + # + # Deal Company (SFID: 123, CompanyID: AAA) + # Deal Company Subsidiary 1 - (SFID: 123, CompanyID: BBB) + # Deal Company Subsidiary 2 - (SFID: 123, CompanyID: CCC) - SIGNED! + # Deal Company Subsidiary 3 - (SFID: 123, CompanyID: DDD) + # Deal Company Subsidiary 4 - (SFID: 123, CompanyID: EEE) + # + # Now - the check-prepare-employee signature request could have come from any of the above companies with + # different a company_id - the contributor may have selected the correct option (CCC), the one that was + # signed and executed by a Signatory...or maybe none have been signed...or perhaps another one was signed + # such as companyID BBB. + # + # Originally, we designed the system to keep track of all these sub-companies separately - different CLA + # managers, different approval lists, etc. + # + # Later, the stakeholders wanted to group these all together as one but keep track of the signing entity + # name for each project | company. They wanted to allow the users to select one for each (project | + # organization) pair. + # + # So, we could have CLA signatories/managers wanting: + # + # - Project OpenCue + Deal Company Subsidiary 2 + # - Project OpenVDB + Deal Company Subsidiary 4 + # - Project OpenTelemetry + Deal Company + # + # As a result, we need to query the entire company family under the same external_id for a signed CCLA. + # Currently, we only allow 1 of these to be signed for each Project | Company pair. Later, we may change + # this behavior (it's been debated). + # + # Let's see if they signed the CCLA for another of the Company/Signed Entity Names for this + # project - if so, let's return that one, if not, return the error + + # First, grab the current company's external ID/SFID + company_external_id = company.get_company_external_id() + # if missing, not much we can do... + if company_external_id is None: + cla.log.warning(f'{fn} - project {project.get_project_name()} and ' + f'company {company.get_company_name()} - company missing external id - ' + f'{request_info}') + cla.log.warning(msg) + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} + # Lookup the other companies by external id...will have 1 or more (current record plus possibly others)... + company_list = company.get_company_by_external_id(company_external_id) + # This shouldn't happen, let's trap for it anyway + if len(company_list) == 0: + cla.log.warning(f'{fn} - project {project.get_project_name()} and ' + f'company {company.get_company_name()} - unable to lookup companies by external id: ' + f'{company_external_id} - {request_info}') + cla.log.warning(msg) + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} + + # As we loop, let's use a flag to keep track if we find a CCLA + found_ccla = False + for other_company in company_list: + cla.log.debug(f'{fn} - loading CCLA signatures by cla group: {project.get_project_name()} ' + f'and company id: {other_company.get_company_id()}...') + ccla_signatures = Signature().get_ccla_signatures_by_company_project( + company_id=other_company.get_company_id(), + project_id=project_id + ) + + # Do we have a signed CCLA for this project|company ? If so, we found it - use it! Should NOT have + # more than one of the companies with Signed CCLAs + if len(ccla_signatures) > 0: + found_ccla = True + break + + # if we didn't fine a signed CCLA under any of the other companies... + if not found_ccla: + # Give up + cla.log.warning(msg) + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} # Add a note in the log if we have more than 1 signed and approved CCLA signature if len(ccla_signatures) > 1: From 921feafcf2948ef01432d88c92afd9dd303299d2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 29 Jan 2021 19:29:31 -0500 Subject: [PATCH 0024/1276] Resolved Unit Test Issue in Staging/Prod (#2547) Signed-off-by: David Deal --- .circleci/config.yml | 16 ++++++++++++---- cla-backend-go/tests/github_v4_test.go | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b13471042..e62dbbb10 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -261,13 +261,21 @@ jobs: - run: name: Test command: | - cd cla-backend-go - make test + if [[ "${STAGE}" == "dev" ]]; then + cd cla-backend-go + make test + else + echo "Skipping test - only runs in dev stage." + fi - run: name: Lint command: | - cd cla-backend-go - make lint + if [[ "${STAGE}" == "dev" ]]; then + cd cla-backend-go + make lint + else + echo "Skipping lint - only runs in dev stage." + fi - run: name: Move Binary command: | diff --git a/cla-backend-go/tests/github_v4_test.go b/cla-backend-go/tests/github_v4_test.go index 5a5555da4..1465288f6 100644 --- a/cla-backend-go/tests/github_v4_test.go +++ b/cla-backend-go/tests/github_v4_test.go @@ -5,6 +5,7 @@ package tests import ( "fmt" + "os" "strconv" "testing" @@ -21,8 +22,17 @@ func TestGetRepositoryIDFromName(t *testing.T) { ctx := utils.NewContext() // Need to initialize the system to load the configuration which contains a number of SSM parameters - viper.Set("STAGE", "dev") - viper.Set("DYNAMODB_AWS_REGION", "us-east-1") + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) ini.Init() _, err := ini.GetAWSSession() if err != nil { From f267a36388da8dd4d700582ca00063c3af027ba9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 29 Jan 2021 19:53:41 -0500 Subject: [PATCH 0025/1276] Fixed CI/CD Tests - Updated python check-prepare load company logic (#2548) Signed-off-by: David Deal --- .circleci/config.yml | 3 +++ cla-backend/cla/models/docusign_models.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e62dbbb10..9dd32a7e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -300,6 +300,7 @@ jobs: AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV AWS_PROFILE: lf-cla AWS_REGION: us-east-1 + DYNAMODB_AWS_REGION: us-east-1 buildGoBackendStaging: <<: *buildGoBackendAnchor @@ -309,6 +310,7 @@ jobs: AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING AWS_PROFILE: lf-cla AWS_REGION: us-east-1 + DYNAMODB_AWS_REGION: us-east-1 buildGoBackendProd: <<: *buildGoBackendAnchor @@ -318,6 +320,7 @@ jobs: AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD AWS_PROFILE: lf-cla AWS_REGION: us-east-1 + DYNAMODB_AWS_REGION: us-east-1 # Deploys deployBackend: &deployBackendAnchor diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 3006c6fbc..166beb296 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -491,6 +491,22 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic # more than one of the companies with Signed CCLAs if len(ccla_signatures) > 0: found_ccla = True + # Need to load the correct company record + try: + company_id = ccla_signatures[0].get_signature_reference_id() + cla.log.debug(f'{fn} - loading correct signed CCLA company by id: ' + f'{ccla_signatures[0].get_signature_reference_id()} ' + f'with signed entity name: {ccla_signatures[0].get_signing_entity_name()} ...') + company.load(ccla_signatures[0].get_signature_reference_id()) + cla.log.debug(f'{fn} - loaded company {company.get_company_name()} ' + f'with signing entity name: {company.get_signing_entity_name()} ' + f'for {request_info}.') + except DoesNotExist: + cla.log.warning(f'{fn} - company does NOT exist ' + f'using company_id: {ccla_signatures[0].get_signature_reference_id()} ' + f'for: {request_info}') + return {'errors': {'company_id': f'Company ({ccla_signatures[0].get_signature_reference_id()}) ' + 'does not exist.'}} break # if we didn't fine a signed CCLA under any of the other companies... From a3d36dfea99771b946797faf2f55bbbbb1a3111e Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 1 Feb 2021 01:17:11 -0500 Subject: [PATCH 0026/1276] Updated check_and_prepare_employee_signature logic (#2549) - Updated to set/reset the company_id based on if the signing entity name was correctly specified by the employee Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 27 +++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 166beb296..b4dcc91b0 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -370,7 +370,10 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic # Returns an error if any of the above is false. fn = 'docusign_models.check_and_prepare_employee_signature' - request_info = f'project: {project_id}, company: {company_id}, user: {user_id}' + # Keep a variable with the actual company_id - may swap the original selected company id to use another + # company id if another signing entity name (another related company) is already signed + actual_company_id = company_id + request_info = f'project: {project_id}, company: {actual_company_id}, user: {user_id}' cla.log.info(f'{fn} - check and prepare employee signature for {request_info}') # Ensure the project exists @@ -386,12 +389,12 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic # Ensure the company exists company = Company() try: - cla.log.debug(f'{fn} - loading company by id: {company_id}...') - company.load(str(company_id)) + cla.log.debug(f'{fn} - loading company by id: {actual_company_id}...') + company.load(str(actual_company_id)) cla.log.debug(f'{fn} - company {company.get_company_name()} exists for: {request_info}') except DoesNotExist: cla.log.warning(f'{fn} - company does NOT exist for: {request_info}') - return {'errors': {'company_id': f'Company ({company_id}) does not exist.'}} + return {'errors': {'company_id': f'Company ({actual_company_id}) does not exist.'}} # Ensure the user exists user = User() @@ -493,7 +496,11 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic found_ccla = True # Need to load the correct company record try: - company_id = ccla_signatures[0].get_signature_reference_id() + # Reset the actual company id value since we found a CCLA under a related signing entity name + # company + actual_company_id = ccla_signatures[0].get_signature_reference_id() + # Reset the request_info string with the updated company_id, will use it for debug/warning below + request_info = f'project: {project_id}, company: {actual_company_id}, user: {user_id}' cla.log.debug(f'{fn} - loading correct signed CCLA company by id: ' f'{ccla_signatures[0].get_signature_reference_id()} ' f'with signed entity name: {ccla_signatures[0].get_signing_entity_name()} ...') @@ -535,10 +542,12 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic cla.log.info(f'{fn} - user is approved for this CCLA: {request_info}') - # Assume this company is the user's employer. + # Assume this company is the user's employer. Associated the company with the user in the EasyCLA user record + # For v2, we make the association with the platform via the platform project service via a separate API + # call from the UI # TODO: DAD - we should check to see if they already have a company id assigned - if user.get_user_company_id() != company_id: - user.set_user_company_id(str(company_id)) + if user.get_user_company_id() != actual_company_id: + user.set_user_company_id(str(actual_company_id)) event_data = (f'The user {user.get_user_name()} with GitHub username ' f'{user.get_github_username()} (' f'{user.get_user_github_id()}) and user ID ' @@ -551,7 +560,7 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic f'project {project.get_project_name()}.') Event.create_event( event_type=EventType.UserAssociatedWithCompany, - event_company_id=company_id, + event_company_id=actual_company_id, event_company_name=company.get_company_name(), event_project_id=project_id, event_project_name=project.get_project_name(), From b85931e3f67b2b08c05361cea4fa493f0f1e25ff Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 1 Feb 2021 09:17:48 +0300 Subject: [PATCH 0027/1276] [#2544] Bug/Update Email approval (#2550) - Changed email content for approval list emails for contributor Signed-off-by: wanyaland --- cla-backend-go/signatures/service.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index fa13c9160..757253e6d 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -611,19 +611,23 @@ func (s service) getRemoveGitHubContributors(approvalList *models.ApprovalList) func (s service) sendRequestAccessEmailToContributors(authUser *auth.User, companyModel *models.Company, claGroupModel *models.ClaGroup, approvalList *models.ApprovalList) { addEmailUsers := s.getAddEmailContributors(approvalList) for _, user := range addEmailUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "added", "to", "you are authorized to contribute to") + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "added", "to", + fmt.Sprintf("you are authorized to contribute to %s on behalf of %s", claGroupModel.ProjectName, companyModel.CompanyName)) } removeEmailUsers := s.getRemoveEmailContributors(approvalList) for _, user := range removeEmailUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "removed", "from", "you are no longer authorized to contribute to") + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "removed", "from", + fmt.Sprintf("you are no longer authorized to contribute to %s on behalf of %s ", claGroupModel.ProjectName, companyModel.CompanyName)) } addGitHubUsers := s.getAddGitHubContributors(approvalList) for _, user := range addGitHubUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "added", "to", "you are authorized to contribute to") + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "added", "to", + fmt.Sprintf("you are authorized to contribute to %s on behalf of %s", claGroupModel.ProjectName, companyModel.CompanyName)) } removeGitHubUsers := s.getRemoveGitHubContributors(approvalList) for _, user := range removeGitHubUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "removed", "from", "you are no longer authorized to contribute to") + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "removed", "from", + fmt.Sprintf("you are no longer authorized to contribute to %s on behalf of %s ", claGroupModel.ProjectName, companyModel.CompanyName)) } } @@ -817,12 +821,12 @@ func sendRequestAccessEmailToContributorRecipient(authUser *auth.User, companyMo

Hello %s,

This is a notification email from EasyCLA regarding the project %s.

You have been %s %s the Approval List of %s for %s by CLA Manager %s. This means that %s on behalf of %s.

-

If you had previously submitted one or more pull requests to %s that had failed, you should -close and re-open the pull request to force a recheck by the EasyCLA system.

+

If you had previously submitted a pull request to EasyCLA Test Group that had failed, +you can now go back to it and follow the link to verify with your organization.

%s %s`, recipientName, projectName, addRemove, toFrom, - companyName, projectName, authUser.UserName, authorizedString, projectName, projectName, + companyName, projectName, authUser.UserName, authorizedString, projectName, utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) err := utils.SendEmail(subject, body, recipients) From 261c5eee7f327f6d9ff2e449137d1ce35e084631 Mon Sep 17 00:00:00 2001 From: makkalot Date: Mon, 1 Feb 2021 16:18:15 +0200 Subject: [PATCH 0028/1276] check when 409 is returned for role assignment on company creation Signed-off-by: makkalot --- cla-backend-go/v2/organization-service/client.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index a0276e7c8..8ffcb077a 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -91,7 +91,11 @@ func (osc *Client) CreateOrgUserRoleOrgScope(ctx context.Context, emailID string result, err := osc.cl.Organizations.CreateOrgUsrRoleScopes(params, clientAuth) if err != nil { log.WithFields(f).WithError(err).Warn("unable to assign user to organization") - return err + _, ok := err.(*organizations.CreateOrgUsrRoleScopesConflict) + if !ok { + return err + } + log.WithFields(f).Warn("the role already assigned for the user skipping") } log.WithFields(f).Debugf("Successfully assigned user to organization, result: %#v", result) From fec31e976bfbffc1f7b03334536efdd5ce8c0fe2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 1 Feb 2021 13:11:19 -0500 Subject: [PATCH 0029/1276] Resolved empty list check for python (#2553) Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index b4dcc91b0..73fff4597 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -473,7 +473,7 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic # Lookup the other companies by external id...will have 1 or more (current record plus possibly others)... company_list = company.get_company_by_external_id(company_external_id) # This shouldn't happen, let's trap for it anyway - if len(company_list) == 0: + if not company_list: cla.log.warning(f'{fn} - project {project.get_project_name()} and ' f'company {company.get_company_name()} - unable to lookup companies by external id: ' f'{company_external_id} - {request_info}') From a698e004cb544312f2bbb564a90396c3feb6a47e Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 1 Feb 2021 13:56:12 -0500 Subject: [PATCH 0030/1276] Updatd Get External ID to return list (#2554) - Updated error response model with more details - Updated API to return list for get external company id - Updated test scenario logic Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 8 +++++++- cla-backend/cla/models/dynamo_models.py | 5 +++-- cla-backend/helpers/create_signatures.py | 5 ++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 73fff4597..d8781fffb 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -538,7 +538,13 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic if not user.is_approved(ccla_signature): # TODO: DAD - update this warning message cla.log.warning(f'{fn} - user is not authorized for this CCLA: {request_info}') - return {'errors': {'ccla_approval_list': 'user not authorized for this ccla'}} + return {'errors': {'ccla_approval_list': 'user not authorized for this ccla', + 'company_id': actual_company_id, + 'company_name': company.get_company_name(), + 'signing_entity_name': company.get_signing_entity_name(), + 'company_external_id': company.get_company_external_id(), + } + } cla.log.info(f'{fn} - user is approved for this CCLA: {request_info}') diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index a433712ba..b7aa12a16 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -3346,11 +3346,12 @@ def get_company_by_id(self, company_id: str): def get_company_by_external_id(self, company_external_id: str): company_generator = self.model.company_external_id_index.query(company_external_id) + companies = [] for company_model in company_generator: company = Company() company.model = company_model - return company - return None + companies.append(company) + return companies def all(self, ids: List[str] = None): if ids is None: diff --git a/cla-backend/helpers/create_signatures.py b/cla-backend/helpers/create_signatures.py index 5967d1e73..347b4222b 100644 --- a/cla-backend/helpers/create_signatures.py +++ b/cla-backend/helpers/create_signatures.py @@ -20,7 +20,10 @@ user = get_user_instance().get_user_by_github_id(USER_GITHUB_ID) project1 = get_project_instance().get_projects_by_external_id(PROJECT_EXTERNAL_ID1)[0] project2 = get_project_instance().get_projects_by_external_id(PROJECT_EXTERNAL_ID2)[0] -company = get_company_instance().get_company_by_external_id(COMPANY_EXTERNAL_ID) +company_list = get_company_instance().get_company_by_external_id(COMPANY_EXTERNAL_ID) +company = None +if company_list: + company = company_list[0] # Test ICLA Agreement. sig_id = str(uuid.uuid4()) From 00b92b0f1a58ffc8922b33e956e8a72bd294d071 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 1 Feb 2021 16:54:33 -0500 Subject: [PATCH 0031/1276] Updated check_and_prepare_employee_signature error response models (#2555) - Response models now include additional informaiton on the company id, name, signing entity name, and external id Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 25 ++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index d8781fffb..96b586f10 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -469,7 +469,14 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic f'company {company.get_company_name()} - company missing external id - ' f'{request_info}') cla.log.warning(msg) - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + 'company_id': actual_company_id, + 'company_name': company.get_company_name(), + 'signing_entity_name': company.get_signing_entity_name(), + 'company_external_id': company.get_company_external_id(), + } + } + # Lookup the other companies by external id...will have 1 or more (current record plus possibly others)... company_list = company.get_company_by_external_id(company_external_id) # This shouldn't happen, let's trap for it anyway @@ -478,7 +485,13 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic f'company {company.get_company_name()} - unable to lookup companies by external id: ' f'{company_external_id} - {request_info}') cla.log.warning(msg) - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + 'company_id': actual_company_id, + 'company_name': company.get_company_name(), + 'signing_entity_name': company.get_signing_entity_name(), + 'company_external_id': company.get_company_external_id(), + } + } # As we loop, let's use a flag to keep track if we find a CCLA found_ccla = False @@ -520,7 +533,13 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic if not found_ccla: # Give up cla.log.warning(msg) - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + 'company_id': actual_company_id, + 'company_name': company.get_company_name(), + 'signing_entity_name': company.get_signing_entity_name(), + 'company_external_id': company.get_company_external_id(), + } + } # Add a note in the log if we have more than 1 signed and approved CCLA signature if len(ccla_signatures) > 1: From bdf2de850492c44a06aa75a48aae95436773f782 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 2 Feb 2021 01:21:57 +0300 Subject: [PATCH 0032/1276] [#2539] Bug/ Project Multiple GH orgs (#2556) - Resolved issue caused by return of TLF GH orgs Signed-off-by: wanyaland --- cla-backend-go/github_organizations/repository.go | 2 +- cla-backend-go/v2/github_organizations/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index 6340e69c3..a6ef3adc4 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -238,7 +238,7 @@ func (repo repository) GetGithubOrganizations(ctx context.Context, projectSFID s func (repo repository) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ - "functionName": "GetGitHubOrganizations", + "functionName": "GetGithubOrganizationsByParent", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, } diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index c469257e0..51a0b11ec 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,7 +85,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } var parentProjectSFID string - if projectServiceRecord.Parent == "" || projectServiceRecord.Parent == utils.TheLinuxFoundation { + if projectServiceRecord.Foundation != nil && projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent From 55be850c9c489e7d9b6ed951d7e32919ba55986b Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 2 Feb 2021 13:03:51 +0300 Subject: [PATCH 0033/1276] [#2427,#2558] Bug/Invite Email Cla Manager - Updated email sent to org admin with appropriate v2 link (corporate console) - Updated email content sent to user with no LFID in the invite workflow Signed-off-by: wanyaland --- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/v2/cla_manager/service.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index f62cba994..64020c4c6 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -270,7 +270,7 @@ func server(localMode bool) http.Handler { v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) - v1ApprovalListService := approval_list.NewService(approvalListRepo, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleURL, http.DefaultClient) + v1ApprovalListService := approval_list.NewService(approvalListRepo, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 580bf3cb8..a1ecc83ba 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -1377,12 +1377,12 @@ func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, requesterUsername, r

This email will guide you to completing the CLA Manager role assignment

1. Accept Invite link below will take you SSO login page where you can login with your LF Login or create a LF Login and then login.

2. After logging in SSO screen should direct you to CLA Corporate Console page where you will see the project you a re associated with.

-

3. Click on workflow steps to complete the signup process. Please follow this documentation to help you guide through the process - https://docs.linuxfoundation.org/lfx/easycla/ccla-managers-and-ccla-signatories

+

3. Click on workflow steps to complete the signup process. Please follow this documentation to help you guide through the process - https://docs.linuxfoundation.org/lfx/v/v2/easycla/corporate-cla-manager-designee-or-initial-cla-manager/sign-corporate-cla-for-a-company

4. Once you have completed CLA Manager workflow you will be able to manage the approved list of contributors

Accept Invite

%s %s - `, userWithNoLFIDName, requesterUsername, requesterEmail, organizationName, projectName, + `, userWithNoLFIDName, requesterUsername, requesterEmail, projectName, organizationName, utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) acsClient := v2AcsService.GetClient() automate := false From c91725613e1a7a09d8e4f41f8a295c9f8350de20 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 2 Feb 2021 17:35:43 +0200 Subject: [PATCH 0034/1276] [#2536] Fix/exclude noreply GitHub emails (#2561) --- cla-backend-go/v2/cla_manager/service.go | 29 ++++++- cla-backend-go/v2/cla_manager/service_test.go | 54 ++++++++++++ cla-backend/cla/models/github_models.py | 16 +++- .../cla/tests/unit/test_github_models.py | 84 +++++++++++++++++++ 4 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 cla-backend-go/v2/cla_manager/service_test.go diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index a1ecc83ba..1f96db0e6 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -75,6 +75,8 @@ var ( const ( // NoAccount represents user with no company NoAccount = "Individual - No Account" + // used for filtering when fetching contributor email + excludedNoReplyEmails = "noreply.github.com" ) type service struct { @@ -965,7 +967,8 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return nil, projectErr } - sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, contributor.UserName, contributor.UserEmails[0], name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee") + contibutorEmail := GetNonNoReplyUserEmail(contributor.UserEmails) + sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee") if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) @@ -1449,3 +1452,27 @@ func companyV1toV2(v1CompanyModel *v1Models.Company) *models.Company { Version: v1CompanyModel.Version, } } + +// GetNonNoReplyUserEmail tries to fetch an email which doesn't have noreply string in it +// but if it's the only one we have it'll still be returned +func GetNonNoReplyUserEmail(userEmails []string) string { + if len(userEmails) == 0 { + return "" + } + + excludedEmails := []string{} + + for _, email := range userEmails { + if strings.HasSuffix(email, excludedNoReplyEmails) { + excludedEmails = append(excludedEmails, email) + continue + } + return email + } + + if len(excludedEmails) > 0 { + return excludedEmails[0] + } + + return "" +} diff --git a/cla-backend-go/v2/cla_manager/service_test.go b/cla-backend-go/v2/cla_manager/service_test.go new file mode 100644 index 000000000..3d7c86357 --- /dev/null +++ b/cla-backend-go/v2/cla_manager/service_test.go @@ -0,0 +1,54 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package cla_manager + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetNonNoReplyUserEmail(t *testing.T) { + testCases := []struct { + name string + emails []string + resultEmail string + }{ + { + name: "empty emails", + emails: []string{}, + resultEmail: "", + }, + { + name: "single noreply email", + emails: []string{ + "single@users.noreply.github.com", + }, + resultEmail: "single@users.noreply.github.com", + }, + { + name: "multiple emails with noreply", + emails: []string{ + "single@users.noreply.github.com", + "pumacat@gmail.com", + }, + resultEmail: "pumacat@gmail.com", + }, + { + name: "multiple emails without noreply", + emails: []string{ + "pumacat@gmail.com", + "pumacat2@gmail.com", + }, + resultEmail: "pumacat@gmail.com", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + result := GetNonNoReplyUserEmail(tc.emails) + assert.Equal(tt, tc.resultEmail, result) + }) + } +} diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 01c530c4a..29b14e9cd 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -21,6 +21,9 @@ from cla.models.dynamo_models import Repository, GitHubOrg from cla.utils import get_project_instance, append_project_version_to_url +# some emails we want to exclude when we register the users +EXCLUDE_GITHUB_EMAILS = ["noreply.github.com"] + class GitHub(repository_service_interface.RepositoryService): """ @@ -577,7 +580,18 @@ def get_user_emails(self, session, client_id) -> Union[List[str], dict]: # pyli cla.log.debug('GitHub user emails: %s', emails) if 'error' in emails: return emails - return [item['email'] for item in emails if item['verified']] + + verified_emails = [item['email'] for item in emails if item['verified']] + excluded_emails = [email for email in verified_emails + if any([email.endswith(e) for e in EXCLUDE_GITHUB_EMAILS])] + included_emails = [email for email in verified_emails + if not any([email.endswith(e) for e in EXCLUDE_GITHUB_EMAILS])] + + if len(included_emails) > 0: + return included_emails + + # something we're not very happy about but probably it can happen + return excluded_emails def get_primary_user_email(self, request) -> Union[Optional[str], dict]: """ diff --git a/cla-backend/cla/tests/unit/test_github_models.py b/cla-backend/cla/tests/unit/test_github_models.py index 994fc124d..ca387c031 100644 --- a/cla-backend/cla/tests/unit/test_github_models.py +++ b/cla-backend/cla/tests/unit/test_github_models.py @@ -9,6 +9,7 @@ import cla from cla.models.github_models import get_pull_request_commit_authors, handle_commit_from_user, MockGitHub from cla.models.dynamo_models import Signature, Project +from cla.models.github_models import GitHub as GithubModel class TestGitHubModels(unittest.TestCase): @@ -142,5 +143,88 @@ def test_process_easycla_command_comment(self): }) +class TestGithubUserEmails(unittest.TestCase): + + def test_empty_emails(self): + with patch.object(GithubModel, "_fetch_github_emails") as _fetch_github_emails: + _fetch_github_emails.return_value = [] + github = GithubModel() + emails = github.get_user_emails(None, "fake_client_id") + assert not emails + + def test_emails_with_noreply(self): + with patch.object(GithubModel, "_fetch_github_emails") as _fetch_github_emails: + _fetch_github_emails.return_value = [ + { + "email": "octocat@users.noreply.github.com", + "verified": True, + "primary": True, + "visibility": "public" + }, + { + "email": "pumacat@gmail.com", + "verified": True, + "primary": True, + "visibility": "public" + }, + { + "email": "pumacat+notveried@gmail.com", + "verified": False, + "primary": True, + "visibility": "public" + } + ] + github = GithubModel() + emails = github.get_user_emails(None, "fake_client_id") + assert emails + assert len(emails) == 1 + assert emails == ["pumacat@gmail.com"] + + def test_emails_with_noreply_single(self): + with patch.object(GithubModel, "_fetch_github_emails") as _fetch_github_emails: + _fetch_github_emails.return_value = [ + { + "email": "octocat@users.noreply.github.com", + "verified": True, + "primary": True, + "visibility": "public" + }, + ] + github = GithubModel() + emails = github.get_user_emails(None, "fake_client_id") + assert emails + assert len(emails) == 1 + assert emails == ["octocat@users.noreply.github.com"] + + def test_emails_without_noreply(self): + with patch.object(GithubModel, "_fetch_github_emails") as _fetch_github_emails: + _fetch_github_emails.return_value = [ + { + "email": "pumacat@gmail.com", + "verified": True, + "primary": True, + "visibility": "public" + }, + { + "email": "pumacat2@gmail.com", + "verified": True, + "primary": True, + "visibility": "public" + }, + { + "email": "pumacat+notveried@gmail.com", + "verified": False, + "primary": True, + "visibility": "public" + } + ] + github = GithubModel() + emails = github.get_user_emails(None, "fake_client_id") + assert emails + assert len(emails) == 2 + assert "pumacat@gmail.com" in emails + assert "pumacat2@gmail.com" in emails + + if __name__ == '__main__': unittest.main() From b9edf0415577c4077eca3be95bc14b9a7611f614 Mon Sep 17 00:00:00 2001 From: Shubhra Kar Date: Tue, 2 Feb 2021 08:44:12 -0800 Subject: [PATCH 0035/1276] [Snyk] Security upgrade jinja2 from 2.11.2 to 2.11.3 (#2557) The following vulnerabilities are fixed by pinning transitive dependencies: - https://snyk.io/vuln/SNYK-PYTHON-JINJA2-1012994 Co-authored-by: snyk-bot --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 64247bdbb..ec9c6d220 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -21,7 +21,7 @@ gunicorn==19.9.0 hug==2.6.0 idna==2.8 importlib-metadata==1.6.1 -Jinja2==2.11.2 +Jinja2==2.11.3 jmespath==0.9.4 lazy-object-proxy==1.4.3 Logbook==1.5.3 From b0db85e2aed34353bf73062b623aea3b062400ca Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 2 Feb 2021 12:56:32 -0500 Subject: [PATCH 0036/1276] Updated SF Auto-Create Record Logic (#2562) --- cla-backend-go/company/service.go | 48 ++++++++++++------- .../cla/tests/unit/test_user_service.py | 1 + 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index bc3490840..411e63200 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -670,22 +670,12 @@ func (s service) GetCompanyBySigningEntityName(ctx context.Context, signingEntit } log.WithFields(f).Debug("Searching company by signing entity name...") comp, err := s.repo.GetCompanyBySigningEntityName(ctx, signingEntityName) - if err == nil { + if err != nil { log.WithFields(f).WithError(err).Warn("problem searching organizations by signing entity name") - return comp, nil - } - - if _, ok := err.(*utils.CompanyNotFound); ok { - log.WithFields(f).Debugf("Company with signing entity name %s does not exist", signingEntityName) - comp, err = s.CreateOrgFromExternalID(ctx, signingEntityName, companySFID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("Unable to create organization from external ID: %s using signing entity name: %s", companySFID, signingEntityName) - return comp, err - } - return comp, nil + return nil, err } - return nil, err + return comp, nil } func (s service) SearchOrganizationByName(ctx context.Context, orgName string, websiteName string, filter string) (*models.OrgList, error) { @@ -712,10 +702,10 @@ func (s service) SearchOrganizationByName(ctx context.Context, orgName string, w signingEntityNames = utils.TrimSpaceFromItems(org.SigningEntityName) // Auto-create on-demand from SF for _, signingEntityName := range signingEntityNames { - // By looking up the signing entity name in our own DB, we auto-create the record if it doesn't exist - _, lookupErr := s.GetCompanyBySigningEntityName(ctx, signingEntityName, org.ID) - if lookupErr != nil { - log.WithFields(f).WithError(lookupErr).Warnf("problem locating company record using signing entity name: %s with SFID: %s", signingEntityName, org.ID) + // Auto-create the internal record, if needed + _, err = s.CreateOrgFromExternalID(ctx, signingEntityName, org.ID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("Unable to create organization from external ID: %s using signing entity name: %s", org.ID, signingEntityName) } } } @@ -737,8 +727,30 @@ func (s service) CreateOrgFromExternalID(ctx context.Context, signingEntityName, "companySFID": companySFID, "signingEntityName": signingEntityName, } + + var companyModel *models.Company + var lookupErr error + if signingEntityName == "" { + // Lookup the company in our database...does it exist? + companyModel, lookupErr = s.GetCompanyByExternalID(ctx, companySFID) + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Debug("problem locating internal company record by signing entity name and SFID - must not exist yet") + } + } else { + // Lookup the company in our database...does it exist? + companyModel, lookupErr = s.GetCompanyBySigningEntityName(ctx, signingEntityName, companySFID) + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Debug("problem locating internal company record by signing entity name and SFID - must not exist yet") + } + } + + // Already exists - no need to create in our own database + if companyModel != nil { + return companyModel, nil + } + osc := organization_service.GetClient() - log.WithFields(f).Debugf("Searching organization by company SFID...") + log.WithFields(f).Debugf("Searching organization by company SFID in the organization service...") org, err := osc.GetOrganization(ctx, companySFID) if err != nil { log.WithFields(f).WithError(err).Warn("getting organization details failed") diff --git a/cla-backend/cla/tests/unit/test_user_service.py b/cla-backend/cla/tests/unit/test_user_service.py index e407a6fe7..b7480b8a8 100644 --- a/cla-backend/cla/tests/unit/test_user_service.py +++ b/cla-backend/cla/tests/unit/test_user_service.py @@ -17,6 +17,7 @@ def mock_pcg(): pcg.set_cla_group_id('foo_cla_group_id') yield pcg + @patch('cla.user_service.ProjectCLAGroup.get_by_cla_group_id') @patch('cla.user_service.UserService._list_org_user_scopes') def test_user_has_role_scope(mock_user_scopes, mock_pcgs, mock_pcg): From cd6c22313ab97854da2ee4d33a71257baf1c5953 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 2 Feb 2021 20:08:57 -0500 Subject: [PATCH 0037/1276] Added CLA Summary Report API - Added Signed Entity Name Filter (#2563) - Added new v3 API for CLA Summary Report which returns a quick summary of CLA based on the query criteria. - Added includeSigningEntityName query parameter when searching for organizations in SF - determines if the signed entity names are included with the response. Signed-off-by: David Deal --- cla-backend-go/cla_manager/handlers.go | 2 +- cla-backend-go/cla_manager/service.go | 4 +- cla-backend-go/company/handlers.go | 12 +- cla-backend-go/company/service.go | 54 +- cla-backend-go/signatures/converters.go | 373 +++++++++++++ cla-backend-go/signatures/handlers.go | 32 +- cla-backend-go/signatures/repository.go | 501 ++++++++---------- cla-backend-go/signatures/service.go | 22 +- cla-backend-go/swagger/cla.v1.yaml | 61 +++ .../swagger/common/signature-report.yaml | 25 + .../swagger/common/signature-summary.yaml | 62 +++ cla-backend-go/swagger/common/signature.yaml | 14 +- cla-backend-go/v2/company/handlers.go | 10 +- cla-backend-go/v2/company/service.go | 6 +- .../v2/dynamo_events/cla_manager.go | 4 +- .../v2/dynamo_events/projects_cla_groups.go | 4 +- cla-backend-go/v2/signatures/converters.go | 5 +- cla-backend-go/v2/signatures/handlers.go | 12 +- cla-backend-go/v2/signatures/service.go | 4 +- 19 files changed, 872 insertions(+), 335 deletions(-) create mode 100644 cla-backend-go/signatures/converters.go create mode 100644 cla-backend-go/swagger/common/signature-report.yaml create mode 100644 cla-backend-go/swagger/common/signature-summary.yaml diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index df311de9d..d37b32052 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -315,7 +315,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Update the signature ACL - _, aclErr := sigService.AddCLAManager(ctx, sigModel.SignatureID.String(), request.UserID) + _, aclErr := sigService.AddCLAManager(ctx, sigModel.SignatureID, request.UserID) if aclErr != nil { msg := buildErrorMessageForApprove(params, aclErr) log.Warn(msg) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index fcfc355d1..e526edc55 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -213,7 +213,7 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID companyID, claGroupID, sigModel.SignatureID) // Update the signature ACL - addedSignature, aclErr := s.sigService.AddCLAManager(ctx, sigModel.SignatureID.String(), LFID) + addedSignature, aclErr := s.sigService.AddCLAManager(ctx, sigModel.SignatureID, LFID) if aclErr != nil { return nil, aclErr } @@ -304,7 +304,7 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou } // Update the signature ACL - updatedSignature, aclErr := s.sigService.RemoveCLAManager(ctx, sigModel.SignatureID.String(), LFID) + updatedSignature, aclErr := s.sigService.RemoveCLAManager(ctx, sigModel.SignatureID, LFID) if aclErr != nil || updatedSignature == nil { log.Warnf("remove CLA Manager returned an error or empty signature model using Signature ID: %s, error: %+v", sigModel.SignatureID, sigErr) diff --git a/cla-backend-go/company/handlers.go b/cla-backend-go/company/handlers.go index 1085b5ed6..0c7cebbf5 100644 --- a/cla-backend-go/company/handlers.go +++ b/cla-backend-go/company/handlers.go @@ -347,17 +347,23 @@ func Configure(api *operations.ClaAPI, service IService, usersService users.Serv api.OrganizationSearchOrganizationHandler = organization.SearchOrganizationHandlerFunc(func(params organization.SearchOrganizationParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "company.handler.OrganizationSearchOrganizationHandler", + "companyName": params.CompanyName, + "websiteName": params.WebsiteName, + "includeSigningEntityName": params.IncludeSigningEntityName, + } if params.CompanyName == nil && params.WebsiteName == nil && params.DollarFilter == nil { - log.Debugf("CompanyName or WebsiteName or filter atleast one required") + log.WithFields(f).Debugf("CompanyName or WebsiteName or filter at least one required") return organization.NewSearchOrganizationBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(errors.New("companyName or websiteName or filter at least one required"))) } companyName, websiteName, filter := validateParams(params) - result, err := service.SearchOrganizationByName(ctx, companyName, websiteName, filter) + result, err := service.SearchOrganizationByName(ctx, companyName, websiteName, utils.BoolValue(params.IncludeSigningEntityName), filter) if err != nil { - log.Warnf("error occured while search org %s. error = %s", *params.CompanyName, err.Error()) + log.Warnf("error occurred while search org %s. error = %s", *params.CompanyName, err.Error()) return organization.NewSearchOrganizationInternalServerError().WithXRequestID(reqID).WithPayload(errorResponse(err)) } return organization.NewSearchOrganizationOK().WithXRequestID(reqID).WithPayload(result) diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index 411e63200..e9508a143 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -53,7 +53,7 @@ type IService interface { // nolint RejectCompanyAccessRequest(ctx context.Context, companyInviteID string) (*InviteModel, error) // calls org service - SearchOrganizationByName(ctx context.Context, orgName string, websiteName string, filter string) (*models.OrgList, error) + SearchOrganizationByName(ctx context.Context, orgName string, websiteName string, includeSigningEntityName bool, filter string) (*models.OrgList, error) sendRequestAccessEmail(ctx context.Context, companyModel *models.Company, requesterName, requesterEmail, recipientName, recipientAddress string) sendRequestApprovedEmailToRecipient(ctx context.Context, companyModel *models.Company, recipientName, recipientAddress string) @@ -678,13 +678,14 @@ func (s service) GetCompanyBySigningEntityName(ctx context.Context, signingEntit return comp, nil } -func (s service) SearchOrganizationByName(ctx context.Context, orgName string, websiteName string, filter string) (*models.OrgList, error) { +func (s service) SearchOrganizationByName(ctx context.Context, orgName string, websiteName string, includeSigningEntityName bool, filter string) (*models.OrgList, error) { f := logrus.Fields{ - "functionName": "company.service.SearchOrganizationByName", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "orgName": orgName, - "websiteName": websiteName, - "filter": filter, + "functionName": "company.service.SearchOrganizationByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "orgName": orgName, + "websiteName": websiteName, + "includeSigningEntityName": includeSigningEntityName, + "filter": filter, } osc := organization_service.GetClient() @@ -697,24 +698,33 @@ func (s service) SearchOrganizationByName(ctx context.Context, orgName string, w result := &models.OrgList{List: make([]*models.Org, 0, len(orgs))} for _, org := range orgs { - var signingEntityNames []string - if len(org.SigningEntityName) > 0 { - signingEntityNames = utils.TrimSpaceFromItems(org.SigningEntityName) - // Auto-create on-demand from SF - for _, signingEntityName := range signingEntityNames { - // Auto-create the internal record, if needed - _, err = s.CreateOrgFromExternalID(ctx, signingEntityName, org.ID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("Unable to create organization from external ID: %s using signing entity name: %s", org.ID, signingEntityName) + if includeSigningEntityName { + var signingEntityNames []string + if len(org.SigningEntityName) > 0 { + signingEntityNames = utils.TrimSpaceFromItems(org.SigningEntityName) + // Auto-create on-demand from SF + for _, signingEntityName := range signingEntityNames { + // Auto-create the internal record, if needed + _, err = s.CreateOrgFromExternalID(ctx, signingEntityName, org.ID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("Unable to create organization from external ID: %s using signing entity name: %s", org.ID, signingEntityName) + } } } + result.List = append(result.List, &models.Org{ + OrganizationID: org.ID, + OrganizationName: org.Name, + SigningEntityNames: signingEntityNames, + OrganizationWebsite: org.Link, + }) + } else { + result.List = append(result.List, &models.Org{ + OrganizationID: org.ID, + OrganizationName: org.Name, + SigningEntityNames: []string{}, + OrganizationWebsite: org.Link, + }) } - result.List = append(result.List, &models.Org{ - OrganizationID: org.ID, - OrganizationName: org.Name, - SigningEntityNames: signingEntityNames, - OrganizationWebsite: org.Link, - }) } return result, nil } diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go new file mode 100644 index 000000000..a347d2a50 --- /dev/null +++ b/cla-backend-go/signatures/converters.go @@ -0,0 +1,373 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package signatures + +import ( + "context" + "strings" + "sync" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/communitybridge/easycla/cla-backend-go/gen/models" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" +) + +// buildProjectSignatureModels converts the response model into a response data model +func (repo repository) buildProjectSignatureModels(ctx context.Context, results *dynamodb.QueryOutput, projectID string, loadACLDetails bool) ([]*models.Signature, error) { + f := logrus.Fields{ + "functionName": "signatures.converters.buildProjectSignatureModels", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": projectID, + } + var sigs []*models.Signature + + // The DB signature model + var dbSignatures []ItemSignature + + err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) + if err != nil { + log.WithFields(f).Warnf("error unmarshalling signatures from database for project: %s, error: %v", + projectID, err) + return nil, err + } + + var wg sync.WaitGroup + wg.Add(len(dbSignatures)) + for _, dbSignature := range dbSignatures { + + // Set the signature type in the response + var claType = "" + // Corporate Signature + if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeCompany && dbSignature.SignatureType == utils.SignatureTypeCCLA { + claType = utils.ClaTypeCCLA + } + // Employee Signature + if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeUser && dbSignature.SignatureType == utils.SignatureTypeCLA && dbSignature.SignatureUserCompanyID != "" { + claType = utils.ClaTypeECLA + } + + // Individual Signature + if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeUser && dbSignature.SignatureType == utils.SignatureTypeCLA && dbSignature.SignatureUserCompanyID == "" { + claType = utils.ClaTypeICLA + } + + sig := &models.Signature{ + SignatureID: dbSignature.SignatureID, + ClaType: claType, + SignatureCreated: dbSignature.DateCreated, + SignatureModified: dbSignature.DateModified, + SignatureType: dbSignature.SignatureType, + SignatureReferenceID: dbSignature.SignatureReferenceID, + SignatureReferenceName: dbSignature.SignatureReferenceName, + SignatureReferenceNameLower: dbSignature.SignatureReferenceNameLower, + SignatureSigned: dbSignature.SignatureSigned, + SignatureApproved: dbSignature.SignatureApproved, + SignatureMajorVersion: dbSignature.SignatureDocumentMajorVersion, + SignatureMinorVersion: dbSignature.SignatureDocumentMinorVersion, + Version: dbSignature.SignatureDocumentMajorVersion + "." + dbSignature.SignatureDocumentMinorVersion, + SignatureReferenceType: dbSignature.SignatureReferenceType, + ProjectID: dbSignature.SignatureProjectID, + Created: dbSignature.DateCreated, + Modified: dbSignature.DateModified, + EmailApprovalList: dbSignature.EmailWhitelist, + DomainApprovalList: dbSignature.DomainWhitelist, + GithubUsernameApprovalList: dbSignature.GitHubWhitelist, + GithubOrgApprovalList: dbSignature.GitHubOrgWhitelist, + UserName: dbSignature.UserName, + UserLFID: dbSignature.UserLFUsername, + UserGHID: dbSignature.UserGithubUsername, + SignedOn: dbSignature.SignedOn, + SignatoryName: dbSignature.SignatoryName, + UserDocusignName: dbSignature.UserDocusignName, + UserDocusignDateSigned: dbSignature.UserDocusignDateSigned, + } + sigs = append(sigs, sig) + go func(sigModel *models.Signature, signatureUserCompanyID string, sigACL []string) { + defer wg.Done() + var companyName = "" + var companySigningEntityName = "" + var userName = "" + var userLFID = "" + var userGHID = "" + var userGHUsername = "" + var swg sync.WaitGroup + swg.Add(2) + + go func() { + defer swg.Done() + if sigModel.SignatureReferenceType == utils.SignatureReferenceTypeUser { + userModel, userErr := repo.usersRepo.GetUser(sigModel.SignatureReferenceID) + if userErr != nil || userModel == nil { + log.WithFields(f).Warnf("unable to lookup user using id: %s, error: %v", sigModel.SignatureReferenceID, userErr) + } else { + userName = userModel.Username + userLFID = userModel.LfUsername + userGHID = userModel.GithubID + userGHUsername = userModel.GithubUsername + } + + if signatureUserCompanyID != "" { + dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, signatureUserCompanyID) + if companyErr != nil { + log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", signatureUserCompanyID, companyErr) + } else { + companyName = dbCompanyModel.CompanyName + companySigningEntityName = dbCompanyModel.SigningEntityName + } + } + } else if sigModel.SignatureReferenceType == utils.SignatureReferenceTypeCompany { + dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, sigModel.SignatureReferenceID) + if companyErr != nil { + log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", sigModel.SignatureReferenceID, companyErr) + } else { + companyName = dbCompanyModel.CompanyName + companySigningEntityName = dbCompanyModel.SigningEntityName + } + } + }() + + var signatureACL []models.User + go func() { + defer swg.Done() + for _, userName := range sigACL { + if loadACLDetails { + userModel, userErr := repo.usersRepo.GetUserByUserName(userName, true) + if userErr != nil { + log.WithFields(f).Warnf("unable to lookup user using username: %s, error: %v", userName, userErr) + } else { + if userModel == nil { + log.WithFields(f).Warnf("User looking for username is null: %s for signature: %s", userName, sigModel.SignatureID) + } else { + signatureACL = append(signatureACL, *userModel) + } + } + } else { + signatureACL = append(signatureACL, models.User{LfUsername: userName}) + } + } + }() + swg.Wait() + sigModel.CompanyName = companyName + sigModel.SigningEntityName = companySigningEntityName + sigModel.UserName = userName + sigModel.UserLFID = userLFID + sigModel.UserGHID = userGHID + sigModel.UserGHUsername = userGHUsername + sigModel.SignatureACL = signatureACL + }(sig, dbSignature.SignatureUserCompanyID, dbSignature.SignatureACL) + } + wg.Wait() + return sigs, nil +} + +// buildProjectSignatureSummaryModels converts the response model into a signature summary model +func (repo repository) buildProjectSignatureSummaryModels(ctx context.Context, results *dynamodb.QueryOutput, projectID string) ([]*models.SignatureSummary, error) { + f := logrus.Fields{ + "functionName": "signatures.converters.buildProjectSignatureSummaryModels", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": projectID, + } + var sigs []*models.SignatureSummary + + // The DB signature model + var dbSignatures []ItemSignature + + err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) + if err != nil { + log.WithFields(f).Warnf("error unmarshalling signatures from database for project: %s, error: %v", + projectID, err) + return nil, err + } + + var wg sync.WaitGroup + wg.Add(len(dbSignatures)) + for _, dbSignature := range dbSignatures { + + // Set the signature type in the response + var claType = "" + // Corporate Signature + if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeCompany && dbSignature.SignatureType == utils.SignatureTypeCCLA { + claType = utils.ClaTypeCCLA + } + // Employee Signature + if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeUser && dbSignature.SignatureType == utils.SignatureTypeCLA && dbSignature.SignatureUserCompanyID != "" { + claType = utils.ClaTypeECLA + } + + // Individual Signature + if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeUser && dbSignature.SignatureType == utils.SignatureTypeCLA && dbSignature.SignatureUserCompanyID == "" { + claType = utils.ClaTypeICLA + } + + sig := &models.SignatureSummary{ + SignatureID: dbSignature.SignatureID, + ClaType: claType, + SignatureType: dbSignature.SignatureType, + SignatureReferenceID: dbSignature.SignatureReferenceID, + SignatureReferenceName: dbSignature.SignatureReferenceName, + SignatureReferenceNameLower: dbSignature.SignatureReferenceNameLower, + SignatureSigned: dbSignature.SignatureSigned, + SignatureApproved: dbSignature.SignatureApproved, + SignatureReferenceType: dbSignature.SignatureReferenceType, + ProjectID: dbSignature.SignatureProjectID, + SignedOn: dbSignature.SignedOn, + SignatoryName: dbSignature.SignatoryName, + UserDocusignName: dbSignature.UserDocusignName, + UserDocusignDateSigned: dbSignature.UserDocusignDateSigned, + } + + sigs = append(sigs, sig) + go func(sigModel *models.SignatureSummary, signatureUserCompanyID string) { + defer wg.Done() + var companyName = "" + var companySigningEntityName = "" + var swg sync.WaitGroup + swg.Add(1) + + go func() { + defer swg.Done() + if sigModel.SignatureReferenceType == "user" { + if signatureUserCompanyID != "" { + dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, signatureUserCompanyID) + if companyErr != nil { + log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", signatureUserCompanyID, companyErr) + } else { + companyName = dbCompanyModel.CompanyName + companySigningEntityName = dbCompanyModel.SigningEntityName + } + } + } else if sigModel.SignatureReferenceType == "company" { + dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, sigModel.SignatureReferenceID) + if companyErr != nil { + log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", sigModel.SignatureReferenceID, companyErr) + } else { + companyName = dbCompanyModel.CompanyName + companySigningEntityName = dbCompanyModel.SigningEntityName + } + } + }() + swg.Wait() + + sigModel.CompanyName = companyName + sigModel.SigningEntityName = companySigningEntityName + }(sig, dbSignature.SignatureUserCompanyID) + } + + wg.Wait() + return sigs, nil +} + +// buildResponse is a helper function which converts a database model to a GitHub organization response model +func buildResponse(items []*dynamodb.AttributeValue) []models.GithubOrg { + // Convert to a response model + var orgs []models.GithubOrg + for _, org := range items { + selected := true + orgs = append(orgs, models.GithubOrg{ + ID: org.S, + Selected: &selected, + }) + } + + return orgs +} + +// buildApprovalAttributeList builds the updated approval list based on the added and removed values +func buildApprovalAttributeList(ctx context.Context, existingList, addEntries, removeEntries []string) *dynamodb.AttributeValue { + f := logrus.Fields{ + "functionName": "buildApprovalAttributeList", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + var updatedList []string + log.WithFields(f).Debugf("buildApprovalAttributeList - existing: %+v, add entries: %+v, remove entries: %+v", + existingList, addEntries, removeEntries) + + // Add the existing entries to our response + for _, value := range existingList { + // No duplicates allowed + if !utils.StringInSlice(value, updatedList) { + log.WithFields(f).Debugf("buildApprovalAttributeList - adding existing entry: %s", value) + updatedList = append(updatedList, strings.TrimSpace(value)) + } else { + log.WithFields(f).Debugf("buildApprovalAttributeList - skipping existing entry: %s", value) + } + } + + // For all the new values... + for _, value := range addEntries { + // No duplicates allowed + if !utils.StringInSlice(value, updatedList) { + log.WithFields(f).Debugf("buildApprovalAttributeList - adding new entry: %s", value) + updatedList = append(updatedList, strings.TrimSpace(value)) + } else { + log.WithFields(f).Debugf("buildApprovalAttributeList - skipping new entry: %s", value) + } + } + + // Remove the items + log.WithFields(f).Debugf("buildApprovalAttributeList - before: %+v - removing entries: %+v", updatedList, removeEntries) + updatedList = utils.RemoveItemsFromList(updatedList, removeEntries) + log.WithFields(f).Debugf("buildApprovalAttributeList - after: %+v - removing entries: %+v", updatedList, removeEntries) + + // Remove any duplicates - shouldn't have any if checked before adding + log.WithFields(f).Debugf("buildApprovalAttributeList - before: %+v - removing duplicates", updatedList) + updatedList = utils.RemoveDuplicates(updatedList) + log.WithFields(f).Debugf("buildApprovalAttributeList - after: %+v - removing duplicates", updatedList) + + // Convert to the response type + var responseList []*dynamodb.AttributeValue + for _, value := range updatedList { + responseList = append(responseList, &dynamodb.AttributeValue{S: aws.String(value)}) + } + + return &dynamodb.AttributeValue{L: responseList} +} + +// buildCompanyIDList is a helper function to convert the DB response models into a simple list of company IDs +func (repo repository) buildCompanyIDList(ctx context.Context, results *dynamodb.QueryOutput) ([]SignatureCompanyID, error) { + f := logrus.Fields{ + "functionName": "buildCompanyIDList", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + var response []SignatureCompanyID + + // The DB signature model + var dbSignatures []ItemSignature + err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) + if err != nil { + log.WithFields(f).Warnf("error unmarshalling signatures from database, error: %v", err) + return nil, err + } + + // Loop and extract the company ID (signature_reference_id) value + for _, item := range dbSignatures { + // Lookup the company by ID - try to get more information like the external ID and name + companyModel, companyLookupErr := repo.companyRepo.GetCompany(ctx, item.SignatureReferenceID) + // Start building a model for this entry in the list + signatureCompanyID := SignatureCompanyID{ + SignatureID: item.SignatureID, + CompanyID: item.SignatureReferenceID, + } + + if companyLookupErr != nil || companyModel == nil { + log.WithFields(f).Warnf("problem looking up company using id: %s, error: %+v", + item.SignatureReferenceID, companyLookupErr) + response = append(response, signatureCompanyID) + } else { + if companyModel.CompanyExternalID != "" { + signatureCompanyID.CompanySFID = companyModel.CompanyExternalID + } + if companyModel.CompanyName != "" { + signatureCompanyID.CompanyName = companyModel.CompanyName + } + response = append(response, signatureCompanyID) + } + } + + return response, nil +} diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index 6cc246320..e541edb55 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -8,6 +8,8 @@ import ( "fmt" "net/http" + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/models" "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" @@ -207,7 +209,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d } if signatureModel != nil { projectID = signatureModel.ProjectID - companyID = signatureModel.SignatureReferenceID.String() + companyID = signatureModel.SignatureReferenceID } eventsService.LogEvent(&events.LogEventArgs{ EventType: events.ApprovalListGitHubOrganizationAdded, @@ -257,7 +259,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d } if signatureModel != nil { projectID = signatureModel.ProjectID - companyID = signatureModel.SignatureReferenceID.String() + companyID = signatureModel.SignatureReferenceID } eventsService.LogEvent(&events.LogEventArgs{ @@ -288,6 +290,32 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d return signatures.NewGetProjectSignaturesOK().WithXRequestID(reqID).WithPayload(projectSignatures) }) + api.SignaturesCreateProjectSummaryReportHandler = signatures.CreateProjectSummaryReportHandlerFunc(func(params signatures.CreateProjectSummaryReportParams, claUser *user.CLAUser) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "signature.handlers.SignaturesCreateProjectSummaryReportHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "claType": utils.StringValue(params.ClaType), + "signatureType": utils.StringValue(params.SignatureType), + "nextKey": utils.StringValue(params.NextKey), + "searchField": utils.StringValue(params.SearchField), + "searchTerm": utils.StringValue(params.SearchTerm), + "sortOrder": utils.StringValue(params.SortOrder), + "fullMatch": utils.BoolValue(params.FullMatch), + "pageSize": utils.Int64Value(params.PageSize), + } + projectSummaryReport, err := service.CreateProjectSummaryReport(ctx, params) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error creating project summary report for projectID: %s, error: %+v", + params.ProjectID, err) + return signatures.NewGetProjectSignaturesBadRequest().WithPayload(errorResponse(err)) + } + + return signatures.NewCreateProjectSummaryReportOK().WithXRequestID(reqID).WithPayload(projectSummaryReport) + }) + // Get Project Company Signatures api.SignaturesGetProjectCompanySignaturesHandler = signatures.GetProjectCompanySignaturesHandlerFunc(func(params signatures.GetProjectCompanySignaturesParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index dd69e310c..457488782 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -9,9 +9,6 @@ import ( "fmt" "sort" "strings" - "sync" - - "github.com/go-openapi/strfmt" "github.com/sirupsen/logrus" @@ -60,7 +57,8 @@ type SignatureRepository interface { GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) GetSignatureACL(ctx context.Context, signatureID string) ([]string, error) - GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams, pageSize int64) (*models.Signatures, error) + GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) + CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, pageSize int64) (*models.Signatures, error) @@ -663,9 +661,9 @@ func addConditionToFilter(filter expression.ConditionBuilder, cond expression.Co } // GetProjectSignatures returns a list of signatures for the specified project -func (repo repository) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams, pageSize int64) (*models.Signatures, error) { +func (repo repository) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "GetProjectSignatures", + "functionName": "signatures.repository.GetProjectSignatures", "tableName": repo.signatureTableName, utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ProjectID, @@ -731,7 +729,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(params.SignatureType)) filter = addConditionToFilter(filter, signatureTypeExpression, &filterAdded) } - if *params.SignatureType == "ccla" { + if *params.SignatureType == utils.ClaTypeCCLA { signatureReferenceIDExpression := expression.Name("signature_reference_id").AttributeExists() signatureUserCclaCompanyIDExpression := expression.Name("signature_user_ccla_company_id").AttributeNotExists() filter = addConditionToFilter(filter, signatureReferenceIDExpression, &filterAdded) @@ -855,7 +853,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur totalCount := *describeTableResult.Table.ItemCount if int64(len(sigs)) > realPageSize { sigs = sigs[0:realPageSize] - lastEvaluatedKey = sigs[realPageSize-1].SignatureID.String() + lastEvaluatedKey = sigs[realPageSize-1].SignatureID } return &models.Signatures{ @@ -867,6 +865,225 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur }, nil } +// CreateProjectSummaryReport generates a project summary report based on the specified input +func (repo repository) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) { // nolint + f := logrus.Fields{ + "functionName": "signatures.repository.CreateProjectSummaryReport", + "tableName": repo.signatureTableName, + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": params.ProjectID, + "signatureType": aws.StringValue(params.SignatureType), + "searchField": aws.StringValue(params.SearchField), + "searchTerm": aws.StringValue(params.SearchTerm), + "fullMatch": aws.BoolValue(params.FullMatch), + "pageSize": aws.Int64Value(params.PageSize), + "nextKey": aws.StringValue(params.NextKey), + "sortOrder": aws.StringValue(params.SortOrder), + "companyIDList": params.Body, + } + + indexName := SignatureProjectIDIndex + if params.SortOrder != nil && *params.SortOrder != "" { + indexName = SignatureProjectDateIDIndex + } + + realPageSize := int64(100) + if params.PageSize != nil && *params.PageSize > 0 { + realPageSize = *params.PageSize + } + + // This is the key we want to match + condition := expression.Key("signature_project_id").Equal(expression.Value(params.ProjectID)) + + builder := expression.NewBuilder().WithProjection(buildProjection()) + var filter expression.ConditionBuilder + var filterAdded bool + + if params.ClaType != nil { + filterAdded = true + if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { + filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). + And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). + And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + + } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { + filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). + And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). + And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_user_ccla_company_id").AttributeExists()) + } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { + filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)). + And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany))). + And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + } + } else { + if params.SearchField != nil { + searchFieldExpression := expression.Name("signature_reference_type").Equal(expression.Value(params.SearchField)) + filter = addConditionToFilter(filter, searchFieldExpression, &filterAdded) + } + + if params.SignatureType != nil { + if params.SearchTerm != nil && (params.FullMatch != nil && !*params.FullMatch) { + indexName = SignatureProjectIDTypeIndex + condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(*params.SignatureType)))) + } else { + signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(params.SignatureType)) + filter = addConditionToFilter(filter, signatureTypeExpression, &filterAdded) + } + if *params.SignatureType == utils.ClaTypeCCLA { + signatureReferenceIDExpression := expression.Name("signature_reference_id").AttributeExists() + signatureUserCclaCompanyIDExpression := expression.Name("signature_user_ccla_company_id").AttributeNotExists() + filter = addConditionToFilter(filter, signatureReferenceIDExpression, &filterAdded) + filter = addConditionToFilter(filter, signatureUserCclaCompanyIDExpression, &filterAdded) + } + } + + if params.SearchTerm != nil { + if *params.FullMatch { + indexName = SignatureReferenceSearchIndex + condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(*params.SearchTerm)))) + } else { + searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*params.SearchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*params.SearchTerm))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + } + } + + // Filter condition to cater for approved and signed signatures + signatureApprovedExpression := expression.Name("signature_approved").Equal(expression.Value(true)) + filter = addConditionToFilter(filter, signatureApprovedExpression, &filterAdded) + + signatureSignedExpression := expression.Name("signature_signed").Equal(expression.Value(true)) + filter = addConditionToFilter(filter, signatureSignedExpression, &filterAdded) + } + + if len(params.Body) > 0 { + // expression.Name("Color").In(expression.Value("red"), expression.Value("green"), expression.Value("blue")) + var referenceIDExpressions []expression.OperandBuilder + for _, value := range params.Body { + referenceIDExpressions = append(referenceIDExpressions, expression.Value(value)) + } + if len(referenceIDExpressions) == 1 { + filter = addConditionToFilter(filter, expression.Name("signature_reference_id").In(referenceIDExpressions[0]), &filterAdded) + } else if len(referenceIDExpressions) > 1 { + filter = addConditionToFilter(filter, expression.Name("signature_reference_id").In(referenceIDExpressions[0], referenceIDExpressions[1:]...), &filterAdded) + } + } + + if filterAdded { + builder = builder.WithFilter(filter) + } + builder = builder.WithKeyCondition(condition) + + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + log.WithFields(f).Warnf("error building expression for project signature query, projectID: %s, error: %v", + params.ProjectID, err) + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.signatureTableName), + Limit: aws.Int64(realPageSize), // The maximum number of items to evaluate (not necessarily the number of matching items) + IndexName: aws.String(indexName), // Name of a secondary index to scan + } + f["indexName"] = indexName + + // If we have the next key, set the exclusive start key value + if params.NextKey != nil { + log.WithFields(f).Debugf("received a nextKey, value: %s", *params.NextKey) + // The primary key of the first item that this operation will evaluate. + // and the query key (if not the same) + queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "signature_id": { + S: params.NextKey, + }, + "signature_project_id": { + S: ¶ms.ProjectID, + }, + } + if params.FullMatch != nil && *params.FullMatch && params.SearchTerm != nil { + queryInput.ExclusiveStartKey["signature_reference_name_lower"] = &dynamodb.AttributeValue{ + S: params.SearchTerm, + } + } + } + + sigs := make([]*models.SignatureSummary, 0) + var lastEvaluatedKey string + + // Loop until we have all the records + for ok := true; ok; ok = lastEvaluatedKey != "" { + // Make the DynamoDB Query API call + log.WithFields(f).Debugf("Running signature project query using queryInput: %+v", queryInput) + results, errQuery := repo.dynamoDBClient.Query(queryInput) + if errQuery != nil { + log.WithFields(f).Warnf("error retrieving project signature ID for project: %s, error: %v", + params.ProjectID, errQuery) + return nil, errQuery + } + + // Convert the list of DB models to a list of response models + signatureList, modelErr := repo.buildProjectSignatureSummaryModels(ctx, results, params.ProjectID) + if modelErr != nil { + log.WithFields(f).Warnf("error converting DB model to response model for signatures with project %s, error: %v", + params.ProjectID, modelErr) + return nil, modelErr + } + + // Add to the signatures response model to the list + sigs = append(sigs, signatureList...) + + //log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey) + if results.LastEvaluatedKey["signature_id"] != nil { + lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S + queryInput.ExclusiveStartKey = results.LastEvaluatedKey + } else { + lastEvaluatedKey = "" + } + + if int64(len(sigs)) >= realPageSize { + break + } + } + + // How many total records do we have - may not be up-to-date as this value is updated only periodically + describeTableInput := &dynamodb.DescribeTableInput{ + TableName: &repo.signatureTableName, + } + describeTableResult, err := repo.dynamoDBClient.DescribeTable(describeTableInput) + if err != nil { + log.WithFields(f).Warnf("error retrieving total record count for project: %s, error: %v", params.ProjectID, err) + return nil, err + } + + // Meta-data for the response + totalCount := *describeTableResult.Table.ItemCount + if int64(len(sigs)) > realPageSize { + sigs = sigs[0:realPageSize] + lastEvaluatedKey = sigs[realPageSize-1].SignatureID + } + + return &models.SignatureReport{ + ProjectID: params.ProjectID, + ResultCount: int64(len(sigs)), + TotalCount: totalCount, + LastKeyScanned: lastEvaluatedKey, + Signatures: sigs, + }, nil +} + // GetProjectCompanySignature returns a the signature for the specified project and specified company with the other query flags func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { f := logrus.Fields{ @@ -1258,7 +1475,7 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, totalCount := *describeTableResult.Table.ItemCount if int64(len(sigs)) > pageSize { sigs = sigs[0:pageSize] - lastEvaluatedKey = sigs[pageSize-1].SignatureID.String() + lastEvaluatedKey = sigs[pageSize-1].SignatureID } return &models.Signatures{ @@ -1373,7 +1590,7 @@ func (repo repository) GetCompanySignatures(ctx context.Context, params signatur } if int64(len(sigs)) > pageSize { sigs = sigs[0:pageSize] - lastEvaluatedKey = sigs[pageSize-1].SignatureID.String() + lastEvaluatedKey = sigs[pageSize-1].SignatureID } // Meta-data for the response @@ -1761,7 +1978,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID.String(), columnName) + sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", columnName, companyID, projectID, signed, approved) @@ -1782,7 +1999,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID.String(), columnName) + sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", columnName, companyID, projectID, signed, approved) @@ -1803,7 +2020,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID.String(), columnName) + sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", columnName, companyID, projectID, signed, approved) @@ -1824,7 +2041,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID.String(), columnName) + sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", columnName, companyID, projectID, signed, approved) @@ -1854,7 +2071,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan TableName: aws.String(repo.signatureTableName), Key: map[string]*dynamodb.AttributeValue{ "signature_id": { - S: aws.String(sig.SignatureID.String()), + S: aws.String(sig.SignatureID), }, }, ExpressionAttributeNames: expressionAttributeNames, @@ -2073,260 +2290,6 @@ func (repo repository) AddSignedOn(ctx context.Context, signatureID string) erro return nil } -// buildProjectSignatureModels converts the response model into a response data model -func (repo repository) buildProjectSignatureModels(ctx context.Context, results *dynamodb.QueryOutput, projectID string, loadACLDetails bool) ([]*models.Signature, error) { - f := logrus.Fields{ - "functionName": "buildProjectSignatureModels", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectID": projectID, - } - var sigs []*models.Signature - - // The DB signature model - var dbSignatures []ItemSignature - - err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) - if err != nil { - log.WithFields(f).Warnf("error unmarshalling signatures from database for project: %s, error: %v", - projectID, err) - return nil, err - } - - var wg sync.WaitGroup - wg.Add(len(dbSignatures)) - for _, dbSignature := range dbSignatures { - - // Set the signature type in the response - var claType = "" - // Corporate Signature - if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeCompany && dbSignature.SignatureType == utils.SignatureTypeCCLA { - claType = utils.ClaTypeCCLA - } - // Employee Signature - if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeUser && dbSignature.SignatureType == utils.SignatureTypeCLA && dbSignature.SignatureUserCompanyID != "" { - claType = utils.ClaTypeECLA - } - - // Individual Signature - if dbSignature.SignatureReferenceType == utils.SignatureReferenceTypeUser && dbSignature.SignatureType == utils.SignatureTypeCLA && dbSignature.SignatureUserCompanyID == "" { - claType = utils.ClaTypeICLA - } - - sig := &models.Signature{ - SignatureID: strfmt.UUID4(dbSignature.SignatureID), - ClaType: claType, - SignatureCreated: dbSignature.DateCreated, - SignatureModified: dbSignature.DateModified, - SignatureType: dbSignature.SignatureType, - SignatureReferenceID: strfmt.UUID4(dbSignature.SignatureReferenceID), - SignatureReferenceName: dbSignature.SignatureReferenceName, - SignatureReferenceNameLower: dbSignature.SignatureReferenceNameLower, - SignatureSigned: dbSignature.SignatureSigned, - SignatureApproved: dbSignature.SignatureApproved, - SignatureMajorVersion: dbSignature.SignatureDocumentMajorVersion, - SignatureMinorVersion: dbSignature.SignatureDocumentMinorVersion, - Version: dbSignature.SignatureDocumentMajorVersion + "." + dbSignature.SignatureDocumentMinorVersion, - SignatureReferenceType: dbSignature.SignatureReferenceType, - ProjectID: dbSignature.SignatureProjectID, - Created: dbSignature.DateCreated, - Modified: dbSignature.DateModified, - EmailApprovalList: dbSignature.EmailWhitelist, - DomainApprovalList: dbSignature.DomainWhitelist, - GithubUsernameApprovalList: dbSignature.GitHubWhitelist, - GithubOrgApprovalList: dbSignature.GitHubOrgWhitelist, - UserName: dbSignature.UserName, - UserLFID: dbSignature.UserLFUsername, - UserGHID: dbSignature.UserGithubUsername, - SignedOn: dbSignature.SignedOn, - SignatoryName: dbSignature.SignatoryName, - UserDocusignName: dbSignature.UserDocusignName, - UserDocusignDateSigned: dbSignature.UserDocusignDateSigned, - } - sigs = append(sigs, sig) - go func(sigModel *models.Signature, signatureUserCompanyID string, sigACL []string) { - defer wg.Done() - var companyName = "" - var userName = "" - var userLFID = "" - var userGHID = "" - var userGHUsername = "" - var swg sync.WaitGroup - swg.Add(2) - - go func() { - defer swg.Done() - if sigModel.SignatureReferenceType == "user" { - userModel, userErr := repo.usersRepo.GetUser(sigModel.SignatureReferenceID.String()) - if userErr != nil || userModel == nil { - log.WithFields(f).Warnf("unable to lookup user using id: %s, error: %v", sigModel.SignatureReferenceID, userErr) - } else { - userName = userModel.Username - userLFID = userModel.LfUsername - userGHID = userModel.GithubID - userGHUsername = userModel.GithubUsername - } - - if signatureUserCompanyID != "" { - dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, signatureUserCompanyID) - if companyErr != nil { - log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", signatureUserCompanyID, companyErr) - } else { - companyName = dbCompanyModel.CompanyName - } - } - } else if sigModel.SignatureReferenceType == "company" { - dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, sigModel.SignatureReferenceID.String()) - if companyErr != nil { - log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", sigModel.SignatureReferenceID, companyErr) - } else { - companyName = dbCompanyModel.CompanyName - } - } - }() - - var signatureACL []models.User - go func() { - defer swg.Done() - for _, userName := range sigACL { - if loadACLDetails { - userModel, userErr := repo.usersRepo.GetUserByUserName(userName, true) - if userErr != nil { - log.WithFields(f).Warnf("unable to lookup user using username: %s, error: %v", userName, userErr) - } else { - if userModel == nil { - log.WithFields(f).Warnf("User looking for username is null: %s for signature: %s", userName, sigModel.SignatureID) - } else { - signatureACL = append(signatureACL, *userModel) - } - } - } else { - signatureACL = append(signatureACL, models.User{LfUsername: userName}) - } - } - }() - swg.Wait() - sigModel.CompanyName = companyName - sigModel.UserName = userName - sigModel.UserLFID = userLFID - sigModel.UserGHID = userGHID - sigModel.UserGHUsername = userGHUsername - sigModel.SignatureACL = signatureACL - }(sig, dbSignature.SignatureUserCompanyID, dbSignature.SignatureACL) - } - wg.Wait() - return sigs, nil -} - -// buildResponse is a helper function which converts a database model to a GitHub organization response model -func buildResponse(items []*dynamodb.AttributeValue) []models.GithubOrg { - // Convert to a response model - var orgs []models.GithubOrg - for _, org := range items { - selected := true - orgs = append(orgs, models.GithubOrg{ - ID: org.S, - Selected: &selected, - }) - } - - return orgs -} - -// buildApprovalAttributeList builds the updated approval list based on the added and removed values -func buildApprovalAttributeList(ctx context.Context, existingList, addEntries, removeEntries []string) *dynamodb.AttributeValue { - f := logrus.Fields{ - "functionName": "buildApprovalAttributeList", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - } - var updatedList []string - log.WithFields(f).Debugf("buildApprovalAttributeList - existing: %+v, add entries: %+v, remove entries: %+v", - existingList, addEntries, removeEntries) - - // Add the existing entries to our response - for _, value := range existingList { - // No duplicates allowed - if !utils.StringInSlice(value, updatedList) { - log.WithFields(f).Debugf("buildApprovalAttributeList - adding existing entry: %s", value) - updatedList = append(updatedList, strings.TrimSpace(value)) - } else { - log.WithFields(f).Debugf("buildApprovalAttributeList - skipping existing entry: %s", value) - } - } - - // For all the new values... - for _, value := range addEntries { - // No duplicates allowed - if !utils.StringInSlice(value, updatedList) { - log.WithFields(f).Debugf("buildApprovalAttributeList - adding new entry: %s", value) - updatedList = append(updatedList, strings.TrimSpace(value)) - } else { - log.WithFields(f).Debugf("buildApprovalAttributeList - skipping new entry: %s", value) - } - } - - // Remove the items - log.WithFields(f).Debugf("buildApprovalAttributeList - before: %+v - removing entries: %+v", updatedList, removeEntries) - updatedList = utils.RemoveItemsFromList(updatedList, removeEntries) - log.WithFields(f).Debugf("buildApprovalAttributeList - after: %+v - removing entries: %+v", updatedList, removeEntries) - - // Remove any duplicates - shouldn't have any if checked before adding - log.WithFields(f).Debugf("buildApprovalAttributeList - before: %+v - removing duplicates", updatedList) - updatedList = utils.RemoveDuplicates(updatedList) - log.WithFields(f).Debugf("buildApprovalAttributeList - after: %+v - removing duplicates", updatedList) - - // Convert to the response type - var responseList []*dynamodb.AttributeValue - for _, value := range updatedList { - responseList = append(responseList, &dynamodb.AttributeValue{S: aws.String(value)}) - } - - return &dynamodb.AttributeValue{L: responseList} -} - -// buildCompanyIDList is a helper function to convert the DB response models into a simple list of company IDs -func (repo repository) buildCompanyIDList(ctx context.Context, results *dynamodb.QueryOutput) ([]SignatureCompanyID, error) { - f := logrus.Fields{ - "functionName": "buildCompanyIDList", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - } - var response []SignatureCompanyID - - // The DB signature model - var dbSignatures []ItemSignature - err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) - if err != nil { - log.WithFields(f).Warnf("error unmarshalling signatures from database, error: %v", err) - return nil, err - } - - // Loop and extract the company ID (signature_reference_id) value - for _, item := range dbSignatures { - // Lookup the company by ID - try to get more information like the external ID and name - companyModel, companyLookupErr := repo.companyRepo.GetCompany(ctx, item.SignatureReferenceID) - // Start building a model for this entry in the list - signatureCompanyID := SignatureCompanyID{ - SignatureID: item.SignatureID, - CompanyID: item.SignatureReferenceID, - } - - if companyLookupErr != nil || companyModel == nil { - log.WithFields(f).Warnf("problem looking up company using id: %s, error: %+v", - item.SignatureReferenceID, companyLookupErr) - response = append(response, signatureCompanyID) - } else { - if companyModel.CompanyExternalID != "" { - signatureCompanyID.CompanySFID = companyModel.CompanyExternalID - } - if companyModel.CompanyName != "" { - signatureCompanyID.CompanyName = companyModel.CompanyName - } - response = append(response, signatureCompanyID) - } - } - - return response, nil -} - func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) { f := logrus.Fields{ "functionName": "GetClaGroupICLASignatures", diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 757253e6d..8d2de5219 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -36,6 +36,7 @@ type SignatureService interface { GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) + CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, params signatures.GetProjectCompanySignaturesParams) (*models.Signatures, error) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams) (*models.Signatures, error) @@ -94,13 +95,18 @@ func (s service) GetCorporateSignature(ctx context.Context, claGroupID, companyI // GetProjectSignatures returns the list of signatures associated with the specified project func (s service) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) { - const defaultPageSize int64 = 10 - var pageSize = defaultPageSize - if params.PageSize != nil { - pageSize = *params.PageSize + projectSignatures, err := s.repo.GetProjectSignatures(ctx, params) + if err != nil { + return nil, err } - projectSignatures, err := s.repo.GetProjectSignatures(ctx, params, pageSize) + return projectSignatures, nil +} + +// CreateProjectSummaryReport generates a project summary report based on the specified input +func (s service) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) { + + projectSignatures, err := s.repo.CreateProjectSummaryReport(ctx, params) if err != nil { return nil, err } @@ -464,7 +470,7 @@ func (s service) InvalidateProjectRecords(ctx context.Context, projectID string, log.WithFields(f).Warnf("Unable to update signature: %s with project name: %s, error: %v", sigID, projName, updateErr) } - }(signature.SignatureID.String(), projectName) + }(signature.SignatureID, projectName) } // Wait until all the workers are done @@ -799,10 +805,12 @@ func (s service) GetClaGroupICLASignatures(ctx context.Context, claGroupID strin } func (s service) GetClaGroupCCLASignatures(ctx context.Context, claGroupID string) (*models.Signatures, error) { + pageSize := utils.Int64(1000) return s.repo.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ ClaType: aws.String(utils.ClaTypeCCLA), ProjectID: claGroupID, - }, 1000) + PageSize: pageSize, + }) } func (s service) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) { diff --git a/cla-backend-go/swagger/cla.v1.yaml b/cla-backend-go/swagger/cla.v1.yaml index cff74f9bb..b291bb278 100644 --- a/cla-backend-go/swagger/cla.v1.yaml +++ b/cla-backend-go/swagger/cla.v1.yaml @@ -374,6 +374,49 @@ paths: tags: - signatures + /signatures/project/{projectID}/summary-report: + post: + summary: Creates a summary report + description: Creates a summary report when provided the project ID + security: + - OauthSecurity: [ ] + operationId: createProjectSummaryReport + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/path-projectID" + - $ref: '#/parameters/pageSize' + - $ref: '#/parameters/nextKey' + - $ref: '#/parameters/searchTerm' + - $ref: '#/parameters/searchField' + - $ref: '#/parameters/fullMatch' + - $ref: '#/parameters/signatureType' + - $ref: '#/parameters/claType' + - $ref: '#/parameters/sortOrder' + - name: body + in: body + schema: + $ref: '#/definitions/company-id-list' + required: false + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/signature-report' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + tags: + - signatures + /signatures/project/{projectID}/company/{companyID}: get: summary: Get Project Company Signatures @@ -1160,6 +1203,7 @@ paths: parameters: - $ref: "#/parameters/x-request-id" - $ref: '#/parameters/companyName' + - $ref: '#/parameters/include-signing-entity-name' - name: websiteName in: query type: string @@ -2597,6 +2641,13 @@ parameters: those are the available fields to use

+ include-signing-entity-name: + name: include-signing-entity-name + in: query + type: boolean + default: false + required: false + definitions: version: $ref: './common/version.yaml' @@ -2669,6 +2720,12 @@ definitions: github-org: $ref: './common/github-org.yaml' + company-id-list: + type: array + description: A list of company internal IDs + items: + type: string + company-invite-user: type: object x-nullable: false @@ -2876,6 +2933,10 @@ definitions: $ref: './common/signatures.yaml' signature: $ref: './common/signature.yaml' + signature-report: + $ref: './common/signature-report.yaml' + signature-summary: + $ref: './common/signature-summary.yaml' approval-list: $ref: './common/signature-approval-list.yaml' diff --git a/cla-backend-go/swagger/common/signature-report.yaml b/cla-backend-go/swagger/common/signature-report.yaml new file mode 100644 index 000000000..ad3f7d7a8 --- /dev/null +++ b/cla-backend-go/swagger/common/signature-report.yaml @@ -0,0 +1,25 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +x-nullable: false +title: Signature Report +description: Signature Report +properties: + projectID: + type: string + resultCount: + type: integer + format: int64 + x-omitempty: false + totalCount: + type: integer + format: int64 + x-omitempty: false + lastKeyScanned: + type: string + signatures: + type: array + x-omitempty: false + items: + $ref: '#/definitions/signature-summary' diff --git a/cla-backend-go/swagger/common/signature-summary.yaml b/cla-backend-go/swagger/common/signature-summary.yaml new file mode 100644 index 000000000..9530bcfcd --- /dev/null +++ b/cla-backend-go/swagger/common/signature-summary.yaml @@ -0,0 +1,62 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +title: A signature summary model +description: A signature summary model +properties: + signatureID: + description: the signature ID for a compnay record + $ref: './common/properties/internal-id.yaml' + projectID: + description: the CLA Group ID + $ref: './common/properties/internal-id.yaml' + claType: + type: string + description: > + CLA Type field - identifies the specify signature type - individual, employee or corporate signature, valid options: + * `icla` - for individual contributor signature records (individuals not associated with a corporation) + * `ecla` - for employee contributor signature records (acknowledgements from corporate contributors) + * `ccla` - for corporate contributor signature records (created by CLA Signatories and managed by CLA Managers) + enum: [ icla,ecla,ccla ] + signatureSigned: + type: boolean + description: the signature signed flag - true or false value + example: true + signatureApproved: + type: boolean + description: the signature approved flag - true or false value + example: true + signatureReferenceType: + type: string + description: the signature reference type - either user or company + example: 'user' + minLength: 2 + maxLength: 12 + signatureReferenceID: + description: the signature reference ID which references a compnay ID or user ID + $ref: './common/properties/internal-id.yaml' + signatureReferenceName: + type: string + signatureReferenceNameLower: + type: string + signatureType: + type: string + description: the signature type - either cla or ccla + example: 'ccla' + minLength: 2 + maxLength: 12 + signedOn: + type: string + signatoryName: + type: string + companyName: + $ref: './common/properties/company-name.yaml' + signingEntityName: + $ref: './common/properties/company-signing-entity-name.yaml' + userDocusignName: + type: string + description: full name used on docusign document + userDocusignDateSigned: + type: string + description: docusign signature date diff --git a/cla-backend-go/swagger/common/signature.yaml b/cla-backend-go/swagger/common/signature.yaml index 27d1e5489..5413f8a8b 100644 --- a/cla-backend-go/swagger/common/signature.yaml +++ b/cla-backend-go/swagger/common/signature.yaml @@ -6,10 +6,8 @@ title: A signature model description: A signature - may be an ICLA or CCLA signature properties: signatureID: - type: string description: the signature ID - example: 'c71c469a-55ea-492d-9722-fd30b31da2aa' - format: uuid4 + $ref: './common/properties/internal-id.yaml' claType: type: string description: > @@ -45,10 +43,8 @@ properties: minLength: 2 maxLength: 12 signatureReferenceID: - type: string description: the signature reference ID which references a compnay ID or user ID - example: 'c71c469a-55ea-492d-9722-fd30b31da2aa' - format: uuid4 + $ref: './common/properties/internal-id.yaml' signatureReferenceName: type: string signatureReferenceNameLower: @@ -70,9 +66,9 @@ properties: userName: type: string companyName: - type: string - description: the company name - pattern: '^([\w\p{L}][\w\s\p{L}()\[\]+\-/%!@#$]*){2,255}$' + $ref: './common/properties/company-name.yaml' + signingEntityName: + $ref: './common/properties/company-signing-entity-name.yaml' projectID: type: string description: the CLA Group ID diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index fd4d822b3..43da5a36f 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -617,9 +617,15 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo api.CompanySearchCompanyLookupHandler = company.SearchCompanyLookupHandlerFunc(func(params company.SearchCompanyLookupParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "company.handlers.CompanyGetCompanyByInternalIDHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyName": params.CompanyName, + "websiteName": params.WebsiteName, + } if params.CompanyName == nil && params.WebsiteName == nil { - log.Debugf("CompanyName or WebsiteName atleast one required") + log.WithFields(f).Debugf("CompanyName or WebsiteName at least one required") return company.NewSearchCompanyLookupBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, errors.New("companyName or websiteName at least one required"))) } @@ -628,7 +634,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo result, err := service.GetCompanyLookup(ctx, companyName, websiteName) if err != nil { msg := fmt.Sprintf("error occured while search orgname %s, websitename %s", companyName, websiteName) - log.Warnf("error occured while search orgname %s, websitename %s. error = %s", companyName, websiteName, err.Error()) + log.WithFields(f).WithError(err).Warnf("error occured while search orgname %s, websitename %s. error = %s", companyName, websiteName, err.Error()) if _, ok := err.(*organizations.LookupNotFound); ok { return company.NewSearchCompanyLookupNotFound().WithXRequestID(reqID).WithPayload( utils.ErrorResponseNotFoundWithError(reqID, msg, err)) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 19dd56f21..20231955e 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1232,7 +1232,7 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 activeCla.ClaGroupName = cg.ClaGroupName activeCla.CompanyID = companyID activeCla.CompanySfid = v1CompanyModel.CompanyExternalID - activeCla.SignatureID = sig.SignatureID.String() + activeCla.SignatureID = sig.SignatureID // fill details from project service activeCla.ProjectName = cg.ProjectName @@ -1248,7 +1248,7 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 go func() { var err error defer cwg.Done() - cclaURL, err = utils.GetDownloadLink(utils.SignedCLAFilename(sig.ProjectID, sig.SignatureType, sig.SignatureReferenceID.String(), sig.SignatureID.String())) + cclaURL, err = utils.GetDownloadLink(utils.SignedCLAFilename(sig.ProjectID, sig.SignatureType, sig.SignatureReferenceID, sig.SignatureID)) if err != nil { log.Error("fillActiveCLA : unable to get ccla s3 link", err) return @@ -1314,7 +1314,7 @@ func (s *service) filterClaProjects(ctx context.Context, projects []*v2ProjectSe func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepository, sig *v1Models.Signature, result chan *models.CorporateContributor, searchTerm string) { defer wg.Done() - user, err := usersRepo.GetUser(sig.SignatureReferenceID.String()) + user, err := usersRepo.GetUser(sig.SignatureReferenceID) if err != nil { log.Error("fillCorporateContributorModel: unable to get user info", err) return diff --git a/cla-backend-go/v2/dynamo_events/cla_manager.go b/cla-backend-go/v2/dynamo_events/cla_manager.go index 4b0d9c49f..917e418cb 100644 --- a/cla-backend-go/v2/dynamo_events/cla_manager.go +++ b/cla-backend-go/v2/dynamo_events/cla_manager.go @@ -115,10 +115,10 @@ func (s *service) SetInitialCLAManagerACSPermissions(ctx context.Context, signat } log.WithFields(f).Debug("locating company record by signature reference ID...") - company, err := s.companyRepo.GetCompany(ctx, sig.SignatureReferenceID.String()) + company, err := s.companyRepo.GetCompany(ctx, sig.SignatureReferenceID) if err != nil { log.WithFields(f).Warnf("unable to lookup company by signature reference ID: %s, error: %+v", - sig.SignatureReferenceID.String(), err) + sig.SignatureReferenceID, err) return err } diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index 13cfeaa5f..d241c4c7a 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -408,7 +408,7 @@ func (s *service) addCLAManagerPermissions(claGroupID, projectSFID string) error ClaType: aws.String(utils.ClaTypeCCLA), PageSize: aws.Int64(1000), ProjectID: claGroupID, - }, 1000) + }) if err != nil { log.WithFields(f).WithError(err).Warnf("problem querying CCLA signatures for CLA Group - skipping %s role review/assignment for this project", utils.CLAManagerRole) return err @@ -434,7 +434,7 @@ func (s *service) addCLAManagerPermissions(claGroupID, projectSFID string) error // Make sure we can load the company and grab the SFID sig := sig - companyInternalID := sig.SignatureReferenceID.String() + companyInternalID := sig.SignatureReferenceID log.WithFields(f).Debugf("locating company by internal ID: %s", companyInternalID) companyModel, err := s.companyRepo.GetCompany(ctx, companyInternalID) if err != nil { diff --git a/cla-backend-go/v2/signatures/converters.go b/cla-backend-go/v2/signatures/converters.go index cef91da0d..fe87fc318 100644 --- a/cla-backend-go/v2/signatures/converters.go +++ b/cla-backend-go/v2/signatures/converters.go @@ -11,7 +11,6 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/go-openapi/strfmt" "github.com/jinzhu/copier" "github.com/sirupsen/logrus" ) @@ -43,8 +42,8 @@ func v2SignaturesReplaceCompanyID(src *v1Models.Signatures, internalID, external // Replace the internal ID with the External ID for _, sig := range dst.Signatures { - if sig.SignatureReferenceID.String() == internalID { - sig.SignatureReferenceID = strfmt.UUID4(externalID) + if sig.SignatureReferenceID == internalID { + sig.SignatureReferenceID = externalID } } diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index b2861b0e1..a4a86c281 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -268,7 +268,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } if signatureModel != nil { projectID = signatureModel.ProjectID - companyID = signatureModel.SignatureReferenceID.String() + companyID = signatureModel.SignatureReferenceID } eventsService.LogEvent(&events.LogEventArgs{ @@ -335,7 +335,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } if signatureModel != nil { projectID = signatureModel.ProjectID - companyID = signatureModel.SignatureReferenceID.String() + companyID = signatureModel.SignatureReferenceID } eventsService.LogEvent(&events.LogEventArgs{ EventType: events.ApprovalListGitHubOrganizationDeleted, @@ -967,7 +967,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje utils.ErrorResponseForbidden(reqID, fmt.Sprintf("user %s does not have access to the specified signature", authUser.UserName))) } - doc, err := v2service.GetSignedDocument(ctx, signatureModel.SignatureID.String()) + doc, err := v2service.GetSignedDocument(ctx, signatureModel.SignatureID) if err != nil { log.WithFields(f).WithError(err).Warn("problem fetching signed document") if strings.Contains(err.Error(), "bad request") { @@ -1297,15 +1297,15 @@ func isUserHaveAccessOfSignedSignaturePDF(ctx context.Context, authUser *auth.Us // Corporate signature...we can check the company details if signature.SignatureType == CclaSignatureType { - comp, err := companyService.GetCompany(ctx, signature.SignatureReferenceID.String()) + comp, err := companyService.GetCompany(ctx, signature.SignatureReferenceID) if err != nil { - log.WithFields(f).WithError(err).Warnf("failed to load company record using signature reference id: %s", signature.SignatureReferenceID.String()) + log.WithFields(f).WithError(err).Warnf("failed to load company record using signature reference id: %s", signature.SignatureReferenceID) return false, err } // No company SFID? Then, we can't check permissions... if comp == nil || comp.CompanyExternalID == "" { - log.WithFields(f).Warnf("failed to load company record with external SFID using signature reference id: %s", signature.SignatureReferenceID.String()) + log.WithFields(f).Warnf("failed to load company record with external SFID using signature reference id: %s", signature.SignatureReferenceID) return false, err } diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 6cb023f1a..2c0d33b8f 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -191,9 +191,9 @@ func (s service) GetSignedDocument(ctx context.Context, signatureID string) (*mo var url string switch sig.SignatureType { case ClaSignatureType: - url = utils.SignedCLAFilename(sig.ProjectID, "icla", sig.SignatureReferenceID.String(), sig.SignatureID.String()) + url = utils.SignedCLAFilename(sig.ProjectID, "icla", sig.SignatureReferenceID, sig.SignatureID) case CclaSignatureType: - url = utils.SignedCLAFilename(sig.ProjectID, "ccla", sig.SignatureReferenceID.String(), sig.SignatureID.String()) + url = utils.SignedCLAFilename(sig.ProjectID, "ccla", sig.SignatureReferenceID, sig.SignatureID) } signedURL, err := utils.GetDownloadLink(url) if err != nil { From 914ea849d5fab5379ed60c72fbc98ee8f3717cfa Mon Sep 17 00:00:00 2001 From: wanyaland Date: Wed, 3 Feb 2021 13:15:14 +0300 Subject: [PATCH 0038/1276] Feature/Signing Entity - Made signing entity input param optional during company creation Signed-off-by: wanyaland --- cla-backend-go/swagger/cla.v2.yaml | 1 - cla-backend-go/v2/company/handlers.go | 4 ++-- cla-backend-go/v2/company/service.go | 5 +++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 8498d73c2..5f4b1faf5 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4039,7 +4039,6 @@ definitions: - userEmail - companyName - companyWebsite - - signingEntityName properties: companyName: $ref: './common/properties/company-name.yaml' diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index fd4d822b3..f17692776 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -331,7 +331,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo "userID": params.UserID, "companyName": aws.StringValue(params.Input.CompanyName), "companyWebsite": aws.StringValue(params.Input.CompanyWebsite), - "signingEntityName": aws.StringValue(params.Input.SigningEntityName), + "signingEntityName": aws.StringValue(¶ms.Input.SigningEntityName), } // No permissions needed - anyone can create a company @@ -344,7 +344,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("creating company...") - companyModel, err := service.CreateCompany(ctx, *params.Input.CompanyName, *params.Input.SigningEntityName, *params.Input.CompanyWebsite, params.Input.UserEmail.String(), params.UserID) + companyModel, err := service.CreateCompany(ctx, *params.Input.CompanyName, params.Input.SigningEntityName, *params.Input.CompanyWebsite, params.Input.UserEmail.String(), params.UserID) if err != nil { log.Warnf("error returned from create company api: %+v", err) if strings.Contains(err.Error(), "website already exists") { diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 19dd56f21..9e3e6205f 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -424,6 +424,11 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN // Create Easy CLA Company log.WithFields(f).Debugf("Creating EasyCLA company: %s ", companyName) + + if signingEntityName == "" { + log.WithFields(f).Debugf("Setting signing entity with company name value :%s ", companyName) + signingEntityName = companyName + } // OrgID used as externalID for the easyCLA Company // Create a new company model for the create function createCompanyModel := &v1Models.Company{ From f04af4164196446284666d0f70df542e391a9e50 Mon Sep 17 00:00:00 2001 From: makkalot Date: Wed, 3 Feb 2021 13:45:38 +0200 Subject: [PATCH 0039/1276] making company name editable in docusign and saving the company name from docusign into signature as signing entity name Signed-off-by: makkalot --- cla-backend-go/template/repository.go | 4 ++-- cla-backend/cla/models/docusign_models.py | 13 +++++++++++++ cla-backend/cla/tests/unit/test_docusign_models.py | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/template/repository.go b/cla-backend-go/template/repository.go index f83d0a28a..6fba94bb1 100644 --- a/cla-backend-go/template/repository.go +++ b/cla-backend-go/template/repository.go @@ -617,7 +617,7 @@ var templateMap = map[string]models.Template{ ID: "corporation_name", Name: "Corporation Name", AnchorString: "Corporation Name:", - FieldType: "text", + FieldType: "text_unlocked", IsOptional: false, IsEditable: false, Width: 355, @@ -942,7 +942,7 @@ var templateMap = map[string]models.Template{ ID: "corporation_name", Name: "Corporation Name", AnchorString: "Corporation Name:", - FieldType: "text", + FieldType: "text_unlocked", IsOptional: false, IsEditable: false, Width: 355, diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 96b586f10..6bce36446 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -2057,6 +2057,19 @@ def populate_signature_from_ccla_callback(content: str, ccla_tree: ET, signature else: cla.log.warning(f'{fn} - unable to locate signatory_name field from docusign callback') + signing_entity_name_field = ccla_tree.find(".//*[@name='corporation_name']") + if signing_entity_name_field is not None: + signing_entity_name = signing_entity_name_field.find(DocuSign.TAGS['field_value']) + if signing_entity_name is not None: + signing_entity_name = signing_entity_name.text + cla.log.debug(f'{fn} - located signing_entity_name_field value in the docusign document callback - ' + f'setting user_docusign_name attribute: {signing_entity_name} value in the signature') + signature.set_signing_entity_name(signing_entity_name) + else: + cla.log.warning(f'{fn} - unable to extract signing_entity_name field_value from docusign callback') + else: + cla.log.warning(f'{fn} - unable to locate signing_entity_name field from docusign callback') + # seems the content could be bytes if hasattr(content, "decode"): content = content.decode("utf-8") diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index 602c7f331..04805d75d 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -712,7 +712,9 @@ def test_populate_signature_from_ccla_callback(): assert signature.get_user_docusign_name() == "Example Signatory" assert signature.get_user_docusign_date_signed() == "2020-12-17T07:44:08.503" assert signature.get_user_docusign_raw_xml() == content + assert signature.get_signing_entity_name() == "The Linux Foundation" assert "user_docusign_name" in signature.to_dict() + assert "signing_entity_name" in signature.to_dict() assert "user_docusign_date_signed" in signature.to_dict() assert "user_docusign_raw_xml" not in signature.to_dict() assert "user_docusign_name" in str(signature) From b106df089ff5805de7399d0741fa36b5163e7e9b Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 3 Feb 2021 17:08:40 +0200 Subject: [PATCH 0040/1276] handle the scenario of empty result (#2568) Signed-off-by: makkalot --- cla-backend-go/v2/company/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index a70b68772..2dcc824e6 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -943,7 +943,7 @@ func (s *service) GetCompanyCLAGroupManagers(ctx context.Context, companyID, cla if sigModel == nil { log.WithFields(f).Warnf("unable to query CCLA signature using Company ID: %s and CLA Group ID: %s, signed: true, approved: true - no signature found", companyID, claGroupID) - return nil, nil + return &models.CompanyClaManagers{}, nil } projectModel, projErr := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) From d551a673a786aa7dba38782231560aee1b570072 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 3 Feb 2021 17:00:14 -0500 Subject: [PATCH 0041/1276] Added additional logging and audit events for key events (#2571) Signed-off-by: David Deal --- cla-backend-go/events/event_types.go | 1 + .../v2/dynamo_events/projects_cla_groups.go | 305 ++++++++++++++---- cla-backend-go/v2/dynamo_events/signatures.go | 4 +- 3 files changed, 254 insertions(+), 56 deletions(-) diff --git a/cla-backend-go/events/event_types.go b/cla-backend-go/events/event_types.go index c80300f4a..ab32212d6 100644 --- a/cla-backend-go/events/event_types.go +++ b/cla-backend-go/events/event_types.go @@ -80,6 +80,7 @@ const ( ContributorAssignCLADesigneeType = "contributor.assign_designee" ConvertUserToContactType = "lfx_user.convert_to_contact" AssignUserRoleScopeType = "lfx_org_service.assign_user_role_scope" + RemoveUserRoleScopeType = "lfx_org_service.remove_user_role_scope" ProjectServiceCLAEnabled = "project.service.cla.enabled" ProjectServiceCLADisabled = "project.service.cla.disabled" diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index d241c4c7a..8270cdc9e 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -4,6 +4,7 @@ package dynamo_events import ( + "context" "fmt" "strings" "sync" @@ -35,11 +36,13 @@ type ProjectClaGroup struct { // ProjectServiceEnableCLAServiceHandler handles enabling the CLA Service attribute from the project service func (s *service) ProjectServiceEnableCLAServiceHandler(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "ProjectServiceEnableCLAServiceHandler", - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, + "functionName": "dynamo_events.projects_cla_groups.ProjectServiceEnableCLAServiceHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, } log.WithFields(f).Debug("processing request") @@ -55,39 +58,43 @@ func (s *service) ProjectServiceEnableCLAServiceHandler(event events.DynamoDBEve f["foundationSFID"] = newProject.FoundationSFID psc := v2ProjectService.GetClient() - log.WithFields(f).Debug("enabling CLA service...") + log.WithFields(f).Debug("looking up project by SFID...") projectDetails, prjerr := psc.GetProject(newProject.ProjectSFID) if prjerr != nil { - log.WithError(err).Warn("enable to get project details") + log.WithError(err).Warnf("unable to get project details from SFID: %s", newProject.ProjectSFID) } projectName := newProject.ProjectSFID if projectDetails != nil { projectName = projectDetails.Name + f["projectName"] = projectName } + start, _ := utils.CurrentTime() + log.WithFields(f).Debugf("enabling CLA service for project %s with ID: %s", projectName, newProject.ProjectSFID) err = psc.EnableCLA(newProject.ProjectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("enabling CLA service failed") return err } finish, _ := utils.CurrentTime() + log.WithFields(f).Debugf("enabled CLA service for project %s with ID: %s", projectName, newProject.ProjectSFID) log.WithFields(f).Debugf("enabling CLA service completed - took: %s", finish.Sub(start).String()) // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ ContainsPII: false, - EventData: fmt.Sprintf("enabled CLA service for project: %s", projectName), - EventSummary: fmt.Sprintf("enabled CLA service for project: %s", projectName), + EventData: fmt.Sprintf("enabled CLA service for project: %s with ID: %s", projectName, newProject.ProjectSFID), EventFoundationSFID: newProject.FoundationSFID, EventProjectExternalID: newProject.ProjectSFID, EventProjectID: newProject.ClaGroupID, + EventProjectName: projectName, EventProjectSFID: newProject.ProjectSFID, + EventProjectSFName: projectName, + EventSummary: fmt.Sprintf("enabled CLA service for project: %s", projectName), EventType: claEvents.ProjectServiceCLAEnabled, LfUsername: "easycla system", UserID: "easycla system", UserName: "easycla system", - // EventProjectName: "", - EventProjectSFName: projectName, }) if eventErr != nil { log.WithFields(f).WithError(eventErr).Warn("problem logging event for enabling CLA service") @@ -99,11 +106,13 @@ func (s *service) ProjectServiceEnableCLAServiceHandler(event events.DynamoDBEve // ProjectServiceDisableCLAServiceHandler handles disabling/removing the CLA Service attribute from the project service func (s *service) ProjectServiceDisableCLAServiceHandler(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "ProjectServiceDisableCLAServiceHandler", - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, + "functionName": "dynamo_events.projects_cla_groups.ProjectServiceDisableCLAServiceHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, } log.WithFields(f).Debug("processing request") @@ -120,31 +129,42 @@ func (s *service) ProjectServiceDisableCLAServiceHandler(event events.DynamoDBEv f["FoundationSFID"] = oldProject.FoundationSFID psc := v2ProjectService.GetClient() + log.WithFields(f).Debug("looking up project by SFID...") + projectDetails, prjerr := psc.GetProject(oldProject.ProjectSFID) + if prjerr != nil { + log.WithError(err).Warnf("unable to get project details from SFID: %s", oldProject.ProjectSFID) + } + projectName := oldProject.ProjectSFID + if projectDetails != nil { + projectName = projectDetails.Name + f["projectName"] = projectName + } + // Gathering metrics - grab the time before the API call before, _ := utils.CurrentTime() - log.WithFields(f).Debug("disabling CLA service") + log.WithFields(f).Debugf("disabling CLA service for project %s with ID: %s", projectName, oldProject.ProjectSFID) err = psc.DisableCLA(oldProject.ProjectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("disabling CLA service failed") return err } - log.WithFields(f).Debugf("disabling CLA service took %s", time.Since(before).String()) + log.WithFields(f).Debugf("disabled CLA service for project %s with ID: %s", projectName, oldProject.ProjectSFID) + log.WithFields(f).Debugf("disabling CLA service completed - took %s", time.Since(before).String()) // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ ContainsPII: false, - EventData: fmt.Sprintf("disabled CLA service for project: %s", oldProject.ProjectSFID), - EventSummary: fmt.Sprintf("disabled CLA service for project: %s", oldProject.ProjectSFID), + EventData: fmt.Sprintf("disabled CLA service for project: %s with ID: %s", projectName, oldProject.ProjectSFID), EventFoundationSFID: oldProject.FoundationSFID, EventProjectExternalID: oldProject.ProjectSFID, EventProjectID: oldProject.ClaGroupID, + EventProjectName: projectName, EventProjectSFID: oldProject.ProjectSFID, + EventSummary: fmt.Sprintf("disabled CLA service for project: %s", projectName), EventType: claEvents.ProjectServiceCLADisabled, LfUsername: "easycla system", UserID: "easycla system", UserName: "easycla system", - // EventProjectName: "", - // EventProjectSFName: "", }) if eventErr != nil { log.WithFields(f).WithError(eventErr).Warn("problem logging event for disabling CLA service") @@ -157,7 +177,7 @@ func (s *service) ProjectServiceDisableCLAServiceHandler(event events.DynamoDBEv func (s *service) ProjectUnenrolledDisableRepositoryHandler(event events.DynamoDBEventRecord) error { ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "ProjectUnenrolledDisableRepositoryHandler", + "functionName": "dynamo_events.projects_cla_groups.ProjectUnenrolledDisableRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "eventID": event.EventID, "eventName": event.EventName, @@ -231,11 +251,13 @@ func (s *service) ProjectUnenrolledDisableRepositoryHandler(event events.DynamoD // AddCLAPermissions handles adding CLA permissions func (s *service) AddCLAPermissions(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "AddCLAPermissions", - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, + "functionName": "dynamo_events.projects_cla_groups.AddCLAPermissions", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, } log.WithFields(f).Debug("processing event") @@ -252,14 +274,14 @@ func (s *service) AddCLAPermissions(event events.DynamoDBEventRecord) error { f["FoundationSFID"] = newProject.FoundationSFID // Add any relevant CLA Manager permissions for this CLA Group/Project SFID - permErr := s.addCLAManagerPermissions(newProject.ClaGroupID, newProject.ProjectSFID) + permErr := s.addCLAManagerPermissions(ctx, newProject.ClaGroupID, newProject.ProjectSFID) if permErr != nil { log.WithFields(f).WithError(permErr).Warn("problem adding CLA Manager permissions for projectSFID") // Ok - don't fail for now } // Add any relevant CLA Manager Designee permissions for this CLA Group/Project SFID - permErr = s.addCLAManagerDesigneePermissions(newProject.ClaGroupID, newProject.FoundationSFID, newProject.ProjectSFID) + permErr = s.addCLAManagerDesigneePermissions(ctx, newProject.ClaGroupID, newProject.FoundationSFID, newProject.ProjectSFID) if permErr != nil { log.WithFields(f).WithError(permErr).Warn("problem adding CLA Manager Designee permissions for projectSFID") // Ok - don't fail for now @@ -270,11 +292,13 @@ func (s *service) AddCLAPermissions(event events.DynamoDBEventRecord) error { // RemoveCLAPermissions handles removing existing CLA permissions func (s *service) RemoveCLAPermissions(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "RemoveCLAPermissions", - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, + "functionName": "dynamo_events.projects_cla_groups.RemoveCLAPermissions", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, } log.WithFields(f).Debug("processing event") @@ -291,7 +315,7 @@ func (s *service) RemoveCLAPermissions(event events.DynamoDBEventRecord) error { f["FoundationSFID"] = oldProject.FoundationSFID // Remove any CLA related permissions - permErr := s.removeCLAPermissions(oldProject.ProjectSFID) + permErr := s.removeCLAPermissions(ctx, oldProject.ProjectSFID) if permErr != nil { log.WithFields(f).WithError(permErr).Warn("problem removing CLA permissions for projectSFID") // Ok - don't fail for now @@ -300,12 +324,25 @@ func (s *service) RemoveCLAPermissions(event events.DynamoDBEventRecord) error { return nil } -func (s *service) addCLAManagerDesigneePermissions(claGroupID, foundationSFID, projectSFID string) error { - ctx := utils.NewContext() +func (s *service) addCLAManagerDesigneePermissions(ctx context.Context, claGroupID, foundationSFID, projectSFID string) error { f := logrus.Fields{ - "functionName": "addCLAManagerDesigneePermissions", - "claGroupID": claGroupID, - "projectSFID": projectSFID, + "functionName": "dynamo_events.projects_cla_groups.addCLAManagerDesigneePermissions", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "projectSFID": projectSFID, + } + + // Lookup the project name + log.WithFields(f).Debugf("looking up project by SFID: %s", projectSFID) + psc := v2ProjectService.GetClient() + projectModel, err := psc.GetProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup project record by projectSFID") + } + projectName := "" + if projectModel != nil { + projectName = projectModel.Name + f["projectName"] = projectName } //handle userscopes per project(users with Designee role) @@ -377,12 +414,51 @@ func (s *service) addCLAManagerDesigneePermissions(claGroupID, foundationSFID, p orgID := strings.Split(userScope.ObjectID, "|")[1] email := userScope.Email + // Lookup the organization name + log.WithFields(f).Debugf("looking up organization by SFID: %s", orgID) + orgModel, orgLookupErr := orgClient.GetOrganization(ctx, orgID) + if orgLookupErr != nil { + log.WithFields(f).WithError(orgLookupErr).Warnf("unable to lookup organization record by organziation SFID: %s", orgID) + } + orgName := "" + if orgModel != nil { + orgName = orgModel.Name + log.WithFields(f).Debugf("found organization by SFID: %s - Name: %s", orgID, orgName) + } + + log.WithFields(f).Debugf("assiging role: %s to user %s with email %s for project: %s, company: %s...", + utils.CLAManagerRole, userScope.Username, email, projectSFID, orgID) roleErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, email, projectSFID, orgID, claManagerDesigneeRoleID) if roleErr != nil { log.WithFields(f).WithError(roleErr).Warnf("%s, role assignment for user %s failed for this project: %s, company: %s ", utils.CLADesigneeRole, email, projectSFID, orgID) return } + + msgSummary := fmt.Sprintf("assigned role: %s to user %s with email %s for project: %s, company: %s", + utils.CLAManagerRole, userScope.Username, email, projectName, orgName) + msg := fmt.Sprintf("assigned role: %s to user %s with email %s for project: %s with SFID: %s, company: %s with SFID: %s", + utils.CLAManagerRole, userScope.Username, email, projectName, projectSFID, orgName, orgID) + log.WithFields(f).Debug(msg) + // Log the event + eventErr := s.eventsRepo.CreateEvent(&models.Event{ + ContainsPII: false, + EventCompanySFID: orgID, + EventData: msg, + EventProjectExternalID: projectSFID, + EventProjectID: claGroupID, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventCompanyName: orgName, + EventSummary: msgSummary, + EventType: claEvents.AssignUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", + }) + if eventErr != nil { + log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.AssignUserRoleScopeType, msg) + } }(userScope) } @@ -395,15 +471,28 @@ func (s *service) addCLAManagerDesigneePermissions(claGroupID, foundationSFID, p } // addCLAManagerPermissions handles adding the CLA Manager permissions for the specified SF project -func (s *service) addCLAManagerPermissions(claGroupID, projectSFID string) error { - ctx := utils.NewContext() +func (s *service) addCLAManagerPermissions(ctx context.Context, claGroupID, projectSFID string) error { f := logrus.Fields{ - "functionName": "addCLAManagerPermissions", - "projectSFID": projectSFID, - "claGroupID": claGroupID, + "functionName": "dynamo_events.projects_cla_groups.addCLAManagerPermissions", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "claGroupID": claGroupID, } log.WithFields(f).Debug("adding CLA Manager permissions...") + // Lookup the project name + log.WithFields(f).Debugf("looking up project by SFID: %s", projectSFID) + psc := v2ProjectService.GetClient() + projectModel, err := psc.GetProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup project record by projectSFID") + } + projectName := "" + if projectModel != nil { + projectName = projectModel.Name + f["projectName"] = projectName + } + sigModels, err := s.signatureRepo.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ ClaType: aws.String(utils.ClaTypeCCLA), PageSize: aws.Int64(1000), @@ -489,20 +578,45 @@ func (s *service) addCLAManagerPermissions(claGroupID, projectSFID string) error // Does the user already have the cla-manager role? if hasRole { - log.WithFields(f).Debugf("user %s/%s already has role %s for the project %s and organization %s", + log.WithFields(f).Debugf("user %s/%s already has role %s for the project %s and organization %s - skipping assignment", signatureUserModel.LfUsername, userModel.ID, utils.CLAManagerRole, projectSFID, companySFID) // Nothing to do here - move along... return } // Finally....assign the role to this user - roleErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, aws.StringValue(userModel.Email), projectSFID, companySFID, claManagerRoleID) + log.WithFields(f).Debugf("assiging role: %s to user %s/%s/%s for project: %s, company: %s...", + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, *userModel.Email, projectSFID, companySFID) + roleErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, utils.StringValue(userModel.Email), projectSFID, companySFID, claManagerRoleID) if roleErr != nil { log.WithFields(f).WithError(roleErr).Warnf("%s, role assignment for user user %s/%s/%s failed for this project: %s, company: %s", - utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, *userModel.Email, projectSFID, companySFID) + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, utils.StringValue(userModel.Email), projectSFID, companySFID) return } - + msg := fmt.Sprintf("assigned role: %s to user %s/%s/%s for project: %s with SFID:%s, company: %s with SFID: %s", + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, utils.StringValue(userModel.Email), projectName, projectSFID, companyModel.CompanyName, companySFID) + msgSummary := fmt.Sprintf("assigned role: %s to user %s/%s/%s for project: %s, company: %s", + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, utils.StringValue(userModel.Email), projectName, companyModel.CompanyName) + log.WithFields(f).Debug(msg) + // Log the event + eventErr := s.eventsRepo.CreateEvent(&models.Event{ + ContainsPII: false, + EventCompanyName: companyModel.CompanyName, + EventCompanySFID: companySFID, + EventData: msg, + EventProjectExternalID: projectSFID, + EventProjectID: claGroupID, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventSummary: msgSummary, + EventType: claEvents.AssignUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", + }) + if eventErr != nil { + log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.AssignUserRoleScopeType, msg) + } }(signatureUserModel) } @@ -515,37 +629,120 @@ func (s *service) addCLAManagerPermissions(claGroupID, projectSFID string) error } // removeCLAPermissions handles removing CLA Group (projects table) permissions for the specified project -func (s *service) removeCLAPermissions(projectSFID string) error { +func (s *service) removeCLAPermissions(ctx context.Context, projectSFID string) error { f := logrus.Fields{ - "functionName": "removeCLAPermissions", - "projectSFID": projectSFID, + "functionName": "dynamo_events.projects_cla_groups.removeCLAPermissions", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, } log.WithFields(f).Debug("removing CLA permissions...") + // Lookup the project name + log.WithFields(f).Debugf("looking up project by SFID: %s", projectSFID) + psc := v2ProjectService.GetClient() + projectModel, err := psc.GetProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup project record by projectSFID") + } + projectName := "" + if projectModel != nil { + projectName = projectModel.Name + f["projectName"] = projectName + } + client := acsService.GetClient() - err := client.RemoveCLAUserRolesByProject(projectSFID, []string{utils.CLAManagerRole, utils.CLADesigneeRole, utils.CLASignatoryRole}) + roleNames := []string{utils.CLAManagerRole, utils.CLADesigneeRole, utils.CLASignatoryRole} + + log.WithFields(f).Debugf("removing roles: %s for all users for project: %s", strings.Join(roleNames, ","), projectSFID) + err = client.RemoveCLAUserRolesByProject(projectSFID, roleNames) if err != nil { log.WithFields(f).WithError(err).Warn("problem removing CLA user roles by projectSFID") } + msg := fmt.Sprintf("removed roles: %s for all users for project: %s", strings.Join(roleNames, ","), projectSFID) + log.WithFields(f).Debug(msg) + + // Log the event + eventErr := s.eventsRepo.CreateEvent(&models.Event{ + ContainsPII: false, + EventData: msg, + EventProjectExternalID: projectSFID, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventSummary: msg, + EventType: claEvents.RemoveUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", + }) + if eventErr != nil { + log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.RemoveUserRoleScopeType, msg) + } return err } // removeCLAPermissionsByProjectOrganizationRole handles removal of the specified role for the given SF Project and SF Organization -func (s *service) removeCLAPermissionsByProjectOrganizationRole(projectSFID, organizationSFID string, roleNames []string) error { +func (s *service) removeCLAPermissionsByProjectOrganizationRole(ctx context.Context, projectSFID, organizationSFID string, roleNames []string) error { f := logrus.Fields{ - "functionName": "removeCLAPermissionsByProjectOrganizationRole", + "functionName": "dynamo_events.projects_cla_groups.removeCLAPermissionsByProjectOrganizationRole", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "organizationSFID": organizationSFID, "roleNames": strings.Join(roleNames, ","), } - log.WithFields(f).Debug("removing CLA permissions...") + // Lookup the project name + log.WithFields(f).Debugf("looking up project by SFID: %s", projectSFID) + psc := v2ProjectService.GetClient() + projectModel, err := psc.GetProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup project record by projectSFID") + } + projectName := "" + if projectModel != nil { + projectName = projectModel.Name + f["projectName"] = projectName + } + + // Lookup the organization name + log.WithFields(f).Debugf("looking up organization by SFID: %s", organizationSFID) + orgClient := organizationService.GetClient() + orgModel, orgLookupErr := orgClient.GetOrganization(ctx, organizationSFID) + if orgLookupErr != nil { + log.WithFields(f).WithError(orgLookupErr).Warnf("unable to lookup organization record by organziation SFID: %s", organizationSFID) + } + orgName := "" + if orgModel != nil { + orgName = orgModel.Name + log.WithFields(f).Debugf("found organization by SFID: %s - Name: %s", organizationSFID, orgName) + } + + log.WithFields(f).Debugf("removing roles: %s for all users for project: %s, companay: %s", strings.Join(roleNames, ","), projectSFID, organizationSFID) client := acsService.GetClient() - err := client.RemoveCLAUserRolesByProjectOrganization(projectSFID, organizationSFID, roleNames) + err = client.RemoveCLAUserRolesByProjectOrganization(projectSFID, organizationSFID, roleNames) if err != nil { log.WithFields(f).WithError(err).Warn("problem removing CLA user roles by projectSFID and organizationSFID") } + msg := fmt.Sprintf("removed roles: %s for all users for project: %s, companay: %s", strings.Join(roleNames, ","), projectSFID, organizationSFID) + log.WithFields(f).Debug(msg) + + // Log the event + eventErr := s.eventsRepo.CreateEvent(&models.Event{ + ContainsPII: false, + EventCompanySFID: organizationSFID, + EventData: msg, + EventProjectExternalID: projectSFID, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventSummary: msg, + EventType: claEvents.RemoveUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", + }) + if eventErr != nil { + log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.RemoveUserRoleScopeType, msg) + } return err } diff --git a/cla-backend-go/v2/dynamo_events/signatures.go b/cla-backend-go/v2/dynamo_events/signatures.go index dc2bbd7b1..a83513209 100644 --- a/cla-backend-go/v2/dynamo_events/signatures.go +++ b/cla-backend-go/v2/dynamo_events/signatures.go @@ -219,7 +219,7 @@ func (s *service) SignatureSignedEvent(event events.DynamoDBEventRecord) error { if signedAtFoundation { log.WithFields(f).Debugf("removing existing %s role for project: '%s' (%s) and company: '%s' (%s)", utils.CLADesigneeRole, projectCLAGroups[0].ProjectName, foundationSFID, companyModel.CompanyName, companyModel.CompanyExternalID) - err = s.removeCLAPermissionsByProjectOrganizationRole(foundationSFID, companyModel.CompanyExternalID, []string{utils.CLADesigneeRole}) + err = s.removeCLAPermissionsByProjectOrganizationRole(ctx, foundationSFID, companyModel.CompanyExternalID, []string{utils.CLADesigneeRole}) if err != nil { log.WithFields(f).Warnf("failed to remove %s roles for project: '%s' (%s) and company: '%s' (%s), error: %+v", utils.CLADesigneeRole, projectCLAGroups[0].ProjectName, foundationSFID, companyModel.CompanyName, companyModel.CompanyExternalID, err) @@ -231,7 +231,7 @@ func (s *service) SignatureSignedEvent(event events.DynamoDBEventRecord) error { // Remove any roles that were previously assigned for cla-manager-designee log.WithFields(f).Debugf("removing existing %s role for project: '%s' (%s) and company: '%s' (%s)", utils.CLADesigneeRole, projectCLAGroup.ProjectName, projectCLAGroup.ProjectSFID, companyModel.CompanyName, companyModel.CompanyExternalID) - err = s.removeCLAPermissionsByProjectOrganizationRole(projectCLAGroup.ProjectSFID, companyModel.CompanyExternalID, []string{utils.CLADesigneeRole}) + err = s.removeCLAPermissionsByProjectOrganizationRole(ctx, projectCLAGroup.ProjectSFID, companyModel.CompanyExternalID, []string{utils.CLADesigneeRole}) if err != nil { log.WithFields(f).Warnf("failed to remove %s roles for project: '%s' (%s) and company: '%s' (%s), error: %+v", utils.CLADesigneeRole, projectCLAGroup.ProjectName, projectCLAGroup.ProjectSFID, companyModel.CompanyName, companyModel.CompanyExternalID, err) From 67ab4e253fa0063eb0649af8fa4a22e4c1411bfa Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 3 Feb 2021 18:03:30 -0500 Subject: [PATCH 0042/1276] Removed unused/unneeded infra scripts/tools. (#2572) Signed-off-by: David Deal --- infra/backup-dynamodb-tables.sh | 2 +- infra/migrate-ssm-parameters.ts | 116 ------------ infra/put-ssm-parameter.ts | 64 ------- infra/ssm/.gitignore | 4 - infra/ssm/.golangci.yaml | 87 --------- infra/ssm/Makefile | 66 ------- infra/ssm/README.md | 27 --- infra/ssm/go.mod | 13 -- infra/ssm/go.sum | 315 -------------------------------- infra/ssm/logging/common.go | 17 -- infra/ssm/logging/logger.go | 207 --------------------- infra/ssm/main.go | 124 ------------- 12 files changed, 1 insertion(+), 1041 deletions(-) delete mode 100644 infra/migrate-ssm-parameters.ts delete mode 100644 infra/put-ssm-parameter.ts delete mode 100644 infra/ssm/.gitignore delete mode 100644 infra/ssm/.golangci.yaml delete mode 100644 infra/ssm/Makefile delete mode 100644 infra/ssm/README.md delete mode 100644 infra/ssm/go.mod delete mode 100644 infra/ssm/go.sum delete mode 100644 infra/ssm/logging/common.go delete mode 100644 infra/ssm/logging/logger.go delete mode 100644 infra/ssm/main.go diff --git a/infra/backup-dynamodb-tables.sh b/infra/backup-dynamodb-tables.sh index 9940164f7..b07d51d9e 100755 --- a/infra/backup-dynamodb-tables.sh +++ b/infra/backup-dynamodb-tables.sh @@ -78,7 +78,7 @@ declare -a tables=( "cla-${env}-ccla-whitelist-requests" #------------------------------------------------------------------------------ # Perform the database table backup #------------------------------------------------------------------------------ -log "Backing up tables for environemnt ${_Y}${env}${_W} using aws profile ${_Y}${profile}${_W}." +log "Backing up tables for environment ${_Y}${env}${_W} using aws profile ${_Y}${profile}${_W}." for table in "${tables[@]}"; do cmd="aws --profile ${profile} --region ${region} dynamodb create-backup --table-name ${table} --backup-name ${table}-${current_date}" log "Running command: ${cmd}" diff --git a/infra/migrate-ssm-parameters.ts b/infra/migrate-ssm-parameters.ts deleted file mode 100644 index b924bfa0b..000000000 --- a/infra/migrate-ssm-parameters.ts +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const AWS = require('aws-sdk'); -const SSM = require('aws-sdk/clients/ssm'); -const program = require('commander'); -const Promise = require('bluebird'); - -program - .version('1.0.0') - .option('-d, --debug', 'output extra debugging') - .requiredOption('--from ', 'specifies the AWS region to pull the SSM parameters from') - .requiredOption('--to ', 'specifies the AWS region to push the SSM parameters to') - .requiredOption('-s, --stage ', 'the environment stage') - .parse(process.argv); - -if (program.debug) console.log(program.opts()); - -console.log(`From region: ${program.from}`); -console.log(`To region: ${program.to}`); -console.log(`Migrating SSM Parameters in region ${program.from} to region ${program.to} for stage ${program.stage}...`); - -const parameters = [ - `cla-gh-app-private-key-${program.stage}`, - `cla-gh-app-webhook-secret-${program.stage}`, - `cla-gh-app-id-${program.stage}`, - `cla-gh-oauth-client-id-${program.stage}`, - `cla-gh-oauth-secret-${program.stage}`, - `cla-auth0-domain-${program.stage}`, - `cla-auth0-clientId-${program.stage}`, - `cla-auth0-username-claim-${program.stage}`, - `cla-auth0-algorithm-${program.stage}`, - `cla-sf-instance-url-${program.stage}`, - `cla-sf-consumer-key-${program.stage}`, - `cla-sf-consumer-secret-${program.stage}`, - `cla-sf-username-${program.stage}`, - `cla-sf-password-${program.stage}`, - `cla-doc-raptor-api-key-${program.stage}`, - `cla-docusign-root-url-${program.stage}`, - `cla-docusign-username-${program.stage}`, - `cla-docusign-password-${program.stage}`, - `cla-docusign-integrator-key-${program.stage}`, - `cla-api-base-${program.stage}`, - `cla-contributor-base-${program.stage}`, - `cla-corporate-base-${program.stage}`, - `cla-landing-page-${program.stage}`, - `cla-signature-files-bucket-${program.stage}`, - `cla-logo-url-${program.stage}`, - `cla-ses-sender-email-address-${program.stage}`, - `cla-lf-group-client-id-${program.stage}`, - `cla-lf-group-client-secret-${program.stage}`, - `cla-lf-group-refresh-token-${program.stage}`, - `cla-lf-group-client-url-${program.stage}`, - `cla-sns-event-topic-arn-${program.stage}`, - `docraptor-test-mode-${program.stage}`, -]; - -const ssmFrom = new SSM({'region': program.from}); -const ssmTo = new SSM({'region': program.to}); - -// If we do all these at the same time, we end up with the following error: -// Error adding parameter undefined. Error code: ThrottlingException, Message: Rate exceeded -// So, we use this promise library which allows us to throttle the requests in batches of N... -const concurrencyFactor = 10; -Promise.map(parameters, function(param) { - console.log(`Querying SSM Parameter: ${param} in region ${program.from} for stage ${program.stage}...`); - const query = { - "Name": param, - "WithDecryption": false, - }; - - ssmFrom.getParameter(query, (err, data) => { - if (err == null) { - console.log("Data:"); - console.log(data); - console.log(`Uploading parameter ${data.Parameter.Name} = ${data.Parameter.Value}, Description: ${data.Parameter.Description}`); - const query = { - "Name": data.Parameter.Name, - "Value": data.Parameter.Value, - "Description": (data.Parameter.Description !== undefined ? data.Parameter.Description : data.Parameter.Name), - "Type": "String", - "Overwrite": true, - /* Can't use overwrite and Tags at the same time - "Tags": [ - { "Key": "Name", "Value": "EasyCLA" }, - { "Key": "ServiceType", "Value": "Product" }, - { "Key": "Service", "Value": "EasyCLA" }, - { "Key": "ServiceRole", "Value": "Backend" }, - { "Key": "ProgrammingPlatform", "Value": "Go" }, - { "Key": "Owner", "Value": "David Deal" }, - ], - */ - }; - console.log('Query:'); - console.log(query); - - let param = ssmTo.putParameter(query, (err, data) => { - if (err == null) { - //console.log('raw data = %o', data); - console.log(`Set ${data.Parameter.Name} = ${data.Parameter.Value}`) - } else { - console.log(`Error adding parameter ${program.parameter}. Error code: ${err.code}, Message: ${err.message}`); - console.log('error = %o', err); - } - }); - } else { - console.log(`Error fetching parameter: ${param}. Error code: ${err.code}, Message: ${err.message}`); - if (program.debug) { - console.log('error = %o', err); - } - } - }); -}, {concurrency: concurrencyFactor}).then(function() { - // All items have been processed in sets of concurrencyFactor (10) at most - console.log("done"); -}); diff --git a/infra/put-ssm-parameter.ts b/infra/put-ssm-parameter.ts deleted file mode 100644 index 34b8452b9..000000000 --- a/infra/put-ssm-parameter.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const AWS = require('aws-sdk'); -const SSM = require('aws-sdk/clients/ssm'); -const program = require('commander'); - -program - .version('1.0.0') - .option('-o, --overwrite', 'overwrite the value?') - .option('-s, --securestring', 'create parameter as as secure string') - .requiredOption('-r, --region ', 'specifies the AWS region') - .requiredOption('-p, --parameter ', 'the parameter name') - .requiredOption('-d, --desc ', 'the parameter description') - .requiredOption('-v, --value ', 'the parameter value') - .parse(process.argv); -if (program.debug) console.log(program.opts()); -console.log(`program.description ${program.desc}`); -if (program.desc === undefined) { - console.log('Missing --desc parameter'); - process.exit(1); -} - -// Configure AWS -AWS.config.update({ region: program.region }); - -console.log(`Adding SSM Parameter: ${program.parameter} in region ${program.region}...`); -let query = {} -if (program.overwrite) { - query = { - "Name": program.parameter, - "Value": program.value, - "Description": program.desc, - "Type": (program.securestring ? "SecureString" : "String"), - "Overwrite": (program.overwrite ? true : false), - }; -} else { - query = { - "Name": program.parameter, - "Value": program.value, - "Description": program.desc, - "Type": (program.securestring ? "SecureString" : "String"), - "Overwrite": (program.overwrite ? true : false), - "Tags": [ - { "Key": "Name", "Value": "EasyCLA" }, - { "Key": "ServiceType", "Value": "Product" }, - { "Key": "Service", "Value": "EasyCLA" }, - { "Key": "ServiceRole", "Value": "Backend" }, - { "Key": "ProgrammingPlatform", "Value": "Go" }, - { "Key": "Owner", "Value": "David Deal" }, - ], - }; -} - -const ssm = new SSM(); -let param = ssm.putParameter(query, (err, data) => { - if (err == null) { - console.log('raw data = %o', data); - //console.log(`${data.Parameter.Name} = ${data.Parameter.Value}`) - } else { - console.log(`Error adding parameter ${program.parameter}. Error code: ${err.code}, Message: ${err.message}`); - console.log('error = %o', err); - } -}); diff --git a/infra/ssm/.gitignore b/infra/ssm/.gitignore deleted file mode 100644 index 966e173b4..000000000 --- a/infra/ssm/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT -main -cover.out diff --git a/infra/ssm/.golangci.yaml b/infra/ssm/.golangci.yaml deleted file mode 100644 index d7b828cf9..000000000 --- a/infra/ssm/.golangci.yaml +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT - -# More info on config here: https://github.com/golangci/golangci-lint#config-file -run: - deadline: 5m - issues-exit-code: 1 - tests: true - skip-dirs: - - .git - - bin - - vendor - - node_modules - - var - - gen - - tmp - skip-files: - - \.pb\.go$ - - \.pb\.goclay\.go$ - -output: - format: colored-line-number - print-issued-lines: true - print-linter-name: true - -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: true - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: true - govet: - check-shadowing: true - golint: - min-confidence: 0 - dupl: - threshold: 100 - goconst: - min-len: 2 - min-occurrences: 2 - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - -linters: - disable-all: true - enable: - - golint - - govet - - errcheck - - deadcode - - structcheck - - varcheck - - ineffassign - - typecheck - - goconst - - gocyclo - - gofmt - - goimports - - gosec - - megacheck # (staticcheck + gosimple + unused in one linter) - - depguard - - unconvert - - unparam - - unused - - nakedret - - maligned - #- dupl - -issues: - exclude-use-default: false - exclude: - # _ instead of err checks - - G104 - # for "public interface + private struct implementation" cases only! - - exported func * returns unexported type *, which can be annoying to use - # can be removed in the development phase - # - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form) - # not for the active development - can be removed in the stable phase - - should have a package comment, unless it's in another file for this package - - don't use an underscore in package name - # errcheck: Almost all programs ignore errors on these functions and in most cases it's ok - - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv|.*Rollback). is not checked - - should check returned error before deferring diff --git a/infra/ssm/Makefile b/infra/ssm/Makefile deleted file mode 100644 index 3ed7a534a..000000000 --- a/infra/ssm/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT -MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -BUILD_TIME=`date +%FT%T%z` -VERSION := $(shell sh -c 'git describe --always --tags') -BRANCH := $(shell sh -c 'git rev-parse --abbrev-ref HEAD') -COMMIT := $(shell sh -c 'git rev-parse --short HEAD') -LDFLAGS=-ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.branch=$(BRANCH) -X main.buildDate=$(BUILD_TIME)" - -LINT_TOOL=$(shell go env GOPATH)/bin/golangci-lint -LINT_VERSION=v1.29.0 -SWAGGER_TOOL_VERSION=v0.24.0 -GO_PKGS=$(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) -GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') - -.PHONY: generate setup tool-setup setup-dev setup-deploy clean-all clean swagger up fmt test run deps build build-mac build-aws-lambda user-subscribe-lambda qc lint - -setup: $(LINT_TOOL) setup-dev - -tool-setup: - @echo "Installing gobin for installing tools..." - @# gobin is the equivalent of 'go get' whilst in module-aware mode but this does not modify your go.mod - GO111MODULE=off go get -u github.com/myitcv/gobin - -setup-dev: tool-setup - @echo "Installing goimports..." - gobin golang.org/x/tools/cmd/goimports - @echo "Installing cover..." - gobin golang.org/x/tools/cmd/cover - -clean: - @rm -rf main - -fmt: - @echo "Formatting code and optimizing imports..." - @gofmt -w -l -s $(GO_FILES) - @goimports -w -l $(GO_FILES) - -test: - @echo "Running unit tests..." - @ go test -v $(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) -coverprofile=cover.out - -run: - go run main.go - -deps: - go mod download - -build: build-linux -build-linux: deps - @echo "Building Linux amd64 binary..." - env GOOS=linux GOARCH=amd64 go build $(LDFLAGS) main.go - -build-mac: deps - @echo "Building Mac OSX amd64 binary..." - env GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) main.go - -$(LINT_TOOL): - @echo "Downloading golangci-lint version $(LINT_VERSION)..." - @# Latest releases: https://github.com/golangci/golangci-lint/releases - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin $(LINT_VERSION) - -lint: $(LINT_TOOL) - @cd $(MAKEFILE_DIR) && echo "Running lint..." && $(LINT_TOOL) run --allow-parallel-runners --config=.golangci.yaml ./... && echo "Lint check passed." - @cd $(MAKEFILE_DIR) && ./check-headers.sh - diff --git a/infra/ssm/README.md b/infra/ssm/README.md deleted file mode 100644 index 1e44bbcfb..000000000 --- a/infra/ssm/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# SSM Routine - -A simple command line tool to fetch a value from the AWS Systems Manager Parameter Store (SSM). - -## Prerequisites - -- Need to have an AWS account -- Need to have your AWS credentials setup - this application loads the default AWS configuration in your environment - -## Building - -```bash -go build main.go -``` - -## Running - -```bash -# For help/usage -main --help - -# Fetch a key value from SSM - using the default region and stage -main --key 'cla-gh-app-webhook-secret-dev' - -# With additional options -main --key 'cla-gh-app-webhook-secret-dev' --region us-east-1 --stage dev -``` diff --git a/infra/ssm/go.mod b/infra/ssm/go.mod deleted file mode 100644 index b4f453637..000000000 --- a/infra/ssm/go.mod +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT -module github.com/communitybridge/easycla/infra/ssm - -go 1.14 - -require ( - github.com/aws/aws-sdk-go v1.30.14 - github.com/google/uuid v1.1.2 - github.com/sirupsen/logrus v1.5.0 - github.com/spf13/cobra v1.1.0 - github.com/spf13/viper v1.7.1 -) diff --git a/infra/ssm/go.sum b/infra/ssm/go.sum deleted file mode 100644 index 2dc8962fd..000000000 --- a/infra/ssm/go.sum +++ /dev/null @@ -1,315 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.30.14 h1:vZfX2b/fknc9wKcytbLWykM7in5k6dbQ8iHTJDUP1Ng= -github.com/aws/aws-sdk-go v1.30.14/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.0 h1:aq3wCKjTPmzcNWLVGnsFVN4rflK7Uzn10F8/aw8MhdQ= -github.com/spf13/cobra v1.1.0/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/infra/ssm/logging/common.go b/infra/ssm/logging/common.go deleted file mode 100644 index 62e8ca5aa..000000000 --- a/infra/ssm/logging/common.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package logging - -import log "github.com/sirupsen/logrus" - -// UTCFormatter structure for logging -type UTCFormatter struct { - log.Formatter -} - -// Format handler for UTC time - usage: log.SetFormatter(UTCFormatter{&log.JSONFormatter{}}) -func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) { - e.Time = e.Time.UTC() - return u.Formatter.Format(e) -} diff --git a/infra/ssm/logging/logger.go b/infra/ssm/logging/logger.go deleted file mode 100644 index 9bbe69805..000000000 --- a/infra/ssm/logging/logger.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package logging - -import ( - "crypto/rand" - "fmt" - "os" - "runtime" - "strings" - "time" - - "github.com/google/uuid" - "github.com/sirupsen/logrus" -) - -const ( - logFormatText = "text" - logFormatJSON = "json" - logFormatDefault = logFormatText -) - -var logger = logrus.New() - -// init initializes the logger -func init() { - logFormat := os.Getenv("LOG_FORMAT") - // default log format is text - if logFormat == "" { - logFormat = logFormatDefault - } - if logFormat != logFormatJSON && logFormat != logFormatText { - fmt.Printf("Unsupported logging format format: '%s' - setting value to default: '%s'\n", logFormat, logFormatDefault) - logFormat = logFormatDefault - } - - if logFormat == logFormatJSON { - // Log as JSON instead of the default ASCII formatter. - logger.SetFormatter(UTCFormatter{ - Formatter: &logrus.JSONFormatter{ - TimestampFormat: time.RFC3339, - PrettyPrint: false, - }, - }) - } else { - // Log as text - logger.SetFormatter(UTCFormatter{ - Formatter: &logrus.TextFormatter{ - DisableColors: false, - TimestampFormat: time.RFC3339, - FullTimestamp: true, - }, - }) - } - - // Default log level - logger.SetLevel(logrus.DebugLevel) - - envLogLevel := os.Getenv("LOG_LEVEL") - switch strings.ToLower(envLogLevel) { - case "panic": - logger.SetLevel(logrus.PanicLevel) - case "fatal": - logger.SetLevel(logrus.FatalLevel) - case "error": - logger.SetLevel(logrus.ErrorLevel) - case "warn", "warning": - logger.SetLevel(logrus.WarnLevel) - case "info": - logger.SetLevel(logrus.InfoLevel) - case "debug": - logger.SetLevel(logrus.DebugLevel) - case "trace": - logger.SetLevel(logrus.TraceLevel) - } - logger.Infof("Logger Format : %s", logFormat) - logger.Infof("Logger Level : %s", logger.GetLevel()) -} - -// GetLogger returns an instance of our logger -func GetLogger() *logrus.Logger { - return logger -} - -// WithField log message with field -func WithField(key string, value interface{}) *logrus.Entry { - return logger.WithField(key, value) -} - -// Warn log message -func Warn(msg string) { - logger.Warn(msg) -} - -// Warnf log message -func Warnf(msg string, args ...interface{}) { - logger.Warnf(msg, args...) -} - -// Info log message -func Info(msg string) { - logger.Info(msg) -} - -// Infof log message -func Infof(msg string, args ...interface{}) { - logger.Infof(msg, args...) -} - -// Debug log message -func Debug(msg string) { - logger.Debug(msg) -} - -// Debugf log message -func Debugf(msg string, args ...interface{}) { - logger.Debugf(msg, args...) -} - -// Error log message with fields -func Error(trace string, err error) { - logger.WithFields(logrus.Fields{ - "line": trace, - }).Error(err) -} - -// Fatal log message -func Fatal(args ...interface{}) { - logger.Fatal(args...) -} - -// Fatalf log message -func Fatalf(msg string, args ...interface{}) { - logger.Fatalf(msg, args...) -} - -// Panic log message -func Panic(args ...interface{}) { - logger.Panic(args...) -} - -// Panicf log message -func Panicf(msg string, args ...interface{}) { - logger.Panicf(msg, args...) -} - -// Println log message -func Println(args ...interface{}) { - logger.Println(args...) -} - -// Printf ... -func Printf(msg string, args ...interface{}) { - logger.Printf(msg, args...) -} - -// WithFields logs a message with fields -func WithFields(fields logrus.Fields) *logrus.Entry { - return logger.WithFields(fields) -} - -// WithError logs a message with the specified error -func WithError(err error) *logrus.Entry { - return logger.WithField("error", err) -} - -// Trace returns the source code line and function name (of the calling function) -func Trace() (line string) { - pc := make([]uintptr, 15) - n := runtime.Callers(2, pc) - frames := runtime.CallersFrames(pc[:n]) - frame, _ := frames.Next() - - return fmt.Sprintf("%s,:%d %s\n", frame.File, frame.Line, frame.Function) -} - -// GenerateUUID is function to generate our own uuid if the google uuid throws error -func GenerateUUID() string { - Debug("entering func generateUUID") - b := make([]byte, 16) - _, err := rand.Read(b) - if err != nil { - Error(Trace(), err) - return "" - } - theUUID := fmt.Sprintf("%x-%x-%x-%x-%x", - b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) - return theUUID -} - -// GetRequestID is function to generate uuid as request id if client doesn't pass X-REQUEST-ID request header -func GetRequestID(requestIDParams *string) string { - Debug("entering func getRequestID") - //generate UUID as request ID if it doesn't exist in request header - if requestIDParams == nil || *requestIDParams == "" { - theUUID, err := uuid.NewUUID() - newUUID := "" - if err == nil { - newUUID = theUUID.String() - } else { - newUUID = GenerateUUID() - } - requestIDParams = &newUUID - } - return *requestIDParams -} diff --git a/infra/ssm/main.go b/infra/ssm/main.go deleted file mode 100644 index 598daff23..000000000 --- a/infra/ssm/main.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ssm" - log "github.com/communitybridge/easycla/infra/ssm/logging" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - region string - stage string - parameterKey string -) - -func init() { - log.Info("Running init...") - - // cobra.OnInitialize(initConfig) - viper.AutomaticEnv() - defaults := map[string]interface{}{ - "REGION": "us-east-1", - "STAGE": "dev", - } - - for key, value := range defaults { - viper.SetDefault(key, value) - } - - rootCmd.PersistentFlags().StringVarP(®ion, "region", "r", "us-east-1", "the AWS region") - rootCmd.PersistentFlags().StringVarP(&stage, "stage", "s", "dev", "the environment stage") - rootCmd.PersistentFlags().StringVarP(¶meterKey, "key", "k", "", "the SSM parameter key to get") - err := viper.BindPFlag("region", rootCmd.PersistentFlags().Lookup("region")) - if err != nil { - log.Warnf("unable to set 'region' in the configuration - error: %+v", err) - } - err = viper.BindPFlag("stage", rootCmd.PersistentFlags().Lookup("stage")) - if err != nil { - log.Warnf("unable to set 'stage' in the configuration - error: %+v", err) - } - err = viper.BindPFlag("key", rootCmd.PersistentFlags().Lookup("key")) - if err != nil { - log.Warnf("unable to set 'parameterKey' in the configuration - error: %+v", err) - } -} - -// GetAWSSession returns an AWS session based on the region and credentials -func GetAWSSession(awsRegion string) (*session.Session, error) { - log.Debugf("Creating a new AWS session for region: %s", awsRegion) - awsSession := session.Must(session.NewSession( - &aws.Config{ - Region: aws.String(awsRegion), - CredentialsChainVerboseErrors: aws.Bool(true), - MaxRetries: aws.Int(5), - }, - )) - - log.Debugf("Successfully created a new AWS session for region: %s...", awsRegion) - - return awsSession, nil -} - -// getSSMString is a generic routine to fetch the specified key value -func getSSMString(ssmClient *ssm.SSM, key string) (string, error) { - log.Debugf("Loading SSM parameter: %s", key) - value, err := ssmClient.GetParameter(&ssm.GetParameterInput{ - Name: aws.String(key), - WithDecryption: aws.Bool(false), - }) - if err != nil { - log.Warnf("unable to read SSM parameter %s - error: %+v", key, err) - return "", err - } - - return strings.TrimSpace(*value.Parameter.Value), nil -} - -func invoke(cmd *cobra.Command, args []string) { - if viper.GetString("key") == "" { - println("missing --key command line argument") - println(cmd.UsageString()) - return - } - - awsSession, err := GetAWSSession(viper.GetString("region")) - if err != nil { - log.Warnf("Unable to load AWS session - Error: %v", err) - return - } - ssmClient := ssm.New(awsSession) - - log.Debugf("Loading parameter '%s' from region %s, stage: %s", viper.GetString("key"), viper.GetString("region"), viper.GetString("stage")) - value, err := getSSMString(ssmClient, viper.GetString("key")) - if err != nil { - return - } - log.Debugf("Parameter '%s' = %s", viper.GetString("key"), value) -} - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "get-ssm-parameter", - Short: "Gets a SSM Parameter", - Long: "Fetches the specified SSM parameter from the AWS SSM configuration", - Run: invoke, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func main() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} From 0cd153d72dc579de3774abc1c3760d6043ede8e7 Mon Sep 17 00:00:00 2001 From: Amol Sontakke Date: Thu, 4 Feb 2021 14:34:08 +0530 Subject: [PATCH 0043/1276] [#2569] Disabled version 2 option Signed-off-by: Amol Sontakke --- .../cla-console-section/cla-console-section.component.html | 2 +- .../cla-console-section/cla-console-section.component.scss | 6 ++++++ .../cla-console-section/cla-console-section.component.ts | 4 ++-- cla-landing-page/yarn.lock | 5 +++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index 307b0e86b..cf9e8edbd 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -79,7 +79,7 @@
-
EasyCLA Version 2
diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss index 13a7f44f7..18ef96d76 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss @@ -135,6 +135,12 @@ background-color: #8bc34a; color: #fff; } + + &.disabled { + background-color: gray; + color: #fff; + cursor: not-allowed; + } } .error { diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index 3f6aa804b..38494670a 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -100,8 +100,8 @@ export class ClaConsoleSectionComponent implements OnInit { window.open(AppSettings.CONTRIBUTORS_LEARN_MORE, '_blank'); } - onClickVersion(version) { - this.selectedVersion = version; + onClickVersion(version?) { + this.selectedVersion = version ? version : ''; } onClickVersionProceed() { diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 7bbcba9c3..8e79810cb 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1041,6 +1041,11 @@ resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== +"@ng-bootstrap/ng-bootstrap@^6.1.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-6.2.0.tgz#0506d612ca6002bd8fa398d006fa2641013e11d4" + integrity sha512-wqwhnJFyEwvzWQJjXrEt+7oBTSvu2qPbdYvUFYhDVzOJLWB5M7YEhDAkMrfHQJ0pZNBMGr580FqYue+XiURY0Q== + "@ngtools/webpack@9.1.12": version "9.1.12" resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-9.1.12.tgz#a6e2feb40db442335a1c79af4044d399767b5e81" From 4fbe82343c786c03d1132eaaaf01a69e524a49a5 Mon Sep 17 00:00:00 2001 From: Amol Sontakke <56335654+amolsontakke3576@users.noreply.github.com> Date: Thu, 4 Feb 2021 20:41:19 +0530 Subject: [PATCH 0044/1276] Gray-out/disabled "LFX Version 2" from Production EasyCLA landing page [Only for Production] (#2574) Signed-off-by: Amol Sontakke --- .../cla-console-section/cla-console-section.component.html | 2 +- .../cla-console-section/cla-console-section.component.scss | 6 ++++++ .../cla-console-section/cla-console-section.component.ts | 4 ++-- cla-landing-page/yarn.lock | 5 +++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index 307b0e86b..cf9e8edbd 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -79,7 +79,7 @@
-
EasyCLA Version 2
diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss index 13a7f44f7..18ef96d76 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss @@ -135,6 +135,12 @@ background-color: #8bc34a; color: #fff; } + + &.disabled { + background-color: gray; + color: #fff; + cursor: not-allowed; + } } .error { diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index 3f6aa804b..38494670a 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -100,8 +100,8 @@ export class ClaConsoleSectionComponent implements OnInit { window.open(AppSettings.CONTRIBUTORS_LEARN_MORE, '_blank'); } - onClickVersion(version) { - this.selectedVersion = version; + onClickVersion(version?) { + this.selectedVersion = version ? version : ''; } onClickVersionProceed() { diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 7bbcba9c3..8e79810cb 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1041,6 +1041,11 @@ resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== +"@ng-bootstrap/ng-bootstrap@^6.1.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-6.2.0.tgz#0506d612ca6002bd8fa398d006fa2641013e11d4" + integrity sha512-wqwhnJFyEwvzWQJjXrEt+7oBTSvu2qPbdYvUFYhDVzOJLWB5M7YEhDAkMrfHQJ0pZNBMGr580FqYue+XiURY0Q== + "@ngtools/webpack@9.1.12": version "9.1.12" resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-9.1.12.tgz#a6e2feb40db442335a1c79af4044d399767b5e81" From dee0b84a7720a1a7eab978ec88cebd6f9135e606 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 4 Feb 2021 15:32:05 -0500 Subject: [PATCH 0045/1276] Snyk GitHub Action (#2575) --- .github/workflows/snyk.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/snyk.yml diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml new file mode 100644 index 000000000..c308e0c43 --- /dev/null +++ b/.github/workflows/snyk.yml @@ -0,0 +1,13 @@ +name: Snyk Scanning +on: push +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/node@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + command: monitor From 770f38d44792edc850af4aaba8a031a8985e4a35 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 4 Feb 2021 20:34:20 -0500 Subject: [PATCH 0046/1276] Seting Signatory Name/Email in UI - API Performance Improvements (#2577) - Provide signatory name/email in Ui to API for v1 - Improved performance issue with GetCompanyProjectCLA API - added go routines to work faster - Updated logging and error handling - Check for duplicate repository records before adding Signed-off-by: David Deal --- cla-backend-go/project/repository.go | 22 +- cla-backend-go/repositories/handlers.go | 2 +- cla-backend-go/repositories/repository.go | 61 ++-- cla-backend-go/repositories/service.go | 12 +- cla-backend-go/swagger/cla.v2.yaml | 4 + cla-backend-go/utils/constants.go | 7 +- cla-backend-go/utils/errors.go | 60 +++- cla-backend-go/v2/company/service.go | 264 ++++++++++++------ cla-backend-go/v2/github_activity/service.go | 4 +- cla-backend-go/v2/repositories/handlers.go | 15 +- cla-backend-go/v2/repositories/service.go | 27 ++ cla-backend/cla/models/docusign_models.py | 15 +- cla-backend/cla/routes.py | 6 + .../cla-corporate-page/cla-corporate-page.ts | 17 +- 14 files changed, 363 insertions(+), 153 deletions(-) diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 9553c31be..796d5482b 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -83,7 +83,7 @@ type repo struct { // CreateCLAGroup creates a new CLA Group func (repo *repo) CreateCLAGroup(ctx context.Context, claGroupModel *models.ClaGroup) (*models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "CreateCLAGroup", + "functionName": "project.repository.CreateCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectName": claGroupModel.ProjectName, "projectExternalID": claGroupModel.ProjectExternalID, @@ -147,7 +147,7 @@ func (repo *repo) CreateCLAGroup(ctx context.Context, claGroupModel *models.ClaG func (repo *repo) getCLAGroupByID(ctx context.Context, claGroupID string, loadCLAGroupDetails bool) (*models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "getCLAGroupByID", + "functionName": "project.repository.getCLAGroupByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "loadProjectDetails": loadCLAGroupDetails, @@ -202,7 +202,7 @@ func (repo *repo) GetCLAGroupByID(ctx context.Context, claGroupID string, loadRe // GetCLAGroupsByExternalID queries the database and returns a list of the cla groups func (repo *repo) GetCLAGroupsByExternalID(ctx context.Context, params *project.GetProjectsByExternalIDParams, loadRepoDetails bool) (*models.ClaGroups, error) { f := logrus.Fields{ - "functionName": "GetCLAGroupsByExternalID", + "functionName": "project.repository.GetCLAGroupsByExternalID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ProjectSFID": params.ProjectSFID, "NextKey": params.NextKey, @@ -302,7 +302,7 @@ func (repo *repo) GetCLAGroupsByExternalID(ctx context.Context, params *project. // GetClaGroupsByFoundationID queries the database and returns a list of all cla_groups associated with foundation func (repo *repo) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSFID string, loadRepoDetails bool) (*models.ClaGroups, error) { f := logrus.Fields{ - "functionName": "GetClaGroupsByFoundationSFID", + "functionName": "project.repository.GetClaGroupsByFoundationSFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "foundationSFID": foundationSFID, "loadRepoDetails": loadRepoDetails, @@ -363,7 +363,7 @@ func (repo *repo) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSF // GetClaGroupsByProjectSFID returns cla_group associated with project func (repo *repo) GetClaGroupByProjectSFID(ctx context.Context, projectSFID string, loadRepoDetails bool) (*models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "GetClaGroupByProjectSFID", + "functionName": "project.repository.GetClaGroupByProjectSFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "loadRepoDetails": loadRepoDetails, @@ -382,7 +382,7 @@ func (repo *repo) GetClaGroupByProjectSFID(ctx context.Context, projectSFID stri // GetCLAGroupByName returns the project model associated for the specified project name func (repo *repo) GetCLAGroupByName(ctx context.Context, projectName string) (*models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "GetCLAGroupByName", + "functionName": "project.repository.GetCLAGroupByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectName": projectName, "tableName": repo.claGroupTable, @@ -442,7 +442,7 @@ func (repo *repo) GetCLAGroupByName(ctx context.Context, projectName string) (*m // GetExternalCLAGroup returns the project model associated for the specified external project ID func (repo *repo) GetExternalCLAGroup(ctx context.Context, projectExternalID string) (*models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "GetExternalCLAGroup", + "functionName": "project.repository.GetExternalCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectExternalID": projectExternalID, "tableName": repo.claGroupTable} @@ -499,7 +499,7 @@ func (repo *repo) GetExternalCLAGroup(ctx context.Context, projectExternalID str // GetCLAGroups queries the database and returns a list of the projects func (repo *repo) GetCLAGroups(ctx context.Context, params *project.GetProjectsParams) (*models.ClaGroups, error) { f := logrus.Fields{ - "functionName": "GetCLAGroups", + "functionName": "project.repository.GetCLAGroups", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "searchField": params.SearchField, "searchTerm": params.SearchTerm, @@ -596,7 +596,7 @@ func (repo *repo) GetCLAGroups(ctx context.Context, params *project.GetProjectsP // DeleteCLAGroup deletes the CLAGroup by claGroupID func (repo *repo) DeleteCLAGroup(ctx context.Context, claGroupID string) error { f := logrus.Fields{ - "functionName": "DeleteCLAGroup", + "functionName": "project.repository.DeleteCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "tableName": repo.claGroupTable} @@ -635,7 +635,7 @@ func (repo *repo) DeleteCLAGroup(ctx context.Context, claGroupID string) error { // UpdateCLAGroup updates the project by claGroupID func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaGroup) (*models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "UpdateCLAGroup", + "functionName": "project.repository.UpdateCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ProjectID": claGroupModel.ProjectID, "ProjectName": claGroupModel.ProjectName, @@ -754,7 +754,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG func (repo *repo) UpdateRootCLAGroupRepositoriesCount(ctx context.Context, claGroupID string, diff int64, reset bool) error { f := logrus.Fields{ - "functionName": "UpdateRootCLAGroupRepositoriesCount", + "functionName": "project.repository.UpdateRootCLAGroupRepositoriesCount", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "diff": diff, diff --git a/cla-backend-go/repositories/handlers.go b/cla-backend-go/repositories/handlers.go index 7fb6f64d6..0ccc68d13 100644 --- a/cla-backend-go/repositories/handlers.go +++ b/cla-backend-go/repositories/handlers.go @@ -81,7 +81,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv } ghRepo, err := service.GetRepository(ctx, params.RepositoryID) if err != nil { - if err == ErrGithubRepositoryNotFound { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { return github_repositories.NewDeleteProjectGithubRepositoryNotFound() } return github_repositories.NewDeleteProjectGithubRepositoryBadRequest().WithPayload(errorResponse(err)) diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 3f3d5c9fb..28988c713 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -44,11 +44,6 @@ const ( RepositoryNameIndex = "repository-name-index" ) -// errors -var ( - ErrGithubRepositoryNotFound = errors.New(utils.GithubRepoNotFound) -) - // Repository defines functions of Repositories type Repository interface { AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) @@ -85,7 +80,7 @@ type repo struct { // AddGithubRepository adds the specified repository func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "AddGitHubRepository", + "functionName": "repositories.repository.AddGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "externalProjectID": externalProjectID, "projectSFID": projectSFID, @@ -99,7 +94,7 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, _, err := r.GetRepositoryByGithubID(ctx, utils.StringValue(input.RepositoryExternalID), true) if err != nil { // Expecting Not found - no issue if not found - all other error we throw - if err != ErrGithubRepositoryNotFound { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { return nil, err } } else { @@ -204,7 +199,7 @@ func (r repo) DisableRepositoriesOfGithubOrganization(ctx context.Context, exter // GetRepository by repository id func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "GetRepository", + "functionName": "repositories.repository.GetRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, } @@ -221,8 +216,11 @@ func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models. return nil, err } if len(result.Item) == 0 { - log.WithFields(f).Warn("repository with ID does not exist") - return nil, ErrGithubRepositoryNotFound + msg := fmt.Sprintf("repository with ID: %s does not exist", repositoryID) + log.WithFields(f).Warn(msg) + return nil, &utils.GitHubRepositoryNotFound{ + Message: msg, + } } var out RepositoryDBModel @@ -238,7 +236,7 @@ func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models. // GetRepositoryByName fetches the repository by repository name func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "GetRepositoryByName", + "functionName": "repositories.repository.GetRepositoryByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryName": repositoryName, } @@ -270,7 +268,9 @@ func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) ( if len(results.Items) == 0 { log.WithFields(f).Warn("no repositories found with repository name") - return nil, ErrGithubRepositoryNotFound + return nil, &utils.GitHubRepositoryNotFound{ + RepositoryName: repositoryName, + } } var repositories []*RepositoryDBModel @@ -290,7 +290,7 @@ func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) ( // GetRepositoryByCLAGroup gets the list of repositories based on the CLA Group ID func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "GetRepositoryByCLAGroup", + "functionName": "repositories.repository.GetRepositoryByCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "enabled": enabled, @@ -322,8 +322,11 @@ func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, } if len(results.Items) == 0 { - log.WithFields(f).Warn("no repositories found matching the search criteria") - return nil, ErrGithubRepositoryNotFound + msg := fmt.Sprintf("no repositories found associated with CLA Group ID: %s that is enabled", claGroupID) + log.WithFields(f).Warn(msg) + return nil, &utils.GitHubRepositoryNotFound{ + Message: msg, + } } var repositories []*RepositoryDBModel @@ -338,7 +341,7 @@ func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, func (r *repo) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "GetRepositoriesByOrganizationName", + "functionName": "repositories.repository.GetRepositoriesByOrganizationName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitHubOrgName": gitHubOrgName, } @@ -370,8 +373,11 @@ func (r *repo) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgN } if len(results.Items) == 0 { - log.WithFields(f).Warn("no repositories found matching the search criteria") - return nil, ErrGithubRepositoryNotFound + msg := fmt.Sprintf("no repositories found associated GitHub Organization: %s", gitHubOrgName) + log.WithFields(f).Warn(msg) + return nil, &utils.GitHubRepositoryNotFound{ + Message: msg, + } } var repositories []*RepositoryDBModel @@ -409,7 +415,7 @@ func (r repo) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID // List github repositories of project by external/salesforce project id func (r repo) ListProjectRepositories(ctx context.Context, externalProjectID string, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { f := logrus.Fields{ - "functionName": "ListProjectRepositories", + "functionName": "repositories.repository.ListProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "externalProjectID": externalProjectID, "projectSFID": projectSFID, @@ -472,7 +478,7 @@ func (r repo) ListProjectRepositories(ctx context.Context, externalProjectID str // getProjectRepositories returns an array of GH repositories for the specified project ID func (r repo) getProjectRepositories(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "getProjectRepositories", + "functionName": "repositories.repository.getProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectID": projectID, "enabled": enabled, @@ -520,7 +526,7 @@ func (r repo) getProjectRepositories(ctx context.Context, projectID string, enab // getRepositoriesByGithubOrg returns an array of GH repositories for the specified project ID func (r repo) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName string) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "getRepositoriesByGitHubOrg", + "functionName": "repositories.repository.getRepositoriesByGitHubOrg", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "githubOrgName": githubOrgName, } @@ -565,7 +571,7 @@ func (r repo) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName stri // GetRepositoryByGithubID fetches the repository model by its external github id func (r repo) GetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "GetRepositoryByGitHubID", + "functionName": "repositories.repository.GetRepositoryByGitHubID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "externalID": externalID, "enabled": enabled, @@ -600,7 +606,10 @@ func (r repo) GetRepositoryByGithubID(ctx context.Context, externalID string, en } var result *RepositoryDBModel if len(results.Items) == 0 { - return nil, ErrGithubRepositoryNotFound + msg := fmt.Sprintf("no repository found matching external repository ID: %s", externalID) + return nil, &utils.GitHubRepositoryNotFound{ + Message: msg, + } } err = dynamodbattribute.UnmarshalMap(results.Items[0], &result) if err != nil { @@ -625,7 +634,7 @@ func (r repo) disableGithubRepository(ctx context.Context, repositoryID string) // setEnabledGithubRepository updates the existing repository record by setting the enabled flag to false func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID string, enabled bool) error { f := logrus.Fields{ - "functionName": "setEnabledGitHubRepository", + "functionName": "repositories.repository.setEnabledGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "enabled": enabled, @@ -695,7 +704,7 @@ func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID strin // setEnabledGithubRepositoryWithCLAGroupID updates the existing repository record by setting the enabled flag to false func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string, enabled bool) error { f := logrus.Fields{ - "functionName": "setEnabledGitHubRepositoryWithCLAGroupID", + "functionName": "repositories.repository.setEnabledGitHubRepositoryWithCLAGroupID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "claGroupID": claGroupID, @@ -770,7 +779,7 @@ func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repo // setEnabledGithubRepository updates the existing repository record by setting the enabled flag to false func (r repo) setClaGroupIDGithubRepository(ctx context.Context, repositoryID, claGroupID string) error { f := logrus.Fields{ - "functionName": "setClaGroupIDGitHubRepository", + "functionName": "repositories.repository.setClaGroupIDGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "claGroupID": claGroupID, diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index 4c7639699..67d7210ef 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -117,8 +117,8 @@ func (s *service) AddGithubRepository(ctx context.Context, externalProjectID str existingModel, err := s.GetRepositoryByName(ctx, utils.StringValue(input.RepositoryName)) if err != nil { // If not found - ok, otherwise we have a bigger problem - if errors.Is(err, ErrGithubRepositoryNotFound) { - log.WithFields(f).Debug("existing repository not found - will create") + if notFoundErr, ok := err.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).WithError(notFoundErr).Debug("existing repository not found - will create") } else { return nil, err } @@ -126,10 +126,10 @@ func (s *service) AddGithubRepository(ctx context.Context, externalProjectID str if existingModel != nil { log.WithFields(f).Debug("existing repository found - enabling it...") - err := s.EnableRepositoryWithCLAGroupID(ctx, existingModel.RepositoryID, utils.StringValue(input.RepositoryProjectID)) - if err != nil { - log.WithFields(f).WithError(err).Warn("problem enabling repository") - return nil, err + enableErr := s.EnableRepositoryWithCLAGroupID(ctx, existingModel.RepositoryID, utils.StringValue(input.RepositoryProjectID)) + if enableErr != nil { + log.WithFields(f).WithError(enableErr).Warn("problem enabling repository") + return nil, enableErr } return s.repo.GetRepository(ctx, existingModel.RepositoryID) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 5f4b1faf5..b48b3e196 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1288,6 +1288,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' '500': $ref: '#/responses/internal-server-error' tags: @@ -1438,6 +1440,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' '500': $ref: '#/responses/internal-server-error' tags: diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 3d2fd9d53..fab25e9b9 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -120,5 +120,8 @@ const RecordModified = "MODIFY" //RecordAdded dynami event on adding a record const RecordAdded = "INSERT" -//GithubRepoNotFound representing not found repos for CLAGroupID -const GithubRepoNotFound = "github repository not found" +//GithubRepoNotFound is a string that indicates the GitHub repository is not found +const GithubRepoNotFound = "GitHub repository not found" + +//GithubRepoExists is a string that indicates the GitHub repository already exists +const GithubRepoExists = "GitHub repository exists" diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index de860c7a8..5ec837bb5 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -179,7 +179,7 @@ type CompanyAdminNotFound struct { Err error } -// Error is an error string function for Salesforce Project not found errors +// Error is an error string function for the CompanyAdminNotFound model func (e *CompanyAdminNotFound) Error() string { if e.Err == nil { return fmt.Sprintf("company admin for company with ID %s not found", e.CompanySFID) @@ -202,7 +202,7 @@ type CompanyNotFound struct { Err error } -// Error is an error string function for Salesforce Project not found errors +// Error is an error string function for the CompanyNotFound model func (e *CompanyNotFound) Error() string { msg := "company does not exist " if e.Message != "" { @@ -231,3 +231,59 @@ func (e *CompanyNotFound) Error() string { func (e *CompanyNotFound) Unwrap() error { return e.Err } + +// GitHubRepositoryNotFound is an error model for a GitHub repository not found +type GitHubRepositoryNotFound struct { + Message string + RepositoryName string + Err error +} + +// Error is an error string function for the GitHubRepositoryNotFound model +func (e *GitHubRepositoryNotFound) Error() string { + msg := GithubRepoNotFound + if e.Message != "" { + msg = e.Message + } + if e.RepositoryName != "" { + msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *GitHubRepositoryNotFound) Unwrap() error { + return e.Err +} + +// GitHubRepositoryExists is an error model for when a GitHub repository already exists +type GitHubRepositoryExists struct { + Message string + RepositoryName string + Err error +} + +// Error is an error string function for the GitHubRepositoryExists model +func (e *GitHubRepositoryExists) Error() string { + msg := GithubRepoNotFound + if e.Message != "" { + msg = e.Message + } + if e.RepositoryName != "" { + msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *GitHubRepositoryExists) Unwrap() error { + return e.Err +} diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 2dcc824e6..33045bc7a 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -137,6 +137,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod "signingEntityName": v1CompanyModel.SigningEntityName, } + // TODO: DAD - separate go routine log.WithFields(f).Debugf("locating CLA Group(s) under project or foundation...") var err error claGroups, err := s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) @@ -145,6 +146,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod return nil, err } + // TODO: DAD - separate go routine // get the org client for org info filling orgClient := orgService.GetClient() orgModel, err := orgClient.GetOrganization(ctx, v1CompanyModel.CompanyExternalID) @@ -152,6 +154,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod return nil, fmt.Errorf("fetching org model failed for companySFID : %s : %w", v1CompanyModel.CompanyExternalID, err) } + // TODO: DAD - separate go routine signed, approved := true, true maxLoad := int64(10) var sigs []*v1Models.Signature @@ -199,6 +202,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod return &models.CompanyClaManagers{List: claManagers}, nil } + // TODO: DAD - separate go routine // get userinfo and project info var usermap map[string]*v2UserServiceModels.User usermap, err = getUsersInfo(lfUsernames.List()) @@ -211,6 +215,8 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod fillUsersInfo(claManagers, usermap) // fill project info fillProjectInfo(claManagers, claGroups) + + // TODO: DAD - separate go routine // fetch the cla_manager.added events so can fill the addedOn field claManagerAddedEvents, err := s.eventService.GetCompanyEvents(v1CompanyModel.CompanyID, events.ClaManagerCreated, nil, aws.Int64(100), true) if err != nil { @@ -803,13 +809,14 @@ func (s *service) DeleteCompanyBySFID(ctx context.Context, companyID string) err func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompanyProjectCLA", + "functionName": "companyModel.service.GetCompanyProjectCLA", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, "companySFID": companySFID, "projectSFID": projectSFID, } + var canSign bool resources := authUser.ResourceIDsByTypeAndRole(auth.ProjectOrganization, utils.CLADesigneeRole) projectOrg := fmt.Sprintf("%s|%s", projectSFID, companySFID) @@ -820,101 +827,186 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, } } - // Attempt to locate the company model in our database - log.WithFields(f).Debug("locating company by SF ID") - var companyModel *v1Models.Company - var companies []*v1Models.Company - var companyErr error - companies, companyErr = s.companyRepo.GetCompaniesByExternalID(ctx, companySFID) - if companyErr != nil { - // If we were unable to find the company/org in our local database, try to auto-create based - // on the existing SF record - if _, ok := companyErr.(*utils.CompanyNotFound); ok { // nolint - log.WithFields(f).WithError(companyErr).Debug("company not found in EasyCLA database - attempting to auto-create from platform organization service record") - var createCompanyErr error - companyModel, createCompanyErr = s.autoCreateCompany(ctx, companySFID) - if createCompanyErr != nil { - log.WithFields(f).WithError(createCompanyErr).Warn("problem creating company from platform organization SF record") - return nil, createCompanyErr - } - if companyModel == nil { - log.WithFields(f).Warnf("problem creating company from SF records - created model is nil") - return nil, &utils.CompanyNotFound{ - Message: "unable to auto-create company", - CompanySFID: companySFID, + // Channels for returning the results + type CompaniesResult struct { + CompanyError error + Companies []*v1Models.Company + } + companiesChannel := make(chan *CompaniesResult, 1) + + type CLAGroupsResult struct { + CLAGroupError error + CLAGroups map[string]*claGroupModel + } + claGroupsChannel := make(chan *CLAGroupsResult, 1) + + // Separate go routine + go func(ctx context.Context, companySFID string, companyID *string) { + // Attempt to locate the companyModel model in our database + log.WithFields(f).Debug("locating companyModel by SF ID") + companies, companyErr := s.companyRepo.GetCompaniesByExternalID(ctx, companySFID) + if companyErr != nil { + // If we were unable to find the companyModel/org in our local database, try to auto-create based + // on the existing SF record + if _, ok := companyErr.(*utils.CompanyNotFound); ok { // nolint + log.WithFields(f).WithError(companyErr).Debug("companyModel not found in EasyCLA database - attempting to auto-create from platform organization service record") + companyModel, createCompanyErr := s.autoCreateCompany(ctx, companySFID) + if createCompanyErr != nil { + log.WithFields(f).WithError(createCompanyErr).Warn("problem creating companyModel from platform organization SF record") + companiesChannel <- &CompaniesResult{ + CompanyError: createCompanyErr, + Companies: nil, + } + } else if companyModel == nil { + log.WithFields(f).Warnf("problem creating companyModel from SF records - created model is nil") + companiesChannel <- &CompaniesResult{ + CompanyError: &utils.CompanyNotFound{ + Message: "unable to auto-create companyModel", + CompanySFID: companySFID, + }, + Companies: nil, + } + } else { + // Success - send the results + companiesChannel <- &CompaniesResult{ + CompanyError: nil, + Companies: []*v1Models.Company{companyModel}, + } + } + } else { + log.WithFields(f).WithError(companyErr).Warnf("problem fetching companyModel by SFID") + companiesChannel <- &CompaniesResult{ + CompanyError: companyErr, + Companies: nil, } } - // Success, fall through and continue processing + } + + if companyID != nil { + log.WithFields(f).Debugf("Filtering companyModel for ID: %s ", *companyID) + index, found := findCompany(companies, *companyID) + if found { + log.WithFields(f).Debugf("Found companyModel: %v ", companies[index]) + companies = []*v1Models.Company{companies[index]} + } else { + companies = []*v1Models.Company{} + } + } + + // Return the result through the channel + companiesChannel <- &CompaniesResult{ + CompanyError: nil, + Companies: companies, + } + }(ctx, companySFID, companyID) + + // Separate go routine + go func(ctx context.Context, projectSFID string) { + claGroups, err := s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) + if err != nil { + log.WithFields(f).Warnf("problem fetching CLA Groups under project or foundation, error: %+v", err) + claGroupsChannel <- &CLAGroupsResult{ + CLAGroupError: err, + CLAGroups: nil, + } } else { - log.WithFields(f).WithError(companyErr).Warnf("problem fetching company by SFID") - return nil, companyErr + claGroupsChannel <- &CLAGroupsResult{ + CLAGroupError: nil, + CLAGroups: claGroups, + } } + }(ctx, projectSFID) + + // Grab the results + companyResponse := <-companiesChannel + if companyResponse.CompanyError != nil { + return nil, companyResponse.CompanyError } - claGroups, err := s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) - if err != nil { - log.WithFields(f).Warnf("problem fetching CLA Groups under project or foundation, error: %+v", err) - return nil, err + claGroupResponse := <-claGroupsChannel + if claGroupResponse.CLAGroupError != nil { + return nil, claGroupResponse.CLAGroupError } - var companyProjectClaList = make([]*models.CompanyProjectCla, 0) - if companyID != nil { - log.WithFields(f).Debugf("Filtering company for ID: %s ", *companyID) - index, found := findCompany(companies, *companyID) - if found { - log.WithFields(f).Debugf("Found company: %v ", companies[index]) - companies = []*v1Models.Company{companies[index]} - } else { - companies = []*v1Models.Company{} - } + // Setup another channel to fetch all these results + type CompanyProjectCLAList struct { + CompanyProjectCLAError error + CompanyProjectCLA *models.CompanyProjectCla } - for _, company := range companies { - activeCLAList, err := s.GetCompanyProjectActiveCLAs(ctx, company.CompanyID, projectSFID) - if err != nil { - log.WithFields(f).Warnf("problem fetching company project active CLAs, error: %+v", err) - return nil, err - } - var companyProjectCLA = &models.CompanyProjectCla{ - SignedClaList: activeCLAList.List, - UnsignedProjectList: make([]*models.UnsignedProject, 0), - } - for _, activeCLA := range activeCLAList.List { - // remove cla groups for which we have signed cla - log.WithFields(f).Debugf("removing CLA Groups with active CLA, CLA Group: %+v, error: %+v", activeCLA, err) - delete(claGroups, activeCLA.ProjectID) - } - // Get Company details - company, compErr := s.GetCompanyByID(ctx, company.CompanyID) - if compErr != nil { - log.WithFields(f).WithError(compErr).Warnf("unable to fetch company by ID: %s ", company.CompanyID) - return nil, compErr - } - // fill details for not signed cla - for claGroupID, claGroup := range claGroups { - unsignedProject := &models.UnsignedProject{ - CompanyName: company.CompanyName, - SigningEntityName: company.SigningEntityName, - SigningEntityID: company.CompanyID, - CanSign: canSign, - ClaGroupID: claGroupID, - ClaGroupName: claGroup.ClaGroupName, - ProjectName: claGroup.ProjectName, - ProjectSfid: claGroup.ProjectSFID, - SubProjects: claGroup.SubProjects, - IclaEnabled: claGroup.IclaEnabled, - CclaEnabled: claGroup.CclaEnabled, + companyProjectCLAChannel := make(chan *CompanyProjectCLAList, 1) + + // For each company... + for _, companyModel := range companyResponse.Companies { + go func(ctx context.Context, companyModel *v1Models.Company, projectSFID string, claGroups map[string]*claGroupModel) { + + // Fetch the active CLA list for this project + company + activeCLAList, err := s.GetCompanyProjectActiveCLAs(ctx, companyModel.CompanyID, projectSFID) + if err != nil { + log.WithFields(f).Warnf("problem fetching companyModel project active CLAs, error: %+v", err) + companyProjectCLAChannel <- &CompanyProjectCLAList{ + CompanyProjectCLAError: err, + CompanyProjectCLA: nil, + } } - log.WithFields(f).Debugf("adding unsigned CLA Group: %+v, error: %+v", unsignedProject, err) - companyProjectCLA.UnsignedProjectList = append(companyProjectCLA.UnsignedProjectList, unsignedProject) - } - companyProjectClaList = append(companyProjectClaList, companyProjectCLA) - // refresh clagroups for next company instance - claGroups, err = s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) - if err != nil { - log.WithFields(f).Warnf("problem fetching CLA Groups under project or foundation, error: %+v", err) - return nil, err + // Build an inactive list... + inactiveCLAGroups := claGroups + for _, activeCLA := range activeCLAList.List { + // remove cla groups for which we have signed cla + log.WithFields(f).Debugf("removing CLA Groups with active CLA, CLA Group: %+v, error: %+v", activeCLA, err) + delete(inactiveCLAGroups, activeCLA.ProjectID) + } + + var companyProjectCLA = &models.CompanyProjectCla{ + SignedClaList: activeCLAList.List, + UnsignedProjectList: make([]*models.UnsignedProject, 0), + } + + // fill details for not signed cla + for claGroupID, claGroupModel := range inactiveCLAGroups { + unsignedProject := &models.UnsignedProject{ + CompanyName: companyModel.CompanyName, + SigningEntityName: companyModel.SigningEntityName, + SigningEntityID: companyModel.CompanyID, + CanSign: canSign, + ClaGroupID: claGroupID, + ClaGroupName: claGroupModel.ClaGroupName, + ProjectName: claGroupModel.ProjectName, + ProjectSfid: claGroupModel.ProjectSFID, + SubProjects: claGroupModel.SubProjects, + IclaEnabled: claGroupModel.IclaEnabled, + CclaEnabled: claGroupModel.CclaEnabled, + } + //log.WithFields(f).Debugf("adding unsigned CLA Group: %+v, error: %+v", unsignedProject, err) + companyProjectCLA.UnsignedProjectList = append(companyProjectCLA.UnsignedProjectList, unsignedProject) + } + + companyProjectCLAChannel <- &CompanyProjectCLAList{ + CompanyProjectCLAError: err, + CompanyProjectCLA: companyProjectCLA, + } + }(ctx, companyModel, projectSFID, claGroupResponse.CLAGroups) + + /* + // refresh clagroups for next companyModel instance + claGroups, err = s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) + if err != nil { + log.WithFields(f).Warnf("problem fetching CLA Groups under project or foundation, error: %+v", err) + return nil, err + } + */ + } + + // Grab the results + var companyProjectClaList []*models.CompanyProjectCla + for i := 0; i < len(companyResponse.Companies); i++ { + companyProjectCLAResponse := <-companyProjectCLAChannel + if companyProjectCLAResponse.CompanyProjectCLAError != nil { + return nil, companyProjectCLAResponse.CompanyProjectCLAError } + + // No error, save the value + companyProjectClaList = append(companyProjectClaList, companyProjectCLAResponse.CompanyProjectCLA) } return &models.CompanyProjectClaList{ @@ -1618,8 +1710,8 @@ func validateRequestCompanyAdmin(userID string, claManagerName string, contribut } func findCompany(companies []*v1Models.Company, companyID string) (int, bool) { - for index, company := range companies { - if company.CompanyID == companyID { + for index, companyModel := range companies { + if companyModel.CompanyID == companyID { return index, true } } diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index ccf9898ee..d537b0fc4 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -9,6 +9,8 @@ import ( "fmt" "strconv" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/communitybridge/easycla/cla-backend-go/gen/models" "github.com/communitybridge/easycla/cla-backend-go/v2/dynamo_events" @@ -113,7 +115,7 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(sender *github.User, repositoryExternalID := strconv.FormatInt(*repo.ID, 10) repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { - if errors.Is(err, repositories.ErrGithubRepositoryNotFound) { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { log.Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) return nil } diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 19380a3c4..8636a2f3e 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -21,7 +21,6 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/github_repositories" - "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime/middleware" "github.com/jinzhu/copier" @@ -100,6 +99,12 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. result, err := service.AddGithubRepository(ctx, params.ProjectSFID, params.GithubRepositoryInput) if err != nil { + if _, ok := err.(*utils.GitHubRepositoryExists); ok { + msg := fmt.Sprintf("unable to add repository - repository already exists for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return github_repositories.NewAddProjectGithubRepositoryConflict().WithPayload( + utils.ErrorResponseConflictWithError(reqID, msg, err)) + } msg := fmt.Sprintf("problem adding github repositories for projectSFID: %s", params.ProjectSFID) log.WithFields(f).WithError(err).Warn(msg) return github_repositories.NewAddProjectGithubRepositoryBadRequest().WithPayload( @@ -157,7 +162,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. ghRepo, err := service.GetRepository(ctx, params.RepositoryID) if err != nil { - if err == repositories.ErrGithubRepositoryNotFound { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { msg := fmt.Sprintf("repository not found for projectSFID: %s", params.ProjectSFID) log.WithFields(f).WithError(err).Warn(msg) return github_repositories.NewDeleteProjectGithubRepositoryNotFound().WithPayload( @@ -215,7 +220,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. protectedBranch, err := service.GetProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID) if err != nil { - if err == repositories.ErrGithubRepositoryNotFound { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { msg := fmt.Sprintf("unable to locatate branch protection projectSFID: %s, repository: %s", params.ProjectSFID, params.RepositoryID) log.WithFields(f).WithError(err).Warn(msg) return github_repositories.NewGetProjectGithubRepositoryBranchProtectionNotFound().WithPayload( @@ -270,8 +275,8 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. protectedBranch, err := service.UpdateProtectedBranch(ctx, params.RepositoryID, params.ProjectSFID, params.GithubRepositoryBranchProtectionInput) if err != nil { log.Warnf("update protected branch failed for repo %s : %v", params.RepositoryID, err) - if err == repositories.ErrGithubRepositoryNotFound { - msg := fmt.Sprintf("unable to update branch protection projectSFID: %s, repository: %s", params.ProjectSFID, params.RepositoryID) + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { + msg := fmt.Sprintf("unable to update branch protection for projectSFID: %s, repository: %s", params.ProjectSFID, params.RepositoryID) log.WithFields(f).WithError(err).Warn(msg) return github_repositories.NewGetProjectGithubRepositoryBranchProtectionNotFound().WithPayload( utils.ErrorResponseNotFound(reqID, msg)) diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index fc97349d8..fb940872e 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -36,6 +36,7 @@ type Service interface { DisableRepository(ctx context.Context, repositoryID string) error ListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.ListGithubRepositories, error) GetRepository(ctx context.Context, repositoryID string) (*v1Models.GithubRepository, error) + GetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) DisableCLAGroupRepositories(ctx context.Context, claGroupID string) error GetProtectedBranch(ctx context.Context, projectSFID, repositoryID string) (*v2Models.GithubRepositoryBranchProtection, error) UpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) @@ -123,6 +124,28 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i log.WithFields(f).WithError(err).Warn("unable to get repository by external ID") return nil, err } + + // Check if exists already + existingRepositoryModel, lookupErr := s.GetRepositoryByName(ctx, utils.StringValue(ghRepo.FullName)) + if lookupErr != nil { + // If we have the repository not found error - this is ok - we are expecting this + if notFoundErr, ok := lookupErr.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).WithError(notFoundErr).Debugf("GitHub repository lookup didn't find a match for existing repository name: %s - ok to create", utils.StringValue(ghRepo.FullName)) + } else { + // Some other error - not good... + return nil, lookupErr + } + } + + // We already have an existing repository model with the same name + if existingRepositoryModel != nil { + msg := fmt.Sprintf("GitHub repository already exists with repository name: %s", utils.StringValue(ghRepo.FullName)) + log.WithFields(f).Warn(msg) + return nil, &utils.GitHubRepositoryExists{ + Message: msg, + } + } + in := &v1Models.GithubRepositoryInput{ RepositoryExternalID: input.RepositoryGithubID, RepositoryName: ghRepo.FullName, @@ -257,6 +280,10 @@ func (s *service) GetRepository(ctx context.Context, repositoryID string) (*v1Mo return s.repo.GetRepository(ctx, repositoryID) } +func (s *service) GetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) { + return s.repo.GetRepositoryByName(ctx, repositoryName) +} + func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositoryID string) (*v2Models.GithubRepositoryBranchProtection, error) { f := logrus.Fields{ "functionName": "repositories.service.GetProtectedBranch", diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 6bce36446..d9749017a 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -923,11 +923,11 @@ def request_corporate_signature(self, auth_user: object, f'project id: {project_id}, ' f'company id: {company_id}, ' f'signing entity name: {signing_entity_name}, ' - f'send email: {send_as_email}', + f'send email: {send_as_email}, ', f'signatory name: {signatory_name}, ' - f'signatory email: {signatory_email} ' - f'return url type: {return_url_type}', - f'return url: {return_url}', + f'signatory email: {signatory_email}, ' + f'return url type: {return_url_type}, ', + f'return url: {return_url}' ) # Auth user is the currently logged in user - the user who started the signing process @@ -1062,11 +1062,16 @@ def request_corporate_signature(self, auth_user: object, company.load(str(company_id)) cla.log.debug(f'{fn} - Loaded company {company}') + if signing_entity_name is None: + if company.get_signing_entity_name() is None: + signing_entity_name = company.get_company_name() + else: + signing_entity_name = company.get_signing_entity_name() + # Should be the same values...what do we do if they do not match? if company.get_signing_entity_name() != signing_entity_name: cla.log.warning(f'{fn} - signing entity name provided: {signing_entity_name} ' f'does not match the DB company record: {company.get_signing_entity_name()}') - except DoesNotExist as err: cla.log.warning(f'{fn} - Unable to load company by id: {company_id}. ' 'Returning an error response') diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 5a3f371d8..1b4eb787d 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -1245,6 +1245,12 @@ def request_corporate_signature( 'authority_email': 'string', 'return_url': } + { + "project_id": "d8cead54-92b7-48c5-a2c8-b1e295e8f7f1", + "company_id": "83f61e34-4457-45a6-a7ab-449ad6efcfbb", + "return_url": "https://corporate.v1.easycla.lfx.linuxfoundation.org/#/company/83f61e34-4457-45a6-a7ab-449ad6efcfbb" + } + Creates a new signature given project and company IDs. The manager will be redirected to the return_url once signature is complete. diff --git a/cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.ts b/cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.ts index 5c6b5a61a..d6b085488 100644 --- a/cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.ts +++ b/cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.ts @@ -5,14 +5,13 @@ import { Component } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { ClaService } from '../../services/cla.service'; import { generalConstants } from '../../constant/general'; -import { EnvConfig } from '../../services/cla.env.utils'; @IonicPage({ - segment: 'project/:projectId' + segment: 'project/:projectId', }) @Component({ selector: 'cla-corporate-page', - templateUrl: 'cla-corporate-page.html' + templateUrl: 'cla-corporate-page.html', }) export class ClaCorporatePage { projectId: string; @@ -28,7 +27,7 @@ export class ClaCorporatePage { constructor( public navCtrl: NavController, public navParams: NavParams, - private claService: ClaService + private claService: ClaService, ) { this.getDefaults(); this.projectId = navParams.get('projectId'); @@ -37,10 +36,10 @@ export class ClaCorporatePage { getDefaults() { this.project = { - project_name: '' + project_name: '', }; this.signature = { - sign_url: '' + sign_url: '', }; } @@ -64,8 +63,10 @@ export class ClaCorporatePage { let signatureRequest = { project_id: this.projectId, company_id: this.company.company_id, - // TODO: Switch this to intermediary loading screen as docusign postback has delay - return_url: this.getReturnUrl() + send_as_email: false, + authority_name: localStorage.getItem('user_name'), + authority_email: localStorage.getItem('user_email'), + return_url: this.getReturnUrl(), }; this.claService.postCorporateSignatureRequest(signatureRequest).subscribe((response) => { From 056576cbe77897fec672eea9240a9bf28f5f1fac Mon Sep 17 00:00:00 2001 From: Amol Sontakke Date: Fri, 5 Feb 2021 16:11:00 +0530 Subject: [PATCH 0047/1276] [#2569] Changed code as per enviroment Signed-off-by: Amol Sontakke --- .../cla-console-section.component.html | 5 +++-- .../cla-console-section.component.ts | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index cf9e8edbd..44bc1d23a 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -79,8 +79,9 @@
-
EasyCLA Version 2
+
EasyCLA Version + 2
diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index 38494670a..b80a6f6de 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -1,14 +1,14 @@ // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT -import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, Input, isDevMode, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { AppSettings } from 'src/app/config/app-settings'; import { StorageService } from 'src/app/core/services/storage.service'; import { EnvConfig } from 'src/app/config/cla-env-utils'; import { ActivatedRoute } from '@angular/router'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { LandingPageService } from 'src/app/service/landing-page.service'; - +declare let process: any; @Component({ selector: 'app-cla-console-section', templateUrl: './cla-console-section.component.html', @@ -23,6 +23,7 @@ export class ClaConsoleSectionComponent implements OnInit { selectedVersion: string; error: string; consoleType: string; + env: string; constructor( private storageService: StorageService, @@ -44,6 +45,7 @@ export class ClaConsoleSectionComponent implements OnInit { } ngOnInit() { + this.env = process.env.NODE_ENV; this.version = this.router.snapshot.queryParamMap.get('version'); const element: any = document.getElementById('lfx-header'); let projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK] + '#/login'; @@ -100,8 +102,8 @@ export class ClaConsoleSectionComponent implements OnInit { window.open(AppSettings.CONTRIBUTORS_LEARN_MORE, '_blank'); } - onClickVersion(version?) { - this.selectedVersion = version ? version : ''; + onClickVersion(version) { + this.selectedVersion = (this.env === 'production') ? '' : version; } onClickVersionProceed() { From c72e8899581871f9f439479cbcb83758580de976 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 5 Feb 2021 14:43:57 +0300 Subject: [PATCH 0048/1276] [#2579] Bug/Get Company by External ID - Resolved issue caused by recursion in the get company by ExternalID endpoint Signed-off-by: wanyaland --- cla-backend-go/company/service.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index e9508a143..cc48c7808 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -740,18 +740,11 @@ func (s service) CreateOrgFromExternalID(ctx context.Context, signingEntityName, var companyModel *models.Company var lookupErr error - if signingEntityName == "" { - // Lookup the company in our database...does it exist? - companyModel, lookupErr = s.GetCompanyByExternalID(ctx, companySFID) - if lookupErr != nil { - log.WithFields(f).WithError(lookupErr).Debug("problem locating internal company record by signing entity name and SFID - must not exist yet") - } - } else { - // Lookup the company in our database...does it exist? - companyModel, lookupErr = s.GetCompanyBySigningEntityName(ctx, signingEntityName, companySFID) - if lookupErr != nil { - log.WithFields(f).WithError(lookupErr).Debug("problem locating internal company record by signing entity name and SFID - must not exist yet") - } + + // Lookup the company in our database...does it exist? + companyModel, lookupErr = s.GetCompanyBySigningEntityName(ctx, signingEntityName, companySFID) + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Debug("problem locating internal company record by signing entity name and SFID - must not exist yet") } // Already exists - no need to create in our own database From a92b2d0862fd8ad9194595167ad793b21bd31b58 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 5 Feb 2021 15:13:23 +0300 Subject: [PATCH 0049/1276] [#2580] Bug/Add Repository - Resolved add GH repository error handling issue Signed-off-by: wanyaland --- cla-backend-go/repositories/repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 28988c713..738d994fa 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -94,7 +94,7 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, _, err := r.GetRepositoryByGithubID(ctx, utils.StringValue(input.RepositoryExternalID), true) if err != nil { // Expecting Not found - no issue if not found - all other error we throw - if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { + if _, ok := err.(*utils.GitHubRepositoryNotFound); !ok { return nil, err } } else { From cba3d06de9b05f2dd303099222609e9a994d472d Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 11:55:04 -0500 Subject: [PATCH 0050/1276] =?UTF-8?q?Resolved=20#2576=20Updated=20V1=20Ema?= =?UTF-8?q?il=20Regex=20-=20Allow=2012=20characters=20in=20domain=E2=80=A6?= =?UTF-8?q?=20(#2585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: David Deal --- cla-frontend-contributor-console/src/ionic/validators/email.ts | 2 +- cla-frontend-corporate-console/src/ionic/validators/email.ts | 2 +- cla-frontend-project-console/src/ionic/validators/email.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/src/ionic/validators/email.ts b/cla-frontend-contributor-console/src/ionic/validators/email.ts index 6a843aee6..48817974b 100644 --- a/cla-frontend-contributor-console/src/ionic/validators/email.ts +++ b/cla-frontend-contributor-console/src/ionic/validators/email.ts @@ -5,7 +5,7 @@ import { FormControl } from '@angular/forms'; export class EmailValidator { static isValid(control: FormControl): any { - const EMAIL_PATTERN = new RegExp(/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/); + const EMAIL_PATTERN = new RegExp(/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,12})$/); let email = control.value; let isValid = EMAIL_PATTERN.test(email); if (!isValid) { diff --git a/cla-frontend-corporate-console/src/ionic/validators/email.ts b/cla-frontend-corporate-console/src/ionic/validators/email.ts index 6a843aee6..48817974b 100644 --- a/cla-frontend-corporate-console/src/ionic/validators/email.ts +++ b/cla-frontend-corporate-console/src/ionic/validators/email.ts @@ -5,7 +5,7 @@ import { FormControl } from '@angular/forms'; export class EmailValidator { static isValid(control: FormControl): any { - const EMAIL_PATTERN = new RegExp(/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/); + const EMAIL_PATTERN = new RegExp(/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,12})$/); let email = control.value; let isValid = EMAIL_PATTERN.test(email); if (!isValid) { diff --git a/cla-frontend-project-console/src/ionic/validators/email.ts b/cla-frontend-project-console/src/ionic/validators/email.ts index 6a843aee6..48817974b 100644 --- a/cla-frontend-project-console/src/ionic/validators/email.ts +++ b/cla-frontend-project-console/src/ionic/validators/email.ts @@ -5,7 +5,7 @@ import { FormControl } from '@angular/forms'; export class EmailValidator { static isValid(control: FormControl): any { - const EMAIL_PATTERN = new RegExp(/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/); + const EMAIL_PATTERN = new RegExp(/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,12})$/); let email = control.value; let isValid = EMAIL_PATTERN.test(email); if (!isValid) { From 7c9dcab19317238955e1d5f215abf6210ad74663 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 18:34:30 -0500 Subject: [PATCH 0051/1276] Resolved [#2586] Cleaned Up Signing Entity Name Query Logic (#2587) Signed-off-by: David Deal --- cla-backend-go/company/models.go | 20 ++++-- cla-backend-go/company/repository.go | 47 ++++--------- cla-backend-go/company/service.go | 13 ++-- .../projects_cla_groups/repository.go | 2 +- cla-backend-go/swagger/cla.v2.yaml | 21 ++++-- cla-backend-go/v2/company/service.go | 67 +++++++++++++------ 6 files changed, 95 insertions(+), 75 deletions(-) diff --git a/cla-backend-go/company/models.go b/cla-backend-go/company/models.go index 9518086d9..30e9a3e2a 100644 --- a/cla-backend-go/company/models.go +++ b/cla-backend-go/company/models.go @@ -90,11 +90,13 @@ func (dbCompanyModel *DBModel) toModel() (*models.Company, error) { } // dbModelsToResponseModels is a helper routine to convert the (internal) database model to a (public) swagger model -func dbModelsToResponseModels(ctx context.Context, dbModels []DBModel) ([]*models.Company, error) { +func dbModelsToResponseModels(ctx context.Context, dbModels []DBModel, includeChildCompanies bool) ([]*models.Company, error) { f := logrus.Fields{ - "functionName": "company.models.dbModelsToResponseModels", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "functionName": "company.models.dbModelsToResponseModels", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "includeChildCompanies": includeChildCompanies, } + var companyModels []*models.Company var err error for _, dbModel := range dbModels { @@ -103,8 +105,16 @@ func dbModelsToResponseModels(ctx context.Context, dbModels []DBModel) ([]*model log.WithFields(f).WithError(conversionErr).Warn("unable to convert db model to company model") err = conversionErr } else { - log.WithFields(f).Debugf("Converted %+v to %+v", dbModel, respModel) - companyModels = append(companyModels, respModel) + // log.WithFields(f).Debugf("Converted %+v to %+v", dbModel, respModel) + if includeChildCompanies { + companyModels = append(companyModels, respModel) + } else { + // only include if company is not a signing entity name with different name + if respModel.SigningEntityName == "" || respModel.CompanyName == respModel.SigningEntityName { + companyModels = append(companyModels, respModel) + break // no need to continue + } + } } } diff --git a/cla-backend-go/company/repository.go b/cla-backend-go/company/repository.go index 9dae3e5a7..3eee0852d 100644 --- a/cla-backend-go/company/repository.go +++ b/cla-backend-go/company/repository.go @@ -33,7 +33,7 @@ type IRepository interface { //nolint GetCompanies(ctx context.Context) (*models.Companies, error) GetCompany(ctx context.Context, companyID string) (*models.Company, error) GetCompanyByExternalID(ctx context.Context, companySFID string) (*models.Company, error) - GetCompaniesByExternalID(ctx context.Context, companySFID string) ([]*models.Company, error) + GetCompaniesByExternalID(ctx context.Context, companySFID string, includeChildCompanies bool) ([]*models.Company, error) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) SearchCompanyByName(ctx context.Context, companyName string, nextKey string) (*models.Companies, error) @@ -157,17 +157,14 @@ func (repo repository) GetCompanyByExternalID(ctx context.Context, companySFID s "companySFID": companySFID, } - // Historically, we would only have zero or one companySF record in the DB. In v2 we introduced the Signing Entity - // Name concept where we would create a new EasyCLA record in the DB for each signing entity name - this allowed CLA - // Managers/Designee to have separate signature records pointing to separate company records. As a result, these - // new companies would also be attached to the same SF parent company results in this API response returning zero or - // more records. To make this backwards compatible for v1, we will still honor this API call and return the company - // record where the entity name is either missing or the same as the company name. - companyRecords, err := repo.GetCompaniesByExternalID(ctx, companySFID) + const includeChildCompanies = false // Include child/other signing entity name records? + companyRecords, err := repo.GetCompaniesByExternalID(ctx, companySFID, includeChildCompanies) if err != nil { log.WithFields(f).WithError(err).Warn("unable to unmarshall response from the database") return nil, err } + log.WithFields(f).Debugf("loaded %d records", len(companyRecords)) + if len(companyRecords) == 0 { log.WithFields(f).Debug("no records found") return nil, &utils.CompanyNotFound{ @@ -176,36 +173,18 @@ func (repo repository) GetCompanyByExternalID(ctx context.Context, companySFID s } } - log.WithFields(f).Debugf("loaded %d records", len(companyRecords)) - // For debug when problems occur - f["companyName"] = companyRecords[0].CompanyName - var signingEntityNames []string - - // To support backward compatibility, search for the case where the signing entity name is empty or where the - // signing entity name matches the company name - for _, companyModel := range companyRecords { - // Save in case we can't find it - we'll show on the output - signingEntityNames = append(signingEntityNames, companyModel.SigningEntityName) - // If this is our record... - if companyModel.SigningEntityName == "" || companyModel.SigningEntityName == companyModel.CompanyName { - return companyModel, nil - } - } - f["signingEntityNames"] = strings.Join(signingEntityNames, ";") - log.WithFields(f).Warning("unable to match company name with existing signing entity names") - return nil, &utils.CompanyNotFound{ - Message: "company record not found - unable to match company name with existing signing entity names", - CompanyID: companySFID, - } + return companyRecords[0], nil } // GetCompaniesByExternalID returns a list of companies based on the company external ID. A company will have more than one if/when the SF record has multiple entity names - for which we create separate EasyCLA company records -func (repo repository) GetCompaniesByExternalID(ctx context.Context, companySFID string) ([]*models.Company, error) { +func (repo repository) GetCompaniesByExternalID(ctx context.Context, companySFID string, includeChildCompanies bool) ([]*models.Company, error) { f := logrus.Fields{ - "functionName": "company.repository.GetCompaniesByExternalID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companySFID": companySFID, + "functionName": "company.repository.GetCompaniesByExternalID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companySFID": companySFID, + "includeChildCompanies": includeChildCompanies, } + condition := expression.Key("company_external_id").Equal(expression.Value(companySFID)) builder := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildCompanyProjection()) // Use the nice builder to create the expression @@ -248,7 +227,7 @@ func (repo repository) GetCompaniesByExternalID(ctx context.Context, companySFID } log.WithFields(f).Debug("converting database records to a response model...") - return dbModelsToResponseModels(ctx, dbCompanyModels) + return dbModelsToResponseModels(ctx, dbCompanyModels, includeChildCompanies) } // GetCompanyBySigningEntityName search the company by signing entity name diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index cc48c7808..68c5b154b 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -39,7 +39,7 @@ type IService interface { // nolint GetCompanies(ctx context.Context) (*models.Companies, error) GetCompany(ctx context.Context, companyID string) (*models.Company, error) GetCompanyByExternalID(ctx context.Context, companySFID string) (*models.Company, error) - GetCompaniesByExternalID(ctx context.Context, companySFID string) ([]*models.Company, error) + GetCompaniesByExternalID(ctx context.Context, companySFID string, includeChildCompanies bool) ([]*models.Company, error) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName, companySFID string) (*models.Company, error) SearchCompanyByName(ctx context.Context, companyName string, nextKey string) (*models.Companies, error) GetCompaniesByUserManager(ctx context.Context, userID string) (*models.Companies, error) @@ -644,15 +644,16 @@ func (s service) GetCompanyByExternalID(ctx context.Context, companySFID string) return nil, err } -func (s service) GetCompaniesByExternalID(ctx context.Context, companySFID string) ([]*models.Company, error) { +func (s service) GetCompaniesByExternalID(ctx context.Context, companySFID string, includeChildCompanies bool) ([]*models.Company, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompaniesByExternalID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companySFID": companySFID, + "functionName": "company.service.GetCompaniesByExternalID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companySFID": companySFID, + "includeChildCompanies": includeChildCompanies, } log.WithFields(f).Debug("Searching companies by external ID...") - comp, err := s.repo.GetCompaniesByExternalID(ctx, companySFID) + comp, err := s.repo.GetCompaniesByExternalID(ctx, companySFID, includeChildCompanies) if err != nil { log.WithFields(f).WithError(err).Warn("unable to locate matching records by companySFID") return nil, err diff --git a/cla-backend-go/projects_cla_groups/repository.go b/cla-backend-go/projects_cla_groups/repository.go index aa77848bd..1edb53a01 100644 --- a/cla-backend-go/projects_cla_groups/repository.go +++ b/cla-backend-go/projects_cla_groups/repository.go @@ -96,7 +96,7 @@ func (repo *repo) queryClaGroupsProjects(keyCondition expression.KeyConditionBui var projectClaGroups []*ProjectClaGroup for { - log.WithFields(f).Debugf("running query using input: %+v", queryInput) + // log.WithFields(f).Debugf("running query using input: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { log.WithFields(f).Warnf("error retrieving project cla-groups, error: %v", errQuery) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index b48b3e196..abbad12d2 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4679,15 +4679,19 @@ definitions: x-omitempty: false example: "2019-07-18T11:38:13.144674+0000" project_id: - type: string - description: The CLA Group/Project ID + title: CLA Group ID + description: The CLA Group/Project ID - here for backwards compatiablity + $ref: './common/properties/internal-id.yaml' + x-omitempty: false + cla_group_id: + title: CLA Group ID + description: The CLA Group ID + $ref: './common/properties/internal-id.yaml' x-omitempty: false - example: "e1e30240-a722-4c82-a648-121681d959c7" project_sfid: - type: string + $ref: './common/properties/external-id.yaml' description: The project SalesForce ID x-omitempty: false - example: "a2g17000000hyxNAAA" project_name: type: string description: The project name @@ -4708,11 +4712,14 @@ definitions: example: "John Doe" x-omitempty: false signature_id: - type: string - example: "55ec4162-9e41-47da-a643-f81666953a51" + $ref: './common/properties/external-id.yaml' + title: Signature ID + description: The internal signature ID x-omitempty: false project_logo: type: string + title: Project Logo + description: the project logo example: "http://wwwlinuxfoundation.org/logo.gif" x-omitempty: false cla_group_name: diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 33045bc7a..f41d43d12 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -840,11 +840,13 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, } claGroupsChannel := make(chan *CLAGroupsResult, 1) - // Separate go routine + log.WithFields(f).Debug("scheduling query for companies...") + const includeChildCompanies = false // Include child/other signing entity name records? + // Separate go routine - we will get 0 or more companies (Company + separate companies for each signing entity names) go func(ctx context.Context, companySFID string, companyID *string) { // Attempt to locate the companyModel model in our database log.WithFields(f).Debug("locating companyModel by SF ID") - companies, companyErr := s.companyRepo.GetCompaniesByExternalID(ctx, companySFID) + companies, companyErr := s.companyRepo.GetCompaniesByExternalID(ctx, companySFID, includeChildCompanies) if companyErr != nil { // If we were unable to find the companyModel/org in our local database, try to auto-create based // on the existing SF record @@ -901,6 +903,7 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, }(ctx, companySFID, companyID) // Separate go routine + log.WithFields(f).Debug("scheduling query for CLA Groups for this project...") go func(ctx context.Context, projectSFID string) { claGroups, err := s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) if err != nil { @@ -918,15 +921,19 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, }(ctx, projectSFID) // Grab the results + log.WithFields(f).Debug("waiting for companies query to finish...") companyResponse := <-companiesChannel if companyResponse.CompanyError != nil { return nil, companyResponse.CompanyError } + log.WithFields(f).Debugf("companies query finished - %d companies found", len(companyResponse.Companies)) + log.WithFields(f).Debug("waiting for CLA Groups query to finish...") claGroupResponse := <-claGroupsChannel if claGroupResponse.CLAGroupError != nil { return nil, claGroupResponse.CLAGroupError } + log.WithFields(f).Debugf("cla groups query finished - %d CLA Groups found", len(claGroupResponse.CLAGroups)) // Setup another channel to fetch all these results type CompanyProjectCLAList struct { @@ -937,6 +944,7 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, // For each company... for _, companyModel := range companyResponse.Companies { + log.WithFields(f).Debugf("scheduling query for company project CLAs for company: %s...", companyModel.CompanyName) go func(ctx context.Context, companyModel *v1Models.Company, projectSFID string, claGroups map[string]*claGroupModel) { // Fetch the active CLA list for this project + company @@ -953,7 +961,6 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, inactiveCLAGroups := claGroups for _, activeCLA := range activeCLAList.List { // remove cla groups for which we have signed cla - log.WithFields(f).Debugf("removing CLA Groups with active CLA, CLA Group: %+v, error: %+v", activeCLA, err) delete(inactiveCLAGroups, activeCLA.ProjectID) } @@ -986,18 +993,10 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, CompanyProjectCLA: companyProjectCLA, } }(ctx, companyModel, projectSFID, claGroupResponse.CLAGroups) - - /* - // refresh clagroups for next companyModel instance - claGroups, err = s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) - if err != nil { - log.WithFields(f).Warnf("problem fetching CLA Groups under project or foundation, error: %+v", err) - return nil, err - } - */ } // Grab the results + log.WithFields(f).Debug("waiting for company project CLA results to finish...") var companyProjectClaList []*models.CompanyProjectCla for i := 0; i < len(companyResponse.Companies); i++ { companyProjectCLAResponse := <-companyProjectCLAChannel @@ -1008,6 +1007,7 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, // No error, save the value companyProjectClaList = append(companyProjectClaList, companyProjectCLAResponse.CompanyProjectCLA) } + log.WithFields(f).Debugf("company project cla groups query finished - %d responses", len(companyResponse.Companies)) return &models.CompanyProjectClaList{ List: companyProjectClaList, @@ -1094,42 +1094,57 @@ func v2ProjectToMap(projectDetails *v2ProjectServiceModels.ProjectOutputDetailed return epmap, nil } -func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, id string) (map[string]*claGroupModel, error) { +func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, projectSFID string) (map[string]*claGroupModel, error) { + f := logrus.Fields{ + "functionName": "company.service.getCLAGroupsUnderProjectOrFoundation", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + } + result := make(map[string]*claGroupModel) psc := v2ProjectService.GetClient() - projectDetails, err := psc.GetProject(id) + log.WithFields(f).Debug("loading project SFID...") + projectDetails, err := psc.GetProject(projectSFID) if err != nil { return nil, err } + log.WithFields(f).Debug("loaded project SFID") + var allProjectMapping []*projects_cla_groups.ProjectClaGroup if projectDetails.ProjectType == FoundationType { // get all projects for all cla group under foundation - allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForFoundation(id) + allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForFoundation(projectSFID) if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to get project IDs for foundation SFID: %s", projectSFID) return nil, err } } else { // get cla group id from project - projectMapping, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(id) + projectMapping, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) if perr != nil { + log.WithFields(f).WithError(perr).Warnf("unable to get CLA group IDs for project SFID: %s", projectSFID) return nil, err } // get all projects for that cla group allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForClaGroup(projectMapping.ClaGroupID) if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to get project IDs for CLA Group: %s", projectMapping.ClaGroupID) return nil, err } if len(allProjectMapping) > 1 { // reload data in projectDetails for all projects of foundation projectDetails, err = psc.GetProject(projectDetails.Foundation.ID) if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to load project from project service using SFID: %s", projectDetails.Foundation.ID) return nil, err } } } + // v2ProjectMap will contains projectSFID -> salesforce details of that project v2ProjectMap, err := v2ProjectToMap(projectDetails) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to convert project to project map") return nil, err } // for all cla-groups create claGroupModel @@ -1156,7 +1171,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, id s // get cla-group info cginfo, err := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil || cginfo == nil { - log.Warnf("Unable to get details of cla_group: %s", claGroupID) + log.WithFields(f).Warnf("Unable to get details of cla_group: %s", claGroupID) return } claGroup.ClaGroupName = cginfo.ProjectName @@ -1174,7 +1189,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, id s for _, spid := range claGroup.SubProjectIDs { subProject, ok := v2ProjectMap[spid] if !ok { - log.Warnf("Unable to fill details for cla_group: %s with project details of %s", claGroupID, spid) + log.WithFields(f).Warnf("Unable to fill details for cla_group: %s with project details of %s", claGroupID, spid) return } claGroup.SubProjects = append(claGroup.SubProjects, subProject.Name) @@ -1182,7 +1197,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, id s } project, ok := v2ProjectMap[pid] if !ok { - log.Warnf("Unable to fill details for cla_group: %s with project details of %s", claGroupID, claGroup.ProjectSFID) + log.WithFields(f).Warnf("Unable to fill details for cla_group: %s with project details of %s", claGroupID, claGroup.ProjectSFID) return } claGroup.ProjectLogo = project.ProjectLogo @@ -1191,7 +1206,11 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, id s claGroup.ProjectSFID = pid }(id, cg) } + + log.WithFields(f).Debug("waiting for queries to finish...") wg.Wait() + log.WithFields(f).Debug("queries finished") + return result, nil } @@ -1287,7 +1306,7 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 defer wg.Done() cg, ok := claGroups[sig.ProjectID] if !ok { - log.Warn("unable to get project details") + log.WithFields(f).Warn("unable to get project details") return } @@ -1308,6 +1327,7 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 } } } + sort.Strings(acl) // fill details from dynamodb activeCla.CompanyName = v1CompanyModel.CompanyName @@ -1316,7 +1336,8 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 } else { activeCla.SigningEntityName = v1CompanyModel.SigningEntityName } - activeCla.ProjectID = sig.ProjectID + activeCla.ProjectID = sig.ProjectID // for backwards compatibility + activeCla.ClaGroupID = sig.ProjectID if sig.SignedOn == "" { activeCla.SignedOn = sig.SignatureCreated } else { @@ -1336,7 +1357,9 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 activeCla.ProjectSfid = cg.ProjectSFID activeCla.ProjectType = cg.ProjectType activeCla.ProjectLogo = cg.ProjectLogo + sort.Strings(cg.SubProjects) activeCla.SubProjects = cg.SubProjects + var signatoryName string var cwg sync.WaitGroup cwg.Add(2) @@ -1347,7 +1370,7 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 defer cwg.Done() cclaURL, err = utils.GetDownloadLink(utils.SignedCLAFilename(sig.ProjectID, sig.SignatureType, sig.SignatureReferenceID, sig.SignatureID)) if err != nil { - log.Error("fillActiveCLA : unable to get ccla s3 link", err) + log.WithFields(f).WithError(err).Warn("fillActiveCLA : unable to get ccla s3 link", err) return } }() From 8b16f7f79de36a5a3a70618c3cfb16065a152300 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 19:14:16 -0500 Subject: [PATCH 0052/1276] Resolved #2569 v2 Link Disabled for Prod (#2588) --- .circleci/config.yml | 2 +- cla-landing-page/angular.json | 6 ++++++ cla-landing-page/package.json | 8 +++++--- .../cla-console-section.component.html | 4 ++-- .../cla-console-section/cla-console-section.component.ts | 5 ++++- cla-landing-page/src/environments/environment.dev.ts | 1 + cla-landing-page/src/environments/environment.prod.ts | 1 + cla-landing-page/src/environments/environment.staging.ts | 1 + cla-landing-page/src/environments/environment.ts | 1 + 9 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9dd32a7e2..d70015f06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -814,7 +814,7 @@ jobs: echo "Running pre-fetch config: 'yarn prebuild:${STAGE}'..." yarn prebuild:${STAGE} echo "Running build..." - yarn build + yarn build:${STAGE} echo "Running deploy in folder: `pwd`" SLS_DEBUG=* ../node_modules/serverless/bin/serverless.js deploy -s ${STAGE} -r ${AWS_REGION} --verbose diff --git a/cla-landing-page/angular.json b/cla-landing-page/angular.json index 78fa38fec..720b01320 100644 --- a/cla-landing-page/angular.json +++ b/cla-landing-page/angular.json @@ -127,6 +127,12 @@ "configurations": { "production": { "browserTarget": "landing-page:build:production" + }, + "staging": { + "browserTarget": "landing-page:build:staging" + }, + "dev": { + "browserTarget": "landing-page:build:dev" } } }, diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 6df8617e2..5d429eee7 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -3,9 +3,10 @@ "version": "0.0.0", "scripts": { "ng": "./node_modules/@angular/cli/bin/ng", - "serve": "./node_modules/@angular/cli/bin/ng serve", - "build": "./node_modules/@angular/cli/bin/ng build", - "serve:local": "ng serve --port 8100", + "serve": "ng serve --port 8100 --configuration=dev", + "serve:dev:local": "ng serve --port 8100 --configuration=dev", + "serve:staging:local": "ng serve --port 8100 --configuration=staging", + "serve:prod:local": "ng serve --port 8100 --configuration=production", "test": "yarn test:unit", "test:unit": "ng test --watch=false --progress=false --browsers=ChromeHeadless", "lint": "./node_modules/@angular/cli/bin/ng lint", @@ -13,6 +14,7 @@ "sls": "./node_modules/serverless/bin/serverless.js", "eslint": "./node_modules/.bin/eslint \"./src/**/*.ts\"", "eslint-fix": "./node_modules/.bin/eslint \"./src/**/*.ts\" --fix", + "build": "yarn prebuild:dev && ./node_modules/@angular/cli/bin/ng build --prod --configuration=dev", "build:dev": "yarn prebuild:dev && ./node_modules/@angular/cli/bin/ng build --prod --configuration=dev", "prebuild:dev": "STAGE_ENV=dev node ./src/app/config/scripts/prefetch-ssm.js", "deploy:cloudfront:dev": "SLS_DEBUG=* yarn sls deploy --stage='dev' --cloudfront=true --verbose", diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index d3375a6f5..03bb868a3 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -80,8 +80,8 @@
EasyCLA Version - 2
+ [ngClass]="{'active':selectedVersion==='2','disabled':environment.environment === 'prod'}"> + EasyCLA Version 2
diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index b80a6f6de..c7cbfe7d6 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -1,13 +1,15 @@ // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT -import { Component, Input, isDevMode, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { AppSettings } from 'src/app/config/app-settings'; import { StorageService } from 'src/app/core/services/storage.service'; import { EnvConfig } from 'src/app/config/cla-env-utils'; import { ActivatedRoute } from '@angular/router'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { LandingPageService } from 'src/app/service/landing-page.service'; +import { environment } from 'src/environments/environment'; + declare let process: any; @Component({ selector: 'app-cla-console-section', @@ -24,6 +26,7 @@ export class ClaConsoleSectionComponent implements OnInit { error: string; consoleType: string; env: string; + public environment = environment; constructor( private storageService: StorageService, diff --git a/cla-landing-page/src/environments/environment.dev.ts b/cla-landing-page/src/environments/environment.dev.ts index a9228a3c6..440dbbfe1 100644 --- a/cla-landing-page/src/environments/environment.dev.ts +++ b/cla-landing-page/src/environments/environment.dev.ts @@ -2,5 +2,6 @@ // SPDX-License-Identifier: MIT export const environment = { + environment: 'dev', production: false }; diff --git a/cla-landing-page/src/environments/environment.prod.ts b/cla-landing-page/src/environments/environment.prod.ts index ba215c941..c93e059c2 100644 --- a/cla-landing-page/src/environments/environment.prod.ts +++ b/cla-landing-page/src/environments/environment.prod.ts @@ -2,5 +2,6 @@ // SPDX-License-Identifier: MIT export const environment = { + environment: 'prod', production: true }; diff --git a/cla-landing-page/src/environments/environment.staging.ts b/cla-landing-page/src/environments/environment.staging.ts index ba215c941..7ca2fe6b6 100644 --- a/cla-landing-page/src/environments/environment.staging.ts +++ b/cla-landing-page/src/environments/environment.staging.ts @@ -2,5 +2,6 @@ // SPDX-License-Identifier: MIT export const environment = { + environment: 'staging', production: true }; diff --git a/cla-landing-page/src/environments/environment.ts b/cla-landing-page/src/environments/environment.ts index cb5a2f67a..27b042ffb 100644 --- a/cla-landing-page/src/environments/environment.ts +++ b/cla-landing-page/src/environments/environment.ts @@ -6,6 +6,7 @@ // The list of file replacements can be found in `angular.json`. export const environment = { + environment: 'dev', production: false }; /* From 59373320f9c32a8df66648b4f8ac19d695efd150 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 20:04:13 -0500 Subject: [PATCH 0053/1276] Rolled Back Check Prepare Signatures Signing Entity Name Logic (#2589) Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 232 +++++++++++----------- 1 file changed, 120 insertions(+), 112 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index d9749017a..c4a12dcdf 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -422,124 +422,132 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic # Save our message msg = (f'{fn} - project {project.get_project_name()} and ' f'company {company.get_company_name()} does not have CCLA for: {request_info}') + cla.log.debug(msg) - # Ok - long story here, we could have the tricky situation where now that we've added a concept of Signing - # Entity Names we have, basically, a set of 'child' companies all under a common external_id (SFID). This - # would have been so much simpler if SF supported Parent/Child company relationships to model things like - # Subsidiary and Patten holding companies. - # - # Scenario: - # - # Deal Company (SFID: 123, CompanyID: AAA) - # Deal Company Subsidiary 1 - (SFID: 123, CompanyID: BBB) - # Deal Company Subsidiary 2 - (SFID: 123, CompanyID: CCC) - SIGNED! - # Deal Company Subsidiary 3 - (SFID: 123, CompanyID: DDD) - # Deal Company Subsidiary 4 - (SFID: 123, CompanyID: EEE) - # - # Now - the check-prepare-employee signature request could have come from any of the above companies with - # different a company_id - the contributor may have selected the correct option (CCC), the one that was - # signed and executed by a Signatory...or maybe none have been signed...or perhaps another one was signed - # such as companyID BBB. - # - # Originally, we designed the system to keep track of all these sub-companies separately - different CLA - # managers, different approval lists, etc. + return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + 'company_id': actual_company_id, + 'company_name': company.get_company_name(), + 'signing_entity_name': company.get_signing_entity_name(), + 'company_external_id': company.get_company_external_id(), + } + } + # # Ok - long story here, we could have the tricky situation where now that we've added a concept of Signing + # # Entity Names we have, basically, a set of 'child' companies all under a common external_id (SFID). This + # # would have been so much simpler if SF supported Parent/Child company relationships to model things like + # # Subsidiary and Patten holding companies. + # # + # # Scenario: + # # + # # Deal Company (SFID: 123, CompanyID: AAA) + # # Deal Company Subsidiary 1 - (SFID: 123, CompanyID: BBB) + # # Deal Company Subsidiary 2 - (SFID: 123, CompanyID: CCC) - SIGNED! + # # Deal Company Subsidiary 3 - (SFID: 123, CompanyID: DDD) + # # Deal Company Subsidiary 4 - (SFID: 123, CompanyID: EEE) + # # + # # Now - the check-prepare-employee signature request could have come from any of the above companies with + # # different a company_id - the contributor may have selected the correct option (CCC), the one that was + # # signed and executed by a Signatory...or maybe none have been signed...or perhaps another one was signed + # # such as companyID BBB. + # # + # # Originally, we designed the system to keep track of all these sub-companies separately - different CLA + # # managers, different approval lists, etc. + # # + # # Later, the stakeholders wanted to group these all together as one but keep track of the signing entity + # # name for each project | company. They wanted to allow the users to select one for each (project | + # # organization) pair. + # # + # # So, we could have CLA signatories/managers wanting: + # # + # # - Project OpenCue + Deal Company Subsidiary 2 + # # - Project OpenVDB + Deal Company Subsidiary 4 + # # - Project OpenTelemetry + Deal Company + # # + # # As a result, we need to query the entire company family under the same external_id for a signed CCLA. + # # Currently, we only allow 1 of these to be signed for each Project | Company pair. Later, we may change + # # this behavior (it's been debated). + # # + # # Let's see if they signed the CCLA for another of the Company/Signed Entity Names for this + # # project - if so, let's return that one, if not, return the error # - # Later, the stakeholders wanted to group these all together as one but keep track of the signing entity - # name for each project | company. They wanted to allow the users to select one for each (project | - # organization) pair. + # # First, grab the current company's external ID/SFID + # company_external_id = company.get_company_external_id() + # # if missing, not much we can do... + # if company_external_id is None: + # cla.log.warning(f'{fn} - project {project.get_project_name()} and ' + # f'company {company.get_company_name()} - company missing external id - ' + # f'{request_info}') + # cla.log.warning(msg) + # return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + # 'company_id': actual_company_id, + # 'company_name': company.get_company_name(), + # 'signing_entity_name': company.get_signing_entity_name(), + # 'company_external_id': company.get_company_external_id(), + # } + # } # - # So, we could have CLA signatories/managers wanting: + # # Lookup the other companies by external id...will have 1 or more (current record plus possibly others)... + # company_list = company.get_company_by_external_id(company_external_id) + # # This shouldn't happen, let's trap for it anyway + # if not company_list: + # cla.log.warning(f'{fn} - project {project.get_project_name()} and ' + # f'company {company.get_company_name()} - unable to lookup companies by external id: ' + # f'{company_external_id} - {request_info}') + # cla.log.warning(msg) + # return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + # 'company_id': actual_company_id, + # 'company_name': company.get_company_name(), + # 'signing_entity_name': company.get_signing_entity_name(), + # 'company_external_id': company.get_company_external_id(), + # } + # } # - # - Project OpenCue + Deal Company Subsidiary 2 - # - Project OpenVDB + Deal Company Subsidiary 4 - # - Project OpenTelemetry + Deal Company + # # As we loop, let's use a flag to keep track if we find a CCLA + # found_ccla = False + # for other_company in company_list: + # cla.log.debug(f'{fn} - loading CCLA signatures by cla group: {project.get_project_name()} ' + # f'and company id: {other_company.get_company_id()}...') + # ccla_signatures = Signature().get_ccla_signatures_by_company_project( + # company_id=other_company.get_company_id(), + # project_id=project_id + # ) # - # As a result, we need to query the entire company family under the same external_id for a signed CCLA. - # Currently, we only allow 1 of these to be signed for each Project | Company pair. Later, we may change - # this behavior (it's been debated). + # # Do we have a signed CCLA for this project|company ? If so, we found it - use it! Should NOT have + # # more than one of the companies with Signed CCLAs + # if len(ccla_signatures) > 0: + # found_ccla = True + # # Need to load the correct company record + # try: + # # Reset the actual company id value since we found a CCLA under a related signing entity name + # # company + # actual_company_id = ccla_signatures[0].get_signature_reference_id() + # # Reset the request_info string with the updated company_id, will use it for debug/warning below + # request_info = f'project: {project_id}, company: {actual_company_id}, user: {user_id}' + # cla.log.debug(f'{fn} - loading correct signed CCLA company by id: ' + # f'{ccla_signatures[0].get_signature_reference_id()} ' + # f'with signed entity name: {ccla_signatures[0].get_signing_entity_name()} ...') + # company.load(ccla_signatures[0].get_signature_reference_id()) + # cla.log.debug(f'{fn} - loaded company {company.get_company_name()} ' + # f'with signing entity name: {company.get_signing_entity_name()} ' + # f'for {request_info}.') + # except DoesNotExist: + # cla.log.warning(f'{fn} - company does NOT exist ' + # f'using company_id: {ccla_signatures[0].get_signature_reference_id()} ' + # f'for: {request_info}') + # return {'errors': {'company_id': f'Company ({ccla_signatures[0].get_signature_reference_id()}) ' + # 'does not exist.'}} + # break # - # Let's see if they signed the CCLA for another of the Company/Signed Entity Names for this - # project - if so, let's return that one, if not, return the error - - # First, grab the current company's external ID/SFID - company_external_id = company.get_company_external_id() - # if missing, not much we can do... - if company_external_id is None: - cla.log.warning(f'{fn} - project {project.get_project_name()} and ' - f'company {company.get_company_name()} - company missing external id - ' - f'{request_info}') - cla.log.warning(msg) - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', - 'company_id': actual_company_id, - 'company_name': company.get_company_name(), - 'signing_entity_name': company.get_signing_entity_name(), - 'company_external_id': company.get_company_external_id(), - } - } - - # Lookup the other companies by external id...will have 1 or more (current record plus possibly others)... - company_list = company.get_company_by_external_id(company_external_id) - # This shouldn't happen, let's trap for it anyway - if not company_list: - cla.log.warning(f'{fn} - project {project.get_project_name()} and ' - f'company {company.get_company_name()} - unable to lookup companies by external id: ' - f'{company_external_id} - {request_info}') - cla.log.warning(msg) - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', - 'company_id': actual_company_id, - 'company_name': company.get_company_name(), - 'signing_entity_name': company.get_signing_entity_name(), - 'company_external_id': company.get_company_external_id(), - } - } - - # As we loop, let's use a flag to keep track if we find a CCLA - found_ccla = False - for other_company in company_list: - cla.log.debug(f'{fn} - loading CCLA signatures by cla group: {project.get_project_name()} ' - f'and company id: {other_company.get_company_id()}...') - ccla_signatures = Signature().get_ccla_signatures_by_company_project( - company_id=other_company.get_company_id(), - project_id=project_id - ) - - # Do we have a signed CCLA for this project|company ? If so, we found it - use it! Should NOT have - # more than one of the companies with Signed CCLAs - if len(ccla_signatures) > 0: - found_ccla = True - # Need to load the correct company record - try: - # Reset the actual company id value since we found a CCLA under a related signing entity name - # company - actual_company_id = ccla_signatures[0].get_signature_reference_id() - # Reset the request_info string with the updated company_id, will use it for debug/warning below - request_info = f'project: {project_id}, company: {actual_company_id}, user: {user_id}' - cla.log.debug(f'{fn} - loading correct signed CCLA company by id: ' - f'{ccla_signatures[0].get_signature_reference_id()} ' - f'with signed entity name: {ccla_signatures[0].get_signing_entity_name()} ...') - company.load(ccla_signatures[0].get_signature_reference_id()) - cla.log.debug(f'{fn} - loaded company {company.get_company_name()} ' - f'with signing entity name: {company.get_signing_entity_name()} ' - f'for {request_info}.') - except DoesNotExist: - cla.log.warning(f'{fn} - company does NOT exist ' - f'using company_id: {ccla_signatures[0].get_signature_reference_id()} ' - f'for: {request_info}') - return {'errors': {'company_id': f'Company ({ccla_signatures[0].get_signature_reference_id()}) ' - 'does not exist.'}} - break - - # if we didn't fine a signed CCLA under any of the other companies... - if not found_ccla: - # Give up - cla.log.warning(msg) - return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', - 'company_id': actual_company_id, - 'company_name': company.get_company_name(), - 'signing_entity_name': company.get_signing_entity_name(), - 'company_external_id': company.get_company_external_id(), - } - } + # # if we didn't fine a signed CCLA under any of the other companies... + # if not found_ccla: + # # Give up + # cla.log.warning(msg) + # return {'errors': {'missing_ccla': 'Company does not have CCLA with this project.', + # 'company_id': actual_company_id, + # 'company_name': company.get_company_name(), + # 'signing_entity_name': company.get_signing_entity_name(), + # 'company_external_id': company.get_company_external_id(), + # } + # } # Add a note in the log if we have more than 1 signed and approved CCLA signature if len(ccla_signatures) > 1: From 1fb9ede7d9e4c70fe5deb9b83e8f7686c4558be0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 20:38:28 -0500 Subject: [PATCH 0054/1276] Resolved Inactive Landing Page Links (#2590) Signed-off-by: David Deal --- .../cla-console-section/cla-console-section.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index c7cbfe7d6..fa4a55b26 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -48,7 +48,6 @@ export class ClaConsoleSectionComponent implements OnInit { } ngOnInit() { - this.env = process.env.NODE_ENV; this.version = this.router.snapshot.queryParamMap.get('version'); const element: any = document.getElementById('lfx-header'); let projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK] + '#/login'; @@ -106,7 +105,7 @@ export class ClaConsoleSectionComponent implements OnInit { } onClickVersion(version) { - this.selectedVersion = (this.env === 'production') ? '' : version; + this.selectedVersion = (environment.environment === 'prod') ? '' : version; } onClickVersionProceed() { From a3ebcda08994376009d26bd9e5432e24689e637f Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 20:41:48 -0500 Subject: [PATCH 0055/1276] Added Snyk Config for Golang and Python (#2591) Signed-off-by: David Deal --- .github/workflows/snyk.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index c308e0c43..d8003e477 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -5,9 +5,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - name: Run Snyk to check for vulnerabilities + - name: Run Snyk to check for vulnerabilities (Node) uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: command: monitor + - name: Run Snyk to check for vulnerabilities (Golang) + uses: snyk/actions/golang@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + command: monitor + - name: Run Snyk to check for vulnerabilities (Python) + uses: snyk/actions/python@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + command: monitor From 184cca89c72165231011e1950dc90388efa06b93 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Feb 2021 19:08:47 -0800 Subject: [PATCH 0056/1276] Resolved PROD Link Issue Signed-off-by: David Deal --- .../cla-console-section.component.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index fa4a55b26..b7d54232b 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -105,7 +105,15 @@ export class ClaConsoleSectionComponent implements OnInit { } onClickVersion(version) { - this.selectedVersion = (environment.environment === 'prod') ? '' : version; + if (version == '1') { + this.selectedVersion = version; + } else if (version == '2') { + if (environment.environment !== 'prod') { + this.selectedVersion = version; + } else { + this.selectedVersion = ''; + } + } } onClickVersionProceed() { From 18c6245f861747e1d92914c7dfc511af503836b0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Sun, 7 Feb 2021 17:49:54 -0500 Subject: [PATCH 0057/1276] Added Upload Snyk Results to GitHub Code Scanning (#2592) Signed-off-by: David Deal --- .github/workflows/snyk.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index d8003e477..618f845af 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -7,19 +7,26 @@ jobs: - uses: actions/checkout@master - name: Run Snyk to check for vulnerabilities (Node) uses: snyk/actions/node@master + continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: command: monitor - name: Run Snyk to check for vulnerabilities (Golang) uses: snyk/actions/golang@master + continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: command: monitor - name: Run Snyk to check for vulnerabilities (Python) uses: snyk/actions/python@master + continue-on-error: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: command: monitor + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: snyk.sarif From d5fdc53d92b92f62b324d8b5f3f907770a6fc245 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 8 Feb 2021 20:40:16 -0500 Subject: [PATCH 0058/1276] Added Date Created/Modified/Note columns for Project CLA Group Table API (#2595) Signed-off-by: David Deal --- cla-backend-go/projects_cla_groups/models.go | 3 ++ .../projects_cla_groups/repository.go | 16 +++++---- cla-backend-go/utils/errors.go | 36 +++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/projects_cla_groups/models.go b/cla-backend-go/projects_cla_groups/models.go index 98100ce47..0b7ccd4e5 100644 --- a/cla-backend-go/projects_cla_groups/models.go +++ b/cla-backend-go/projects_cla_groups/models.go @@ -13,7 +13,10 @@ type ProjectClaGroup struct { FoundationSFID string `dynamodbav:"foundation_sfid" json:"foundation_sfid"` FoundationName string `dynamodbav:"foundation_name" json:"foundation_name"` RepositoriesCount int64 `dynamodbav:"repositories_count" json:"repositories_count"` + Note string `dynamodbav:"version" json:"note"` Version string `dynamodbav:"version" json:"version"` + DateCreated string `dynamodbav:"version" json:"date_created"` + DateModified string `dynamodbav:"version" json:"date_modified"` } // Quick model to grab the bare minimum values diff --git a/cla-backend-go/projects_cla_groups/repository.go b/cla-backend-go/projects_cla_groups/repository.go index 1edb53a01..a6ff2d22a 100644 --- a/cla-backend-go/projects_cla_groups/repository.go +++ b/cla-backend-go/projects_cla_groups/repository.go @@ -73,7 +73,7 @@ func NewRepository(awsSession *session.Session, stage string) Repository { func (repo *repo) queryClaGroupsProjects(keyCondition expression.KeyConditionBuilder, indexName *string) ([]*ProjectClaGroup, error) { f := logrus.Fields{ - "functionName": "queryClaGroupsProjects", + "functionName": "project_cla_groups.repository.queryClaGroupsProjects", "indexName": aws.StringValue(indexName), "keyCondition": fmt.Sprintf("%+v", keyCondition), } @@ -125,7 +125,7 @@ func (repo *repo) queryClaGroupsProjects(keyCondition expression.KeyConditionBui // GetClaGroupIDForProject retrieves the CLA Group ID for the project func (repo *repo) GetClaGroupIDForProject(projectSFID string) (*ProjectClaGroup, error) { f := logrus.Fields{ - "functionName": "GetClaGroupIDForProject", + "functionName": "project_cla_groups.repository.GetClaGroupIDForProject", "tableName": repo.tableName, "projectSFID": projectSFID, } @@ -182,7 +182,7 @@ func (repo *repo) GetProjectsIdsForFoundation(foundationSFID string) ([]*Project } func (repo *repo) GetProjectsIdsForAllFoundation() ([]*ProjectClaGroup, error) { - f := logrus.Fields{"functionName": "GetProjectsIdsForAllFoundation", "tableName": repo.tableName} + f := logrus.Fields{"functionName": "project_cla_groups.repository.GetProjectsIdsForAllFoundation", "tableName": repo.tableName} scanInput := &dynamodb.ScanInput{ TableName: aws.String(repo.tableName), } @@ -212,7 +212,7 @@ func (repo *repo) GetProjectsIdsForAllFoundation() ([]*ProjectClaGroup, error) { // AssociateClaGroupWithProject creates entry in db to track cla_group association with project/foundation func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID string, foundationSFID string) error { f := logrus.Fields{ - "functionName": "AssociateClaGroupWithProject", + "functionName": "project_cla_groups.repository.AssociateClaGroupWithProject", "claGroupID": claGroupID, "projectSFID": projectSFID, "foundationSFID": foundationSFID, @@ -247,6 +247,7 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st claGroupLookupErr, NotDefined) } + _, nowStr := utils.CurrentTime() input := &ProjectClaGroup{ ProjectSFID: projectSFID, ProjectName: projectName, @@ -254,7 +255,10 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st ClaGroupName: claGroupName, FoundationSFID: foundationSFID, FoundationName: foundationName, + Note: fmt.Sprintf("Associate CLA Group with project API request on: %s", nowStr), Version: "v1", + DateCreated: nowStr, + DateModified: nowStr, } av, err := dynamodbattribute.MarshalMap(input) @@ -296,7 +300,7 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st // RemoveProjectAssociatedWithClaGroup removes all associated project with cla_group func (repo *repo) RemoveProjectAssociatedWithClaGroup(claGroupID string, projectSFIDList []string, all bool) error { f := logrus.Fields{ - "functionName": "RemoveProjectAssociatedWithClaGroup", + "functionName": "project_cla_groups.repository.RemoveProjectAssociatedWithClaGroup", "claGroupID": claGroupID, "projectSFIDList": projectSFIDList, "all": all, @@ -392,7 +396,7 @@ func (repo *repo) GetCLAGroup(claGroupID string) (*ProjectClaGroup, error) { // UpdateRepositoriesCount updates the repositories count func (repo *repo) UpdateRepositoriesCount(projectSFID string, diff int64, reset bool) error { f := logrus.Fields{ - "functionName": "UpdateRepositoriesCount", + "functionName": "project_cla_groups.repository.UpdateRepositoriesCount", "projectSFID": projectSFID, "diff": diff, "reset": reset, diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index 5ec837bb5..bbf3b3dc3 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -192,6 +192,42 @@ func (e *CompanyAdminNotFound) Unwrap() error { return e.Err } +// UserNotFound is an error model for users not found errors +type UserNotFound struct { + Message string + UserLFID string + UserName string + UserEmail string + Err error +} + +// Error is an error string function for the CompanyNotFound model +func (e *UserNotFound) Error() string { + msg := "user does not exist " + if e.Message != "" { + msg = e.Message + } + if e.UserLFID != "" { + msg = fmt.Sprintf("%s - user LFID: %s ", msg, e.UserLFID) + } + if e.UserName != "" { + msg = fmt.Sprintf("%s - user name: %s ", msg, e.UserName) + } + if e.UserEmail != "" { + msg = fmt.Sprintf("%s - email: %s ", msg, e.UserEmail) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *UserNotFound) Unwrap() error { + return e.Err +} + // CompanyNotFound is an error model for company not found errors type CompanyNotFound struct { Message string From b29d8048841f50017dab1478d20c18e1bfcb24ed Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 8 Feb 2021 20:45:23 -0500 Subject: [PATCH 0059/1276] Added SignUp User Handler (#2596) Signed-off-by: David Deal --- cla-backend-go/userSubscribeLambda/main.go | 216 +++++++++++++++++---- 1 file changed, 182 insertions(+), 34 deletions(-) diff --git a/cla-backend-go/userSubscribeLambda/main.go b/cla-backend-go/userSubscribeLambda/main.go index d115f380a..f2699e77f 100644 --- a/cla-backend-go/userSubscribeLambda/main.go +++ b/cla-backend-go/userSubscribeLambda/main.go @@ -9,6 +9,9 @@ import ( "os" "runtime" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" + "github.com/LF-Engineering/lfx-models/models/event" usersModels "github.com/LF-Engineering/lfx-models/models/users" "github.com/aws/aws-lambda-go/events" @@ -37,15 +40,18 @@ var ( ) func init() { + f := logrus.Fields{ + "functionName": "userSubscribeLambda.main.init", + } var awsSession = session.Must(session.NewSession(&aws.Config{})) stage := os.Getenv("STAGE") if stage == "" { - log.Fatal("stage not set") + log.WithFields(f).Fatal("stage not set") } - log.Infof("STAGE set to %s\n", stage) + log.WithFields(f).Infof("STAGE set to %s\n", stage) configFile, err := config.LoadConfig("", awsSession, stage) if err != nil { - log.Panicf("Unable to load config - Error: %v", err) + log.WithFields(f).WithError(err).Panicf("Unable to load config - Error: %v", err) } token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) @@ -54,41 +60,128 @@ func init() { // Handler is the user subscribe handler lambda entry function func Handler(ctx context.Context, snsEvent events.SNSEvent) error { + f := logrus.Fields{ + "functionName": "userSubscribeLambda.main.Handler", + } if len(snsEvent.Records) == 0 { - log.Warn("SNS event contained 0 records - ignoring message.") + log.WithFields(f).Warn("SNS event contained 0 records - ignoring message.") return nil } for _, message := range snsEvent.Records { - log.Infof("Processing message id: '%s' for event source '%s'\n", message.SNS.MessageID, message.EventSource) + log.WithFields(f).Infof("Processing message id: '%s' for event source '%s'", message.SNS.MessageID, message.EventSource) - log.Debugf("Unmarshalling message body: '%s'", message.SNS.Message) - - // log.Debugf("Unmarshalling message body: '%s'", message.SNS.Message) + log.WithFields(f).Debugf("Unmarshalling message body: '%s'", message.SNS.Message) var model event.Event err := model.UnmarshalBinary([]byte(message.SNS.Message)) if err != nil { - log.Warnf("Error: %v, JSON unmarshal failed - unable to process message: %s", err, message.SNS.MessageID) + log.WithFields(f).Warnf("Error: %v, JSON unmarshal failed - unable to process message: %s", err, message.SNS.MessageID) return err } + log.WithFields(f).Debugf("Processing model.Type: %s", model.Type) switch model.Type { + case "UserSignedUp": + log.WithFields(f).Debugf("Detected model.Type: %s - processing...", model.Type) + Create(model) case "UserUpdatedProfile": - Write(model) + log.WithFields(f).Debugf("Detected model.Type: %s - processing...", model.Type) + Update(model) default: - log.Warnf("unrecognized message type: %s - unable to process message ", model.Type) + log.WithFields(f).Warnf("unrecognized message type: %s - unable to process message ", model.Type) } } return nil } -// Write saves the user data model to persistent storage -func Write(user event.Event) { +// Create saves the user data model to persistent storage +func Create(user event.Event) { + f := logrus.Fields{ + "functionName": "userSubscribeLambda.main.Create", + } + + uc := &usersModels.UserCreated{} + err := mapstructure.Decode(user.Data, uc) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to decode event") + return + } + + var userDetails *models.User + var userErr error + var awsSession = session.Must(session.NewSession(&aws.Config{})) + + stage := os.Getenv("STAGE") + if stage == "" { + log.Fatal("stage not set") + } + usersRepo := users.NewRepository(awsSession, stage) + + userDetails, userErr = usersRepo.GetUserByLFUserName(uc.Username) + if userErr != nil { + log.WithFields(f).WithError(userErr).Warnf("unable to locate user by LfUsername: %s", uc.Username) + } + + if userDetails != nil { + log.WithFields(f).Warnf("unable to create user - user already created: %s", uc.Username) + } + + userServiceClient := user_service.GetClient() + sfdcUserObject, err := userServiceClient.GetUserByUsername(uc.Username) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to locate user by SFID: %s", uc.Username) + return + } + if sfdcUserObject == nil { + log.WithFields(f).Debugf("User-service model is nil so skipping user %s", uc.Username) + return + } + + log.WithFields(f).Debugf("Salesforce user-service object : %+v", sfdcUserObject) + + var primaryEmail string + var emails []string + for _, email := range sfdcUserObject.Emails { + if *email.IsPrimary { + primaryEmail = *email.EmailAddress + } + emails = append(emails, *email.EmailAddress) + } + + _, nowStr := utils.CurrentTime() + createUserModel := &models.User{ + Admin: false, + DateCreated: nowStr, + DateModified: nowStr, + Emails: emails, + LfEmail: primaryEmail, + LfUsername: sfdcUserObject.Username, + Note: "Create via user-service event", + UserExternalID: sfdcUserObject.ID, + UserID: userDetails.UserID, + Username: fmt.Sprintf("%s %s", sfdcUserObject.FirstName, sfdcUserObject.LastName), + Version: "v1", + } + + log.WithFields(f).Debugf("Creating user in Dynamo DB : %+v", createUserModel) + _, createErr := usersRepo.CreateUser(createUserModel) + if createErr != nil { + log.WithFields(f).Warnf("unable to create user by LfUsername: %s", uc.Username) + return + } +} + +// Update saves the user data model to persistent storage +func Update(user event.Event) { + f := logrus.Fields{ + "functionName": "userSubscribeLambda.main.Update", + } uc := &usersModels.UserUpdated{} err := mapstructure.Decode(user.Data, uc) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to decode event") return } @@ -104,15 +197,14 @@ func Write(user event.Event) { userDetails, userErr = usersRepo.GetUserByLFUserName(*uc.Username) if userErr != nil { - log.Warnf("Error - unable to locate user by LfUsername: %s, error: %+v", *uc.Username, userErr) - log.Error("", userErr) + log.WithFields(f).WithError(userErr).Warnf("unable to locate user by LfUsername: %s", *uc.Username) } if userDetails == nil { for _, email := range uc.Emails { userDetails, userErr = usersRepo.GetUserByEmail(*email.EmailAddress) if userErr != nil { - log.Warnf("Error - unable to locate user by LfUsername: %s, error: %+v", *uc.Username, userErr) + log.WithFields(f).WithError(userErr).Warnf("unable to locate user by LfUsername: %s", *uc.Username) } } } @@ -120,28 +212,31 @@ func Write(user event.Event) { if userDetails == nil { userDetails, userErr = usersRepo.GetUserByExternalID(uc.UserID) if userErr != nil { - log.Warnf("Error - unable to locate user by UserExternalID: %s, error: %+v", uc.UserID, userErr) + log.WithFields(f).WithError(userErr).Warnf("unable to locate user by UserExternalID: %s", uc.UserID) } } if userDetails == nil { - log.Debugf("User model is nil so skipping user %s", *uc.Username) + log.WithFields(f).Debugf("User model is nil - adding as new user %s...", *uc.Username) + // Attempt to create the user from the upate model + createFromUpdateErr := createUserFromUpdatedModel(uc) + if createFromUpdateErr != nil { + log.WithFields(f).WithError(createFromUpdateErr).Warnf("unable to create new user record from user service update message: %s", uc.UserID) + } return } userServiceClient := user_service.GetClient() - sfdcUserObject, err := userServiceClient.GetUser(uc.UserID) if err != nil { - log.Warnf("Error - unable to locate user by SFID: %s, error: %+v", uc.UserID, userErr) - log.Error("", userErr) + log.WithFields(f).WithError(err).Warnf("unable to locate user by SFID: %s, error: %+v", uc.UserID, userErr) return } - log.Debugf("Salesforce user-service object : %+v", sfdcUserObject) + log.WithFields(f).Debugf("Salesforce user-service object : %+v", sfdcUserObject) if sfdcUserObject == nil { - log.Debugf("User-service model is nil so skipping user %s with SFID %s", *uc.Username, uc.UserID) + log.WithFields(f).Debugf("User-service model is nil so skipping user %s with SFID %s", *uc.Username, uc.UserID) return } @@ -155,37 +250,90 @@ func Write(user event.Event) { } updateUserModel := &models.UserUpdate{ + Emails: emails, LfEmail: primaryEmail, LfUsername: sfdcUserObject.Username, Note: "Update via user-service event", UserExternalID: sfdcUserObject.ID, UserID: userDetails.UserID, Username: fmt.Sprintf("%s %s", sfdcUserObject.FirstName, sfdcUserObject.LastName), - Emails: emails, } - log.Debugf("Updating user in Dynamo DB : %+v", updateUserModel) - + log.WithFields(f).Debugf("Updating user in Dynamo DB : %+v", updateUserModel) _, updateErr := usersRepo.Save(updateUserModel) if updateErr != nil { - log.Warnf("Error - unable to update user by LfUsername: %s, error: %+v", *uc.Username, updateErr) + log.WithFields(f).Warnf("Error - unable to update user by LfUsername: %s, error: %+v", *uc.Username, updateErr) return } } +func createUserFromUpdatedModel(userModelUpdated *usersModels.UserUpdated) error { + f := logrus.Fields{ + "functionName": "userSubscribeLambda.main.createUserFromUpdatedModel", + "userID": userModelUpdated.UserID, + "userName": userModelUpdated.Username, + } + + var awsSession = session.Must(session.NewSession(&aws.Config{})) + + stage := os.Getenv("STAGE") + if stage == "" { + log.Fatal("stage not set") + } + userServiceClient := user_service.GetClient() + sfdcUserObject, err := userServiceClient.GetUser(userModelUpdated.UserID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to locate user by ID: %s", userModelUpdated.UserID) + return err + } + + var primaryEmail string + var emails []string + for _, email := range sfdcUserObject.Emails { + if *email.IsPrimary { + primaryEmail = *email.EmailAddress + } + emails = append(emails, *email.EmailAddress) + } + + newUserModel := &models.User{ + Emails: emails, + LfEmail: primaryEmail, + LfUsername: sfdcUserObject.Username, + Note: "Update via user-service event", + UserExternalID: sfdcUserObject.ID, + UserID: userModelUpdated.UserID, + Username: fmt.Sprintf("%s %s", sfdcUserObject.FirstName, sfdcUserObject.LastName), + } + + log.WithFields(f).Debugf("Creating user in Dynamo DB : %+v", newUserModel) + usersRepo := users.NewRepository(awsSession, stage) + + _, createErr := usersRepo.CreateUser(newUserModel) + if createErr != nil { + log.WithFields(f).WithError(createErr).Warnf("unable to create user by LfUsername: %s", *userModelUpdated.Username) + return createErr + } + + return nil +} + func main() { + f := logrus.Fields{ + "functionName": "userSubscribeLambda.main.main", + } var err error // Show the version and build info - log.Infof("Name : userSubscribe handler") - log.Infof("Version : %s", version) - log.Infof("Git commit hash : %s", commit) - log.Infof("Build date : %s", buildDate) - log.Infof("Golang OS : %s", runtime.GOOS) - log.Infof("Golang Arch : %s", runtime.GOARCH) + log.WithFields(f).Infof("Name : userSubscribe handler") + log.WithFields(f).Infof("Version : %s", version) + log.WithFields(f).Infof("Git commit hash : %s", commit) + log.WithFields(f).Infof("Build date : %s", buildDate) + log.WithFields(f).Infof("Golang OS : %s", runtime.GOOS) + log.WithFields(f).Infof("Golang Arch : %s", runtime.GOARCH) err = cmd.Start(Handler) if err != nil { - log.Fatal(err) + log.WithFields(f).WithError(err).Fatal(err) } } From 61eda27ead12afd78ef2b60599469ff35849e01c Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 9 Feb 2021 17:20:42 +0200 Subject: [PATCH 0060/1276] use templating for cla-manager emails (#2599) Signed-off-by: makkalot --- cla-backend-go/approval_list/service.go | 129 +++++---- cla-backend-go/cla_manager/handlers.go | 171 ++++++------ cla-backend-go/cla_manager/service.go | 177 ++++++------ cla-backend-go/cmd/server.go | 4 +- .../emails/approval_list_templates.go | 74 +++++ .../emails/approval_list_templates_test.go | 68 +++++ .../emails/cla_manager_templates.go | 255 ++++++++++++++++++ .../emails/cla_manager_templates_test.go | 233 ++++++++++++++++ cla-backend-go/emails/render.go | 30 +++ cla-backend-go/emails/uitls.go | 37 +++ .../emails/v2_cla_manager_templates.go | 222 +++++++++++++++ .../emails/v2_cla_manager_templates_test.go | 176 ++++++++++++ cla-backend-go/v2/cla_manager/service.go | 233 ++++++++-------- 13 files changed, 1465 insertions(+), 344 deletions(-) create mode 100644 cla-backend-go/emails/approval_list_templates.go create mode 100644 cla-backend-go/emails/approval_list_templates_test.go create mode 100644 cla-backend-go/emails/cla_manager_templates.go create mode 100644 cla-backend-go/emails/cla_manager_templates_test.go create mode 100644 cla-backend-go/emails/render.go create mode 100644 cla-backend-go/emails/uitls.go create mode 100644 cla-backend-go/emails/v2_cla_manager_templates.go create mode 100644 cla-backend-go/emails/v2_cla_manager_templates_test.go diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 77af8b8f8..1bb039f12 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -9,6 +9,10 @@ import ( "fmt" "net/http" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + + "github.com/communitybridge/easycla/cla-backend-go/emails" + "github.com/communitybridge/easycla/cla-backend-go/signatures" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -41,25 +45,27 @@ type IService interface { } type service struct { - repo IRepository - userRepo users.UserRepository - companyRepo company.IRepository - projectRepo project.ProjectRepository - signatureRepo signatures.SignatureRepository - corpConsoleURL string - httpClient *http.Client + repo IRepository + userRepo users.UserRepository + companyRepo company.IRepository + projectRepo project.ProjectRepository + signatureRepo signatures.SignatureRepository + projectsCLAGroupRepository projects_cla_groups.Repository + corpConsoleURL string + httpClient *http.Client } // NewService creates a new whitelist service -func NewService(repo IRepository, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, corpConsoleURL string, httpClient *http.Client) IService { +func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups.Repository, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, corpConsoleURL string, httpClient *http.Client) IService { return service{ - repo: repo, - userRepo: userRepo, - companyRepo: companyRepo, - projectRepo: projectRepo, - signatureRepo: signatureRepo, - corpConsoleURL: corpConsoleURL, - httpClient: httpClient, + repo: repo, + projectsCLAGroupRepository: projectsCLAGroupRepository, + userRepo: userRepo, + companyRepo: companyRepo, + projectRepo: projectRepo, + signatureRepo: signatureRepo, + corpConsoleURL: corpConsoleURL, + httpClient: httpClient, } } @@ -225,7 +231,7 @@ func (s service) sendRequestSentEmail(companyModel *models.Company, claGroupMode // CLA Manager Name/Email from a list, send this to this recipient (CLA Manager) - otherwise we will send to all // CLA Managers on the Signature ACL if recipientName != "" && recipientEmail != "" { - s.sendRequestEmailToRecipient(companyModel, claGroupModel, contributorName, contributorEmail, recipientName, recipientEmail, message) + s.sendRequestEmailToRecipient(s.projectsCLAGroupRepository, companyModel, claGroupModel, contributorName, contributorEmail, recipientName, recipientEmail, message) return } @@ -246,47 +252,37 @@ func (s service) sendRequestSentEmail(companyModel *models.Company, claGroupMode log.Warnf("unable to send email to manager: %+v - no email on file...", manager) } else { // Send the email - s.sendRequestEmailToRecipient(companyModel, claGroupModel, contributorName, contributorEmail, manager.Username, whichEmail, message) + s.sendRequestEmailToRecipient(s.projectsCLAGroupRepository, companyModel, claGroupModel, contributorName, contributorEmail, manager.Username, whichEmail, message) } } } // sendRequestEmailToRecipient generates and sends an email to the specified recipient -func (s service) sendRequestEmailToRecipient(companyModel *models.Company, claGroupModel *models.ClaGroup, contributorName, contributorEmail, recipientName, recipientAddress, message string) { +func (s service) sendRequestEmailToRecipient(projectClaGroupRepository projects_cla_groups.Repository, companyModel *models.Company, claGroupModel *models.ClaGroup, contributorName, contributorEmail, recipientName, recipientAddress, message string) { companyName := companyModel.CompanyName projectName := claGroupModel.ProjectName - var optionalMessage = "" - if message != "" { - optionalMessage = fmt.Sprintf("

%s included the following message in the request:

", contributorName) - optionalMessage += fmt.Sprintf("

%s


Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

%s (%s) has requested to be added to the Approved List as an authorized contributor from -%s to the project %s. You are receiving this message as a CLA Manager from %s for -%s.

-%s -

If you want to add them to the Approved List, please -log into the EasyCLA Corporate -Console, where you can approve this user's request by selecting the 'Manage Approved List' and adding the -contributor's email, the contributor's entire email domain, their GitHub ID or the entire GitHub Organization for the -repository. This will permit them to begin contributing to %s on behalf of %s.

-

If you are not certain whether to add them to the Approved List, please reach out to them directly to discuss.

-%s -%s`, - recipientName, projectName, contributorName, contributorEmail, - companyName, projectName, companyName, projectName, - optionalMessage, s.corpConsoleURL, - companyModel.CompanyID, projectName, companyName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderRequestToAuthorizeTemplate(projectClaGroupRepository, claGroupModel.Version, claGroupModel.ProjectExternalID, + emails.RequestToAuthorizeTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + }, + ContributorName: contributorName, + ContributorEmail: contributorEmail, + OptionalMessage: message, + CorporateConsoleURL: s.corpConsoleURL, + CompanyID: companyModel.CompanyID, + }) + if err != nil { + log.Warnf("rendering email template : %s failed : %v", emails.RequestToAuthorizeTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -299,9 +295,7 @@ func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Compan companyName := companyModel.CompanyName projectName := claGroupModel.ProjectName - var claManagerText = "" - claManagerText += "
    " - + emailCLAManagerParams := []emails.ClaManagerInfoParams{} // Build a fancy text string with CLA Manager name as an HTML unordered list for _, manager := range signature.SignatureACL { @@ -318,29 +312,32 @@ func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Compan if whichEmail == "" { log.Warnf("unable to send email to manager: %+v - no email on file...", manager) } else { - claManagerText += fmt.Sprintf("
  • %s <%s>
  • ", manager.Username, whichEmail) + emailCLAManagerParams = append(emailCLAManagerParams, emails.ClaManagerInfoParams{ + LfUsername: manager.Username, + Email: whichEmail, + }) } } - claManagerText += "
" // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Approval List Request Denied for Project %s", projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

Your request to get added to the approval list from %s for %s was denied by one of the existing CLA Managers. -If you have further questions about this denial, please contact one of the existing CLA Managers from -%s for %s:

-%s -%s -%s`, - recipientName, projectName, - companyName, projectName, companyName, projectName, - claManagerText, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(claGroupModel.Version, emails.ApprovalListRejectedTemplateName, + emails.ApprovalListRejectedTemplate, + emails.ApprovalListRejectedTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + CLAManagers: emailCLAManagerParams, + }, + }, + ) + if err != nil { + log.Warnf("rendering email failed for : %s : %v", emails.ApprovalListRejectedTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index d37b32052..d66f65225 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/communitybridge/easycla/cla-backend-go/emails" + "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/cla_manager" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -801,27 +803,23 @@ func sendRequestAccessEmailToCLAManagers(companyModel *models.Company, claGroupM // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: New CLA Manager Access Request for %s on %s", companyName, projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

You are currently listed as a CLA Manager from %s for the project %s. This means that you are able to maintain the -list of employees allowed to contribute to %s on behalf of your company, as well as view and manage the list of -your company’s CLA Managers for %s.

-

%s (%s) has requested to be added as another CLA Manager from %s for %s. This would permit them to maintain the -lists of approved contributors and CLA Managers as well.

-

If you want to permit this, please log into the EasyCLA Corporate Console, -select your company, then select the %s project. From the CLA Manager requests, you can approve this user as an -additional CLA Manager.

-%s -%s -`, - recipientName, projectName, - companyName, projectName, projectName, projectName, - requesterName, requesterEmail, companyName, projectName, - utils.GetCorporateURL(claGroupModel.Version == utils.V2), projectName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(claGroupModel.Version, emails.RequestAccessToCLAManagersTemplateName, + emails.RequestAccessToCLAManagersTemplate, emails.RequestAccessToCLAManagersTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + }, + RequesterName: requesterName, + RequesterEmail: requesterEmail, + CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), + }) + if err != nil { + log.Warnf("rendering email template : %s failed : %v", emails.RequestAccessToCLAManagersTemplateName, err) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -836,23 +834,25 @@ func sendRequestApprovedEmailToCLAManagers(companyModel *models.Company, claGrou // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Access Approval Notice for %s", projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

The following user has been approved as a CLA Manager from %s for the project %s. This means that they can now -maintain the list of employees allowed to contribute to %s on behalf of your company, as well as view and manage the -list of company’s CLA Managers for %s.

-
    -
  • %s (%s)
  • -
-%s -%s`, - recipientName, projectName, - companyName, projectName, projectName, projectName, - requesterName, requesterEmail, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate( + claGroupModel.Version, + emails.RequestApprovedToCLAManagersTemplateName, + emails.RequestApprovedToCLAManagersTemplate, + emails.RequestApprovedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + }, + RequesterName: requesterName, + RequesterEmail: requesterEmail, + }) + + if err != nil { + log.Warnf("rendering email template : %s failed : %v", emails.RequestApprovedToCLAManagersTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -867,22 +867,20 @@ func sendRequestApprovedEmailToRequester(companyModel *models.Company, claGroupM // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: New CLA Manager Access Approved for %s", projectName) recipients := []string{requesterEmail} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

You have now been approved as a CLA Manager from %s for the project %s. This means that you can now maintain the -list of employees allowed to contribute to %s on behalf of your company, as well as view and manage the list of your -company’s CLA Managers for %s.

-

To get started, please log into the EasyCLA Corporate Console, and select your -company and then the project %s. From here you will be able to edit the list of approved employees and CLA Managers.

-%s -%s`, - requesterName, projectName, - companyName, projectName, projectName, projectName, - utils.GetCorporateURL(claGroupModel.Version == utils.V2), projectName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(claGroupModel.Version, emails.RequestApprovedToRequesterTemplateName, + emails.RequestApprovedToRequesterTemplate, emails.RequestApprovedToRequesterTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: requesterName, + ProjectName: projectName, + CompanyName: companyName, + }, + CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), + }) + if err != nil { + log.Warnf("email template : %s failed rendering : %s", emails.RequestApprovedToRequesterTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -897,22 +895,27 @@ func sendRequestDeniedEmailToCLAManagers(companyModel *models.Company, claGroupM // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Access Denied Notice for %s", projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

The following user has been denied as a CLA Manager from %s for the project %s. This means that they will not -be able to maintain the list of employees allowed to contribute to %s on behalf of your company.

-
    -
  • %s (%s)
  • -
-%s -%s`, - recipientName, projectName, - companyName, projectName, projectName, - requesterName, requesterEmail, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate( + claGroupModel.Version, + emails.RequestDeniedToCLAManagersTemplateName, + emails.RequestDeniedToCLAManagersTemplate, + emails.RequestDeniedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + }, + RequesterName: requesterName, + RequesterEmail: requesterEmail, + }, + ) + + if err != nil { + log.Warnf("email template render : %s failed : %v", emails.RequestDeniedToCLAManagersTemplateName, err) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -927,18 +930,22 @@ func sendRequestDeniedEmailToRequester(companyModel *models.Company, claGroupMod // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: New CLA Manager Access Denied for %s", projectName) recipients := []string{requesterEmail} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

You have been denied as a CLA Manager from %s for the project %s. This means that you can not maintain the -list of employees allowed to contribute to %s on behalf of your company.

-%s -%s`, - requesterName, projectName, - companyName, projectName, projectName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(claGroupModel.Version, emails.RequestDeniedToRequesterTemplateName, + emails.RequestDeniedToRequesterTemplate, + emails.RequestDeniedToRequesterTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: requesterName, + ProjectName: projectName, + CompanyName: companyName, + }, + }) + + if err != nil { + log.Warnf("email template rendering %s failed : %v", emails.RequestDeniedToRequesterTemplateName, err) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index e526edc55..6579c1370 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -7,6 +7,9 @@ import ( "context" "fmt" + "github.com/communitybridge/easycla/cla-backend-go/emails" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -37,25 +40,27 @@ type IService interface { } type service struct { - repo IRepository - companyService company.IService - projectService project.Service - usersService users.Service - sigService signatures.SignatureService - eventsService events.Service - corporateConsoleURL string + repo IRepository + projectClaRepository projects_cla_groups.Repository + companyService company.IService + projectService project.Service + usersService users.Service + sigService signatures.SignatureService + eventsService events.Service + corporateConsoleURL string } // NewService creates a new service object -func NewService(repo IRepository, companyService company.IService, projectService project.Service, usersService users.Service, sigService signatures.SignatureService, eventsService events.Service, corporateConsoleURL string) IService { +func NewService(repo IRepository, projectClaRepository projects_cla_groups.Repository, companyService company.IService, projectService project.Service, usersService users.Service, sigService signatures.SignatureService, eventsService events.Service, corporateConsoleURL string) IService { return service{ - repo: repo, - companyService: companyService, - projectService: projectService, - usersService: usersService, - sigService: sigService, - eventsService: eventsService, - corporateConsoleURL: corporateConsoleURL, + repo: repo, + projectClaRepository: projectClaRepository, + companyService: companyService, + projectService: projectService, + usersService: usersService, + sigService: sigService, + eventsService: eventsService, + corporateConsoleURL: corporateConsoleURL, } } @@ -324,7 +329,7 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou } // Notify the removed manager - sendRemovedClaManagerEmailToRecipient(companyModel, claGroupModel, userModel.LfUsername, userModel.LfEmail, claManagers) + sendRemovedClaManagerEmailToRecipient(s.projectClaRepository, companyModel, claGroupModel, userModel.LfUsername, userModel.LfEmail, claManagers) // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ @@ -356,22 +361,25 @@ func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Added as CLA Manager for Project :%s", projectName) recipients := []string{requesterEmail} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

You have been added as a CLA Manager from %s for the project %s. This means that you can now maintain the -list of employees allowed to contribute to %s on behalf of your company, as well as view and manage the list of your -company’s CLA Managers for %s.

-

To get started, please log into the EasyCLA Corporate Console, and select your -company and then the project %s. From here you will be able to edit the list of approved employees and CLA Managers.

-%s -%s`, - requesterName, projectName, - companyName, projectName, projectName, projectName, - utils.GetCorporateURL(claGroupModel.Version == utils.V2), projectName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate( + claGroupModel.Version, + emails.ClaManagerAddedEToUserTemplateName, + emails.ClaManagerAddedEToUserTemplate, + emails.ClaManagerAddedEToUserTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: requesterName, + ProjectName: projectName, + CompanyName: companyName, + }, + CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), + }, + ) + if err != nil { + log.Warnf("email template render : %s failed : %v", emails.ClaManagerAddedEToUserTemplateName, err) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -386,23 +394,24 @@ func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGrou // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Added Notice for %s", projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

The following user has been added as a CLA Manager from %s for the project %s. This means that they can now -maintain the list of employees allowed to contribute to %s on behalf of your company, as well as view and manage the -list of company’s CLA Managers for %s.

-
    -
  • %s (%s)
  • -
-%s -%s`, - recipientName, projectName, - companyName, projectName, projectName, projectName, - name, email, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(claGroupModel.Version, emails.ClaManagerAddedToCLAManagersTemplateName, + emails.ClaManagerAddedToCLAManagersTemplate, + emails.ClaManagerAddedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + }, + Name: name, + Email: email, + }) + + if err != nil { + log.Warnf("email template render : %s failed : %v", emails.ClaManagerAddedToCLAManagersTemplate, err) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -411,12 +420,12 @@ list of company’s CLA Managers for %s.

} // sendRequestRejectedEmailToRecipient generates and sends an email to the specified recipient -func sendRemovedClaManagerEmailToRecipient(companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, claManagers []models.User) { +func sendRemovedClaManagerEmailToRecipient(projectsClaGroupRepository projects_cla_groups.Repository, companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, claManagers []models.User) { companyName := companyModel.CompanyName projectName := claGroupModel.ProjectName - var companyManagerText = "" - companyManagerText += "
    " + emailCLAManagerParams := []emails.ClaManagerInfoParams{} + log.Debugf("CLA Managers found: %+v", claManagers) // Build a fancy text string with CLA Manager name as an HTML unordered list @@ -450,28 +459,30 @@ func sendRemovedClaManagerEmailToRecipient(companyModel *models.Company, claGrou } else { log.Warnf("Username: %s", companyAdmin.LfUsername) log.Warnf("Email: %s ", whichEmail) - companyManagerText += fmt.Sprintf("
  • %s %s
  • ", companyAdmin.LfUsername, whichEmail) + emailCLAManagerParams = append(emailCLAManagerParams, emails.ClaManagerInfoParams{ + LfUsername: companyAdmin.LfUsername, + Email: whichEmail, + }) } } - companyManagerText += "
" - // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Removed as CLA Manager for Project %s", projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

You have been removed as a CLA Manager from %s for the project %s.

-

If you have further questions about this, please contact one of the existing managers from -%s:

-%s -%s -%s`, - recipientName, projectName, companyName, projectName, companyName, companyManagerText, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderRemovedCLAManagerTemplate( + projectsClaGroupRepository, + claGroupModel.Version, + recipientName, + companyName, + claGroupModel.ProjectExternalID, + emailCLAManagerParams) + + if err != nil { + log.Warnf("rendering the email content failed for : %s", emails.RemovedCLAManagerTemplateName) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -486,17 +497,25 @@ func sendClaManagerDeleteEmailToCLAManagers(companyModel *models.Company, claGro // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Removed Notice for %s", projectName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

Hello %s,

-

This is a notification email from EasyCLA regarding the project %s.

-

%s(%s) has been removed as a CLA Manager from %s for the project %s.

-%s -%s -`, - recipientName, projectName, name, email, companyName, projectName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(claGroupModel.Version, + emails.ClaManagerDeletedToCLAManagersTemplateName, + emails.ClaManagerDeletedToCLAManagersTemplate, + emails.ClaManagerDeletedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + CompanyName: companyName, + }, + Name: name, + Email: email, + }) + + if err != nil { + log.Warnf("email template render : %s failed : %v", emails.ClaManagerDeletedToCLAManagersTemplateName, err) + return + } + + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 64020c4c6..f0c641092 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -266,11 +266,11 @@ func server(localMode bool) http.Handler { v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, projectRepo, projectClaGroupRepo, v1CompanyService) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo) - v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) + v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) - v1ApprovalListService := approval_list.NewService(approvalListRepo, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) + v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) diff --git a/cla-backend-go/emails/approval_list_templates.go b/cla-backend-go/emails/approval_list_templates.go new file mode 100644 index 000000000..2baf301c1 --- /dev/null +++ b/cla-backend-go/emails/approval_list_templates.go @@ -0,0 +1,74 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + +// ApprovalListRejectedTemplateParams is email params for ApprovalListRejectedTemplate +type ApprovalListRejectedTemplateParams struct { + CLAManagerTemplateParams +} + +const ( + // ApprovalListRejectedTemplateName is email template name for ApprovalListRejectedTemplate + ApprovalListRejectedTemplateName = "ApprovalListRejectedTemplate" + // ApprovalListRejectedTemplate is email template for + ApprovalListRejectedTemplate = ` +

Hello {{.RecipientName}},

+

This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

+

Your request to get added to the approval list from {{.CompanyName}} for {{.ProjectName}} was denied by one of the existing CLA Managers. +If you have further questions about this denial, please contact one of the existing CLA Managers from +{{.CompanyName}} for {{.CompanyName}}:

+
    + {{range .CLAManagers}} +
  • {{.LfUsername}} {{.Email}}
  • + {{end}} +
+` +) + +// RequestToAuthorizeTemplateParams is email params for RequestToAuthorizeTemplate +type RequestToAuthorizeTemplateParams struct { + CLAManagerTemplateParams + ContributorName string + ContributorEmail string + OptionalMessage string + CorporateConsoleURL string + CompanyID string +} + +const ( + // RequestToAuthorizeTemplateName is email template name for RequestToAuthorizeTemplate + RequestToAuthorizeTemplateName = "RequestToAuthorizeTemplate" + // RequestToAuthorizeTemplate is email template for + RequestToAuthorizeTemplate = ` +

Hello {{.RecipientName}},

+

This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}}.

+

{{.ContributorName}} ({{.ContributorEmail}}) has requested to be added to the Approved List as an authorized contributor from +{{.CompanyName}} to the project {{.ProjectName}}. You are receiving this message as a CLA Manager from {{.CompanyName}} for +{{.ProjectName}}.

+{{if .OptionalMessage}} +

{{.ContributorName}} included the following message in the request:

+

{{.OptionalMessage}}


+{{end}} +

If you want to add them to the Approved List, please +log into the EasyCLA Corporate +Console, where you can approve this user's request by selecting the 'Manage Approved List' and adding the +contributor's email, the contributor's entire email domain, their GitHub ID or the entire GitHub Organization for the +repository. This will permit them to begin contributing to {{.ProjectName}} on behalf of {{.CompanyName}}.

+

If you are not certain whether to add them to the Approved List, please reach out to them directly to discuss.

+` +) + +// RenderRequestToAuthorizeTemplate renders RequestToAuthorizeTemplate +func RenderRequestToAuthorizeTemplate(repository projects_cla_groups.Repository, claGroupVersion string, projecSFID string, params RequestToAuthorizeTemplateParams) (string, error) { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projecSFID, ¶ms.CLAManagerTemplateParams); err != nil { + return "", err + } + + return RenderTemplate(claGroupVersion, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, + params, + ) + +} diff --git a/cla-backend-go/emails/approval_list_templates_test.go b/cla-backend-go/emails/approval_list_templates_test.go new file mode 100644 index 000000000..270de899f --- /dev/null +++ b/cla-backend-go/emails/approval_list_templates_test.go @@ -0,0 +1,68 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestApprovalListRejectedTemplate(t *testing.T) { + params := ApprovalListRejectedTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + CompanyName: "JohnsCompany", + CLAManagers: []ClaManagerInfoParams{ + {LfUsername: "LFUserName", Email: "LFEmail"}, + }, + }, + } + + result, err := RenderTemplate(utils.V1, ApprovalListRejectedTemplateName, ApprovalListRejectedTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "approval list from JohnsCompany for JohnsProject") + assert.Contains(t, result, "
  • LFUserName LFEmail
  • ") +} + +func TestRequestToAuthorizeTemplate(t *testing.T) { + params := RequestToAuthorizeTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + CLAGroupName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + CLAManagers: []ClaManagerInfoParams{ + {LfUsername: "LFUserName", Email: "LFEmail"}, + }, + }, + ContributorName: "ContributorNameValue", + ContributorEmail: "ContributorEmailValue", + CorporateConsoleURL: "CorporateConsoleURLValue", + CompanyID: "CompanyIDValue", + } + + result, err := RenderTemplate(utils.V1, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProjectExternal and CLA Group JohnsProject") + assert.Contains(t, result, "ContributorNameValue (ContributorEmailValue) has requested") + assert.Contains(t, result, "") + assert.Contains(t, result, "contributing to JohnsProject on behalf of JohnsCompany") + + params.OptionalMessage = "OptionalMessageValue" + result, err = RenderTemplate(utils.V1, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "ContributorNameValue included the following message") + assert.Contains(t, result, "

    OptionalMessageValue


    ") + +} diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go new file mode 100644 index 000000000..fb11a71f9 --- /dev/null +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -0,0 +1,255 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + +// ClaManagerInfoParams represents the CLAManagerInfo used inside of the Email Templates +type ClaManagerInfoParams struct { + LfUsername string + Email string +} + +// CLAManagerTemplateParams includes the params for the CLAManagerTemplateParams +type CLAManagerTemplateParams struct { + RecipientName string + CLAGroupName string + // ProjectName is same as CLAGroupName in this context it's a legacy naming + // convention we used to have so we keep it here, for the actual project name + // we'll be using ExternalProjectName + ProjectName string + ExternalProjectName string + CompanyName string + FoundationName string + CLAManagers []ClaManagerInfoParams + // ChildProjectCount indicates how many childProjects are under this CLAGroup + // this is important for some of the email rendering knowing if claGroup has + // multiple children + ChildProjectCount int +} + +// GetProjectNameOrFoundation returns if the foundationName is set it gets back +// the foundation Name otherwise the ProjectName is returned +func (claParams CLAManagerTemplateParams) GetProjectNameOrFoundation() string { + if claParams.ChildProjectCount == 0 { + return claParams.ExternalProjectName + } + + // if multiple return the foundation if present + if claParams.FoundationName != "" { + return claParams.FoundationName + } + //default to project name if nothing works + return claParams.ExternalProjectName +} + +// RemovedCLAManagerTemplateParams is email params for RemovedCLAManagerTemplate +type RemovedCLAManagerTemplateParams struct { + CLAManagerTemplateParams +} + +const ( + // RemovedCLAManagerTemplateName is name of the RemovedCLAManagerTemplate + RemovedCLAManagerTemplateName = "RemovedCLAManagerTemplate" + // RemovedCLAManagerTemplate includes the email template for email when user is removed as CLA Manager + RemovedCLAManagerTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}}.

    +

    You have been removed as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}.

    +

    If you have further questions about this, please contact one of the existing managers from +{{.CompanyName}}:

    +
      + {{range .CLAManagers}} +
    • {{.LfUsername}} {{.Email}}
    • + {{end}} +
    +` +) + +// RenderRemovedCLAManagerTemplate renders the RemovedCLAManagerTemplate +func RenderRemovedCLAManagerTemplate(repository projects_cla_groups.Repository, claGroupModelVersion, recipientName, companyName, projectSFID string, claManagers []ClaManagerInfoParams) (string, error) { + params := CLAManagerTemplateParams{ + RecipientName: recipientName, + CompanyName: companyName, + CLAManagers: claManagers, + } + + err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms) + if err != nil { + return "", err + } + + return RenderTemplate(claGroupModelVersion, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, RemovedCLAManagerTemplateParams{params}) +} + +// RequestAccessToCLAManagersTemplateParams is email params for RequestAccessToCLAManagersTemplate +type RequestAccessToCLAManagersTemplateParams struct { + CLAManagerTemplateParams + RequesterName string + RequesterEmail string + CorporateURL string +} + +const ( + // RequestAccessToCLAManagersTemplateName is email template name for RequestAccessToCLAManagersTemplate + RequestAccessToCLAManagersTemplateName = "RequestAccessToCLAManagersTemplateName" + // RequestAccessToCLAManagersTemplate is email template for + RequestAccessToCLAManagersTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    You are currently listed as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you are able to maintain the +list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the list of +your company’s CLA Managers for {{.ProjectName}}.

    +

    {{.RequesterName}} ({{.RequesterEmail}}) has requested to be added as another CLA Manager from {{.CompanyName}} for {{.ProjectName}}. This would permit them to maintain the +lists of approved contributors and CLA Managers as well.

    +

    If you want to permit this, please log into the EasyCLA Corporate Console, +select your company, then select the {{.ProjectName}} project. From the CLA Manager requests, you can approve this user as an +additional CLA Manager.

    +` +) + +// RequestApprovedToCLAManagersTemplateParams is email params for RequestApprovedToCLAManagersTemplate +type RequestApprovedToCLAManagersTemplateParams struct { + CLAManagerTemplateParams + RequesterName string + RequesterEmail string +} + +const ( + // RequestApprovedToCLAManagersTemplateName is email template name for RequestApprovedToCLAManagersTemplate + RequestApprovedToCLAManagersTemplateName = "RequestApprovedToCLAManagersTemplateName" + // RequestApprovedToCLAManagersTemplate is email template for + RequestApprovedToCLAManagersTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    The following user has been approved as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that they can now +maintain the list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the +list of company’s CLA Managers for {{.ProjectName}}.

    +
      +
    • {{.RequesterName}} ({{.RequesterEmail}})
    • +
    +` +) + +// RequestApprovedToRequesterTemplateParams email template params for RequestApprovedToRequesterTemplate +type RequestApprovedToRequesterTemplateParams struct { + CLAManagerTemplateParams + CorporateURL string +} + +const ( + // RequestApprovedToRequesterTemplateName is email template name for RequestApprovedToRequesterTemplate + RequestApprovedToRequesterTemplateName = "RequestApprovedToRequesterTemplate" + // RequestApprovedToRequesterTemplate is email template for + RequestApprovedToRequesterTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    You have now been approved as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you can now maintain the +list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the list of your +company’s CLA Managers for {{.ProjectName}}.

    +

    To get started, please log into the EasyCLA Corporate Console, and select your +company and then the project {{.ProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

    +` +) + +// RequestDeniedToCLAManagersTemplateParams is email params for RequestDeniedToCLAManagersTemplate +type RequestDeniedToCLAManagersTemplateParams struct { + CLAManagerTemplateParams + RequesterName string + RequesterEmail string +} + +const ( + // RequestDeniedToCLAManagersTemplateName is email template name for RequestDeniedToCLAManagersTemplate + RequestDeniedToCLAManagersTemplateName = "RequestDeniedToCLAManagersTemplate" + // RequestDeniedToCLAManagersTemplate is email template for + RequestDeniedToCLAManagersTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    The following user has been denied as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that they will not +be able to maintain the list of employees allowed to contribute to {{.ProjectName}} on behalf of your company.

    +
      +
    • {{.RequesterName}} ({{.RequesterEmail}})
    • +
    +` +) + +// RequestDeniedToRequesterTemplateParams is email params for RequestDeniedToRequesterTemplate +type RequestDeniedToRequesterTemplateParams struct { + CLAManagerTemplateParams +} + +const ( + // RequestDeniedToRequesterTemplateName is email template name for RequestDeniedToRequesterTemplate + RequestDeniedToRequesterTemplateName = "RequestDeniedToRequesterTemplate" + // RequestDeniedToRequesterTemplate is email template for + RequestDeniedToRequesterTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    You have been denied as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you can not maintain the +list of employees allowed to contribute to {{.ProjectName}} on behalf of your company.

    +` +) + +// ClaManagerAddedEToUserTemplateParams is email params for ClaManagerAddedEToUserTemplate +type ClaManagerAddedEToUserTemplateParams struct { + CLAManagerTemplateParams + CorporateURL string +} + +const ( + // ClaManagerAddedEToUserTemplateName is email template name for ClaManagerAddedEToUserTemplate + ClaManagerAddedEToUserTemplateName = "ClaManagerAddedEToUserTemplate" + // ClaManagerAddedEToUserTemplate is email template for + ClaManagerAddedEToUserTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    You have been added as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you can now maintain the +list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the list of your +company’s CLA Managers for {{.ProjectName}}.

    +

    To get started, please log into the EasyCLA Corporate Console, and select your +company and then the project {{.ProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

    +` +) + +// ClaManagerAddedToCLAManagersTemplateParams is email params for ClaManagerAddedToCLAManagersTemplate +type ClaManagerAddedToCLAManagersTemplateParams struct { + CLAManagerTemplateParams + Name string + Email string +} + +const ( + // ClaManagerAddedToCLAManagersTemplateName is email template name for ClaManagerAddedToCLAManagersTemplate + ClaManagerAddedToCLAManagersTemplateName = "ClaManagerAddedToCLAManagersTemplate" + // ClaManagerAddedToCLAManagersTemplate is email template for + ClaManagerAddedToCLAManagersTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    The following user has been added as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that they can now +maintain the list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the +list of company’s CLA Managers for {{.ProjectName}}.

    +
      +
    • {{.Name}} ({{.Email}})
    • +
    +` +) + +// ClaManagerDeletedToCLAManagersTemplateParams is template params for ClaManagerDeletedToCLAManagersTemplate +type ClaManagerDeletedToCLAManagersTemplateParams struct { + CLAManagerTemplateParams + Name string + Email string +} + +const ( + // ClaManagerDeletedToCLAManagersTemplateName is template name for ClaManagerDeletedToCLAManagersTemplate + ClaManagerDeletedToCLAManagersTemplateName = "ClaManagerDeletedToCLAManagersTemplate" + // ClaManagerDeletedToCLAManagersTemplate is template for + ClaManagerDeletedToCLAManagersTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    +

    {{.Name}} ({{.Email}}) has been removed as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}.

    +` +) diff --git a/cla-backend-go/emails/cla_manager_templates_test.go b/cla-backend-go/emails/cla_manager_templates_test.go new file mode 100644 index 000000000..0d962af55 --- /dev/null +++ b/cla-backend-go/emails/cla_manager_templates_test.go @@ -0,0 +1,233 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestRemovedCLAManagerTemplate(t *testing.T) { + params := RemovedCLAManagerTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + CLAManagers: []ClaManagerInfoParams{ + {LfUsername: "LFUserName", Email: "LFEmail"}, + }, + }, + } + + result, err := RenderTemplate(utils.V1, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "
  • LFUserName LFEmail
  • ") + + // even if the foundation is set we should show the project name + // because 0 child projects under the claGroup + params.CLAManagerTemplateParams.FoundationName = "CNCF" + result, err = RenderTemplate(utils.V1, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + + // then we increase the child project count so we should get the FoundationName instead of project name + params.ChildProjectCount = 2 + result, err = RenderTemplate(utils.V1, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project CNCF") +} + +func TestRequestAccessToCLAManagersTemplate(t *testing.T) { + params := RequestAccessToCLAManagersTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + RequesterName: "RequesterName", + RequesterEmail: "RequesterEmail", + CorporateURL: "http://CorporateURL.com", + } + + result, err := RenderTemplate(utils.V1, RequestAccessToCLAManagersTemplateName, RequestAccessToCLAManagersTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "contribute to JohnsProject") + assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "RequesterName (RequesterEmail) has requested") + assert.Contains(t, result, "another CLA Manager from JohnsCompany for JohnsProject") + assert.Contains(t, result, "") + assert.Contains(t, result, "then select the JohnsProject project") + +} + +func TestRequestApprovedToCLAManagersTemplate(t *testing.T) { + params := RequestApprovedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + RequesterName: "RequesterName", + RequesterEmail: "RequesterEmail", + } + + result, err := RenderTemplate(utils.V1, RequestApprovedToCLAManagersTemplateName, RequestApprovedToCLAManagersTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "allowed to contribute to JohnsProject") + assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "
  • RequesterName (RequesterEmail)
  • ") +} + +func TestRequestApprovedToRequesterTemplateParams(t *testing.T) { + params := RequestApprovedToRequesterTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + CorporateURL: "http://CorporateURL.com", + } + + result, err := RenderTemplate(utils.V1, RequestApprovedToRequesterTemplateName, RequestApprovedToRequesterTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "allowed to contribute to JohnsProject") + assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "
    ") + assert.Contains(t, result, "and then the project JohnsProject") +} + +func TestRequestDeniedToCLAManagersTemplate(t *testing.T) { + params := RequestDeniedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + RequesterName: "RequesterName", + RequesterEmail: "RequesterEmail", + } + + result, err := RenderTemplate(utils.V1, RequestDeniedToCLAManagersTemplateName, RequestDeniedToCLAManagersTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "allowed to contribute to JohnsProject") + assert.Contains(t, result, "
  • RequesterName (RequesterEmail)
  • ") +} + +func TestRequestDeniedToRequesterTemplate(t *testing.T) { + params := RequestDeniedToRequesterTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + } + + result, err := RenderTemplate(utils.V1, RequestDeniedToRequesterTemplateName, RequestDeniedToRequesterTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "allowed to contribute to JohnsProject") +} + +func TestClaManagerAddedEToUserTemplate(t *testing.T) { + params := ClaManagerAddedEToUserTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + CorporateURL: "http://CorporateURL.com", + } + + result, err := RenderTemplate(utils.V1, ClaManagerAddedEToUserTemplateName, ClaManagerAddedEToUserTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "allowed to contribute to JohnsProject") + assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "
    ") + assert.Contains(t, result, "and then the project JohnsProject") +} + +func TestClaManagerAddedToCLAManagersTemplate(t *testing.T) { + params := ClaManagerAddedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + Name: "John", + Email: "john@example.com", + } + + result, err := RenderTemplate(utils.V1, ClaManagerAddedToCLAManagersTemplateName, ClaManagerAddedToCLAManagersTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") + assert.Contains(t, result, "contribute to JohnsProject") + assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "
  • John (john@example.com)
  • ") + +} + +func TestClaManagerDeletedToCLAManagersTemplate(t *testing.T) { + params := ClaManagerDeletedToCLAManagersTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + Name: "John", + Email: "john@example.com", + } + + result, err := RenderTemplate(utils.V1, ClaManagerDeletedToCLAManagersTemplateName, ClaManagerDeletedToCLAManagersTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "John (john@example.com) has been removed") + +} diff --git a/cla-backend-go/emails/render.go b/cla-backend-go/emails/render.go new file mode 100644 index 000000000..48267e41f --- /dev/null +++ b/cla-backend-go/emails/render.go @@ -0,0 +1,30 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "bytes" + "html/template" + + "github.com/communitybridge/easycla/cla-backend-go/utils" +) + +// RenderTemplate renders the template for given template with given params +func RenderTemplate(claGroupVersion, templateName, templateStr string, params interface{}) (string, error) { + tmpl := template.New(templateName) + t, err := tmpl.Parse(templateStr) + if err != nil { + return "", err + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, params); err != nil { + return "", err + } + + result := tpl.String() + result = result + utils.GetEmailHelpContent(claGroupVersion == utils.V2) + result = result + utils.GetEmailSignOffContent() + return result, nil +} diff --git a/cla-backend-go/emails/uitls.go b/cla-backend-go/emails/uitls.go new file mode 100644 index 000000000..3c5a3d0b3 --- /dev/null +++ b/cla-backend-go/emails/uitls.go @@ -0,0 +1,37 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "errors" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" +) + +// PrefillCLAManagerTemplateParamsFromClaGroup fetches data from projects_cla_groups.Repository to prefill some of the fields +// of CLAManagerTemplateParams, like childCount and FoundationName and etc +func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups.Repository, projectSFID string, params *CLAManagerTemplateParams) error { + projectCLAGroup, err := repository.GetClaGroupIDForProject(projectSFID) + if err != nil { + if errors.Is(err, projects_cla_groups.ErrProjectNotAssociatedWithClaGroup) { + log.Warnf("no cla group was found for externalProjectID : %s skipping the prefill", projectSFID) + return nil + } + return err + } + + params.CLAGroupName = projectCLAGroup.ClaGroupName + params.ProjectName = projectCLAGroup.ClaGroupName + params.FoundationName = projectCLAGroup.FoundationName + params.ExternalProjectName = projectCLAGroup.ProjectName + + projects, err := repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) + if err != nil { + return err + } + + params.ChildProjectCount = len(projects) + return nil +} diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go new file mode 100644 index 000000000..683060d68 --- /dev/null +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -0,0 +1,222 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/utils" +) + +// V2ContributorApprovalRequestTemplateParams is email template params for V2ContributorApprovalRequestTemplate +type V2ContributorApprovalRequestTemplateParams struct { + CLAManagerTemplateParams + SigningEntityName string + UserDetails string + LfxPortalURL string +} + +const ( + // V2ContributorApprovalRequestTemplateName is email template name for V2ContributorApprovalRequestTemplate + V2ContributorApprovalRequestTemplateName = "V2ContributorApprovalRequestTemplateName" + // V2ContributorApprovalRequestTemplate is email template for + V2ContributorApprovalRequestTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

    +

    The following contributor would like to submit a contribution to the {{.SigningEntityName}} CLA Group +and is requesting to be approved as a contributor for your organization:

    +

    {{.CLAGroupName}} - Signing Entity Name: {{.UserDetails}}

    +

    Approval can be done at {{.LfxPortalURL}}

    +

    Please notify the contributor once they are added so that they may complete the contribution process.

    +` +) + +// V2OrgAdminTemplateParams is email params for V2OrgAdminTemplate +type V2OrgAdminTemplateParams struct { + CLAManagerTemplateParams + SenderName string + SenderEmail string + ProjectList []string + CorporateConsole string +} + +const ( + // V2OrgAdminTemplateName is template name for V2OrgAdminTemplate + V2OrgAdminTemplateName = "V2OrgAdminTemplate" + // V2OrgAdminTemplate is email template for + V2OrgAdminTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the CLA setup and signing process for {{.CompanyName}}.

    +

    {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following projects:

    +
      + {{range .ProjectList}} +
    • {{.}}
    • + {{end}} +
    +

    Before the contribution can be accepted, your organization must sign a CLA. +Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.ProjectName}}

    +

    If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

    +

    Please notify the user once CLA setup is complete.

    +` +) + +// V2ContributorToOrgAdminTemplateParams is email template params for V2ContributorToOrgAdminTemplate +type V2ContributorToOrgAdminTemplateParams struct { + CLAManagerTemplateParams + ProjectNames []string + UserDetails string + CorporateConsole string +} + +const ( + // V2ContributorToOrgAdminTemplateName is email template name for V2ContributorToOrgAdminTemplate + V2ContributorToOrgAdminTemplateName = "V2ContributorToOrgAdminTemplate" + // V2ContributorToOrgAdminTemplate is email template for + V2ContributorToOrgAdminTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}

    +

    The following contributor is requesting to sign CLA for organization: {{.CompanyName}}

    +

    {{.UserDetails}}

    +

    Before the user contribution can be accepted, your organization must sign a CLA. +

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the projects {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    +

    Please notify the contributor once they are added so that they may complete the contribution process.

    +` +) + +// V2CLAManagerDesigneeCorporateTemplateParams is email params for V2CLAManagerDesigneeCorporateTemplate +type V2CLAManagerDesigneeCorporateTemplateParams struct { + CLAManagerTemplateParams + SenderName string + SenderEmail string + ProjectList []string + CorporateConsole string +} + +const ( + // V2CLAManagerDesigneeCorporateTemplateName is email template name for V2CLAManagerDesigneeCorporateTemplate + V2CLAManagerDesigneeCorporateTemplateName = "V2CLAManagerDesigneeCorporateTemplate" + // V2CLAManagerDesigneeCorporateTemplate is email template for + V2CLAManagerDesigneeCorporateTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the CLA setup and signing process for {{.CompanyName}}.

    +

    {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following projects:

    +
      + {{range .ProjectList}} +
    • {{.}}
    • + {{end}} +
    +

    Before the contribution can be accepted, your organization must sign a CLA. +Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.ProjectName}}

    +

    If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

    +

    Please notify the user once CLA setup is complete.

    +` +) + +// RenderV2CLAManagerDesigneeCorporateTemplate renders V2CLAManagerDesigneeCorporateTemplate +func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups.Repository, projectSFID string, params V2CLAManagerDesigneeCorporateTemplateParams) (string, error) { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { + return "", err + } + + return RenderTemplate(utils.V2, V2CLAManagerDesigneeCorporateTemplateName, V2CLAManagerDesigneeCorporateTemplate, params) +} + +// V2ToCLAManagerDesigneeTemplateParams is email params for V2ToCLAManagerDesigneeTemplate +type V2ToCLAManagerDesigneeTemplateParams struct { + RecipientName string + ProjectNames []string + ContributorID string + ContributorName string + CorporateConsole string +} + +const ( + // V2ToCLAManagerDesigneeTemplateName is email template name for V2ToCLAManagerDesigneeTemplate + V2ToCLAManagerDesigneeTemplateName = "V2ToCLAManagerDesigneeTemplateName" + // V2ToCLAManagerDesigneeTemplate is email template for + V2ToCLAManagerDesigneeTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    +

    The following contributor is requesting to sign CLA for organization:

    +

    {{.ContributorID}} ({{.ContributorName}})

    +

    Before the user contribution can be accepted, your organization must sign a CLA. +

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s) {{ range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    +

    Please notify the contributor once they are added so that they may complete the contribution process.

    +` +) + +// V2DesigneeToUserWithNoLFIDTemplateParams is email params for V2DesigneeToUserWithNoLFIDTemplate +type V2DesigneeToUserWithNoLFIDTemplateParams struct { + CLAManagerTemplateParams + RequesterUserName string + RequesterEmail string +} + +const ( + // V2DesigneeToUserWithNoLFIDTemplateName is email template name for V2DesigneeToUserWithNoLFIDTemplate + V2DesigneeToUserWithNoLFIDTemplateName = "V2DesigneeToUserWithNoLFIDTemplateName" + // V2DesigneeToUserWithNoLFIDTemplate is email template for + V2DesigneeToUserWithNoLFIDTemplate = ` +

    Hello {{.RecipientName}},

    +

    User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for Project {{.GetProjectNameOrFoundation}}, CLA Group {{.CLAGroupName}} and Company {{.CompanyName}} but was unable to identify your account details in the EasyCLA system

    +

    This email will guide you to completing the CLA Manager role assignment

    +

    1. Accept Invite link below will take you SSO login page where you can login with your LF Login or create a LF Login and then login.

    +

    2. After logging in SSO screen should direct you to CLA Corporate Console page where you will see the project you a re associated with.

    +

    3. Click on workflow steps to complete the signup process. Please follow this documentation to help you guide through the process - https://docs.linuxfoundation.org/lfx/v/v2/easycla/corporate-cla-manager-designee-or-initial-cla-manager/sign-corporate-cla-for-a-company

    +

    4. Once you have completed CLA Manager workflow you will be able to manage the approved list of contributors

    +

    Accept Invite

    +` +) + +// RenderV2DesigneeToUserWithNoLFIDTemplate renders V2DesigneeToUserWithNoLFIDTemplate +func RenderV2DesigneeToUserWithNoLFIDTemplate(repository projects_cla_groups.Repository, projectSFID string, params V2DesigneeToUserWithNoLFIDTemplateParams) (string, error) { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { + return "", err + } + + return RenderTemplate(utils.V2, V2DesigneeToUserWithNoLFIDTemplateName, + V2DesigneeToUserWithNoLFIDTemplate, params) +} + +// V2CLAManagerToUserWithNoLFIDTemplateParams is email params for V2CLAManagerToUserWithNoLFIDTemplate +type V2CLAManagerToUserWithNoLFIDTemplateParams struct { + CLAManagerTemplateParams + RequesterUserName string + RequesterEmail string +} + +const ( + // V2CLAManagerToUserWithNoLFIDTemplateName is email template name + V2CLAManagerToUserWithNoLFIDTemplateName = "V2CLAManagerToUserWithNoLFIDTemplate" + // V2CLAManagerToUserWithNoLFIDTemplate is email template + V2CLAManagerToUserWithNoLFIDTemplate = ` +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the Project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}} in the EasyCLA system.

    +

    User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for Project {{.ProjectName}} but was unable to identify your account details in +the EasyCLA system. In order to become a CLA Manager for Project {{.ProjectName}}, you will need to accept invite below. +Once complete, notify the user {{.RequesterUserName}} and they will be able to add you as a CLA Manager.

    +

    Accept Invite

    +` +) + +// RenderV2CLAManagerToUserWithNoLFIDTemplate renders V2CLAManagerToUserWithNoLFIDTemplate +func RenderV2CLAManagerToUserWithNoLFIDTemplate(repository projects_cla_groups.Repository, recipientName, projectName, projectSFID, requesterName, requesterEmail string) (string, error) { + params := V2CLAManagerToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: recipientName, + ProjectName: projectName, + }, + RequesterUserName: requesterName, + RequesterEmail: requesterEmail, + } + + err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams) + if err != nil { + return "", err + } + + body, err := RenderTemplate(utils.V2, V2CLAManagerToUserWithNoLFIDTemplateName, + V2CLAManagerToUserWithNoLFIDTemplate, + params) + return body, err +} diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/emails/v2_cla_manager_templates_test.go new file mode 100644 index 000000000..a65723807 --- /dev/null +++ b/cla-backend-go/emails/v2_cla_manager_templates_test.go @@ -0,0 +1,176 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestV2ContributorApprovalRequestTemplate(t *testing.T) { + params := V2ContributorApprovalRequestTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", + }, + SigningEntityName: "SigningEntityNameValue", + UserDetails: "UserDetailsValue", + LfxPortalURL: "http://LfxPortalURL.com", + } + + result, err := RenderTemplate(utils.V1, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the organization JohnsCompany") + assert.Contains(t, result, "contribution to the SigningEntityNameValue CLA Group") + assert.Contains(t, result, "JohnsCLAGroupName - Signing Entity Name: UserDetailsValue") + assert.Contains(t, result, "Approval can be done at http://LfxPortalURL.com") +} + +func TestV2OrgAdminTemplate(t *testing.T) { + params := V2OrgAdminTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", + }, + SenderName: "SenderNameValue", + SenderEmail: "SenderEmailValue", + ProjectList: []string{"Project1", "Project2"}, + CorporateConsole: "http://CorporateConsole.com", + } + + result, err := RenderTemplate(utils.V1, V2OrgAdminTemplateName, V2OrgAdminTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "signing process for JohnsCompany") + assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") + assert.Contains(t, result, "Corporate CLA for JohnsCompany") + assert.Contains(t, result, "
  • Project1
  • ") + assert.Contains(t, result, "
  • Project2
  • ") + assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") + assert.Contains(t, result, "sign the CLA for this project JohnsProject") +} + +func TestV2ContributorToOrgAdminTemplate(t *testing.T) { + params := V2ContributorToOrgAdminTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", + }, + ProjectNames: []string{"Project1", "Project2"}, + UserDetails: "UserDetailsValue", + CorporateConsole: "http://CorporateConsole.com", + } + + result, err := RenderTemplate(utils.V1, V2ContributorToOrgAdminTemplateName, V2ContributorToOrgAdminTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project(s) Project1,Project2") + assert.Contains(t, result, "sign CLA for organization: JohnsCompany") + assert.Contains(t, result, "

    UserDetailsValue

    ") + assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") + assert.Contains(t, result, "CLA for any of the projects Project1,Project2") +} + +func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { + params := V2CLAManagerDesigneeCorporateTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", + }, + SenderName: "SenderNameValue", + SenderEmail: "SenderEmailValue", + ProjectList: []string{"Project1", "Project2"}, + CorporateConsole: "http://CorporateConsole.com", + } + + result, err := RenderTemplate(utils.V1, V2CLAManagerDesigneeCorporateTemplateName, V2CLAManagerDesigneeCorporateTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "CLA setup and signing process for JohnsCompany") + assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") + assert.Contains(t, result, "Corporate CLA for JohnsCompany") + assert.Contains(t, result, "
  • Project1
  • ") + assert.Contains(t, result, "
  • Project2
  • ") + assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") + assert.Contains(t, result, "sign the CLA for this project JohnsProject") +} + +func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { + params := V2ToCLAManagerDesigneeTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectNames: []string{"Project1", "Project2"}, + ContributorID: "ContributorIDValue", + ContributorName: "ContributorNameValue", + CorporateConsole: "http://CorporateConsole.com", + } + + result, err := RenderTemplate(utils.V1, V2ToCLAManagerDesigneeTemplateName, V2ToCLAManagerDesigneeTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project(s) Project1,Project2") + assert.Contains(t, result, "

    ContributorIDValue (ContributorNameValue)

    ") + assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") + assert.Contains(t, result, "CLA for one of the project(s) Project1,Project2") + +} + +func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { + params := V2DesigneeToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", + }, + RequesterUserName: "RequesterUserNameValue", + RequesterEmail: "RequesterEmailValue", + } + + result, err := RenderTemplate(utils.V1, V2DesigneeToUserWithNoLFIDTemplateName, V2DesigneeToUserWithNoLFIDTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "User RequesterUserNameValue (RequesterEmailValue) was trying") + assert.Contains(t, result, "CLA Manager for Project JohnsProjectExternal, CLA Group JohnsCLAGroupName and Company JohnsCompany") +} + +func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { + params := V2CLAManagerToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: CLAManagerTemplateParams{ + RecipientName: "JohnsClaManager", + ProjectName: "JohnsProject", + ExternalProjectName: "JohnsProjectExternal", + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", + }, + RequesterUserName: "RequesterUserNameValue", + RequesterEmail: "RequesterEmailValue", + } + + result, err := RenderTemplate(utils.V1, V2CLAManagerToUserWithNoLFIDTemplateName, V2CLAManagerToUserWithNoLFIDTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the Project JohnsProjectExternal and CLA Group JohnsCLAGroupName in the") + assert.Contains(t, result, "User RequesterUserNameValue (RequesterEmailValue) was trying") + assert.Contains(t, result, "CLA Manager for Project JohnsProject") + assert.Contains(t, result, "notify the user RequesterUserNameValue") +} diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 1f96db0e6..6b1c30235 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -11,6 +11,8 @@ import ( "sync" "time" + "github.com/communitybridge/easycla/cla-backend-go/emails" + "github.com/go-openapi/strfmt" "github.com/sirupsen/logrus" @@ -750,7 +752,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool msg := fmt.Sprintf("User: %s does not have an LF Login", userEmail) log.WithFields(f).Warn(msg) // Send email - sendEmailErr := sendEmailToUserWithNoLFID(ctx, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole) + sendEmailErr := sendEmailToUserWithNoLFID(ctx, s.projectCGRepo, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole) if sendEmailErr != nil { log.WithFields(f).Warnf("Error sending email: %+v", sendEmailErr) return nil, sendEmailErr @@ -789,7 +791,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debugf("sending Email to CLA Manager Designee email: %s ", userEmail) designeeName := fmt.Sprintf("%s %s", lfxUser.FirstName, lfxUser.LastName) - sendEmailToCLAManagerDesigneeCorporate(ctx, LfxPortalURL, v1CompanyModel.CompanyName, []string{projectSF.Name}, userEmail, designeeName, authUser.Email, authUser.UserName) + sendEmailToCLAManagerDesigneeCorporate(ctx, s.projectCGRepo, LfxPortalURL, v1CompanyModel.CompanyName, projectSF.ID, []string{projectSF.Name}, userEmail, designeeName, authUser.Email, authUser.UserName) log.WithFields(f).Debug("creating a contributor notify CLA designee log event...") // Make a note in the event log @@ -968,7 +970,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com } contibutorEmail := GetNonNoReplyUserEmail(contributor.UserEmails) - sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee") + sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee") if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) @@ -1104,21 +1106,27 @@ func sendEmailToCLAManager(ctx context.Context, manager string, managerEmail str } subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(userModel)) recipients := []string{managerEmail} - body := fmt.Sprintf(` -

    Hello %s,

    -

    This is a notification email from EasyCLA regarding the organization %s.

    -

    The following contributor would like to submit a contribution to the %s CLA Group - and is requesting to be approved as a contributor for your organization:

    -

    %s - Signing Entity Name: %s

    -

    Approval can be done at %s

    -

    Please notify the contributor once they are added so that they may complete the contribution process.

    - %s - %s`, - manager, company, signingEntityName, claGroupName, getFormattedUserDetails(userModel), lfxPortalURL, - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) + body, err := emails.RenderTemplate( + utils.V2, emails.V2ContributorApprovalRequestTemplateName, + emails.V2ContributorApprovalRequestTemplate, + emails.V2ContributorApprovalRequestTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: manager, + CompanyName: company, + CLAGroupName: claGroupName, + }, + SigningEntityName: signingEntityName, + UserDetails: getFormattedUserDetails(userModel), + LfxPortalURL: lfxPortalURL, + }, + ) + if err != nil { + log.Warnf("rendering email template : %s failed : %v", emails.V2ContributorApprovalRequestTemplateName, err) + return + } log.WithFields(f).Debugf("sending email with subject: %s to recipients: %+v...", subject, recipients) - err := utils.SendEmail(subject, body, recipients) + err = utils.SendEmail(subject, body, recipients) if err != nil { log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -1227,35 +1235,29 @@ func (s *service) isSigned(ctx context.Context, companyModel *v1Models.Company, return false, nil } -func projectsStrList(projectNames []string) string { - var sb strings.Builder - sb.WriteString("
      ") - for _, project := range projectNames { - sb.WriteString(fmt.Sprintf("
    • %s
    • ", project)) - } - sb.WriteString("
    ") - return sb.String() -} - func sendEmailToOrgAdmin(adminEmail string, admin string, company string, projectNames []string, senderEmail string, senderName string, corporateConsole string) { subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", company) recipients := []string{adminEmail} - projectList := projectsStrList(projectNames) - body := fmt.Sprintf(` -

    Hello %s,

    -

    This is a notification email from EasyCLA regarding the CLA setup and signing process for %s.

    -

    %s %s has identified you as a potential candidate to setup the Corporate CLA for %s in support of the following projects:

    -%s -

    Before the contribution can be accepted, your organization must sign a CLA. -Either you or someone whom to designate from your company can login to this portal (%s) and sign the CLA for this project %s

    -

    If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

    -

    Please notify the user once CLA setup is complete.

    -%s -%s`, - admin, company, senderName, senderEmail, company, projectList, corporateConsole, projectNames[0], - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate( + utils.V2, emails.V2OrgAdminTemplateName, + emails.V2OrgAdminTemplate, + emails.V2OrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: admin, + CompanyName: company, + ProjectName: projectNames[0], + }, + SenderName: senderName, + SenderEmail: senderEmail, + ProjectList: projectNames, + CorporateConsole: corporateConsole, + }, + ) + if err != nil { + log.Warnf("rendering email template : %s failed : %v", emails.V2OrgAdminTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -1266,20 +1268,24 @@ Either you or someone whom to designate from your company can login to this port func contributorEmailToOrgAdmin(adminEmail string, admin string, company string, projectNames []string, contributor *v1Models.User, corporateConsole string) { subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", company, getBestUserName(contributor)) recipients := []string{adminEmail} - body := fmt.Sprintf(` -

    Hello %s,

    -

    This is a notification email from EasyCLA regarding the project(s) %s.

    -

    The following contributor is requesting to sign CLA for organization:

    -

    %s

    -

    Before the user contribution can be accepted, your organization must sign a CLA. -

    Kindly login to this portal %s and sign the CLA for any of the projects %s.

    -

    Please notify the contributor once they are added so that they may complete the contribution process.

    -%s -%s`, - admin, projectNames, getFormattedUserDetails(contributor), corporateConsole, projectNames, - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate( + utils.V2, emails.V2ContributorToOrgAdminTemplateName, + emails.V2ContributorToOrgAdminTemplate, + emails.V2ContributorToOrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: admin, + CompanyName: company, + }, + ProjectNames: projectNames, + UserDetails: getFormattedUserDetails(contributor), + CorporateConsole: corporateConsole, + }, + ) + if err != nil { + log.Warnf("rendering template : %s failed : %v", emails.V2ContributorToOrgAdminTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -1287,7 +1293,7 @@ func contributorEmailToOrgAdmin(adminEmail string, admin string, company string, } } -func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, corporateConsole string, companyName string, projectNames []string, designeeEmail string, designeeName string, senderEmail string, senderName string) { +func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, corporateConsole string, companyName string, projectSFID string, projectNames []string, designeeEmail string, designeeName string, senderEmail string, senderName string) { f := logrus.Fields{ "functionName": "cla_manager.service.sendEmailToCLAManagerDesigneeCorporate", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1302,22 +1308,22 @@ func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, corporateConsol subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) recipients := []string{designeeEmail} - projectList := projectsStrList(projectNames) - body := fmt.Sprintf(` -

    Hello %s,

    -

    This is a notification email from EasyCLA regarding the CLA setup and signing process for %s.

    -

    %s %s has identified you as a potential candidate to setup the Corporate CLA for %s in support of the following projects:

    -%s -

    Before the contribution can be accepted, your organization must sign a CLA. -Either you or someone whom to designate from your company can login to this portal (%s) and sign the CLA for this project %s

    -

    If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

    -

    Please notify the user once CLA setup is complete.

    -%s -%s`, - designeeName, companyName, senderName, senderEmail, companyName, projectList, corporateConsole, projectNames[0], - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(repository, projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: designeeName, + CompanyName: companyName, + ProjectName: projectNames[0], + }, + SenderName: senderName, + SenderEmail: senderEmail, + ProjectList: projectNames, + CorporateConsole: corporateConsole, + }) + if err != nil { + log.Warnf("rendering template : %s : failed: %v", emails.V2CLAManagerDesigneeCorporateTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -1341,20 +1347,22 @@ func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", companyName, contributorID) recipients := []string{designeeEmail} - body := fmt.Sprintf(` -

    Hello %s,

    -

    This is a notification email from EasyCLA regarding the project(s) %s.

    -

    The following contributor is requesting to sign CLA for organization:

    -

    %s (%s)

    -

    Before the user contribution can be accepted, your organization must sign a CLA. -

    Kindly login to this portal %s and sign the CLA for one of the project(s) %s.

    -

    Please notify the contributor once they are added so that they may complete the contribution process.

    -%s -%s`, - designeeName, projectNames, contributorID, contributorName, corporateConsole, projectNames, - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) - - err := utils.SendEmail(subject, body, recipients) + body, err := emails.RenderTemplate(utils.V2, + emails.V2ToCLAManagerDesigneeTemplateName, + emails.V2ToCLAManagerDesigneeTemplate, + emails.V2ToCLAManagerDesigneeTemplateParams{ + RecipientName: designeeName, + ProjectNames: projectNames, + ContributorID: contributorID, + ContributorName: contributorName, + CorporateConsole: corporateConsole, + }) + + if err != nil { + log.Warnf("rendering template : %s failed : %v", emails.V2ToCLAManagerDesigneeTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) if err != nil { log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { @@ -1362,7 +1370,7 @@ func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, } } -func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string) error { +func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string) error { f := logrus.Fields{ "functionName": "cla_manager.service.sendDesigneeEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1374,19 +1382,22 @@ func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, requesterUsername, r } subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager for project: %s ", projectName) - body := fmt.Sprintf(` -

    Hello %s,

    -

    User %s (%s) was trying to add you as a CLA Manager for Project %s and Company %s but was unable to identify your account details in the EasyCLA system

    -

    This email will guide you to completing the CLA Manager role assignment

    -

    1. Accept Invite link below will take you SSO login page where you can login with your LF Login or create a LF Login and then login.

    -

    2. After logging in SSO screen should direct you to CLA Corporate Console page where you will see the project you a re associated with.

    -

    3. Click on workflow steps to complete the signup process. Please follow this documentation to help you guide through the process - https://docs.linuxfoundation.org/lfx/v/v2/easycla/corporate-cla-manager-designee-or-initial-cla-manager/sign-corporate-cla-for-a-company

    -

    4. Once you have completed CLA Manager workflow you will be able to manage the approved list of contributors

    -

    Accept Invite

    - %s - %s - `, userWithNoLFIDName, requesterUsername, requesterEmail, projectName, organizationName, - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) + body, err := emails.RenderV2DesigneeToUserWithNoLFIDTemplate(repository, *projectID, + emails.V2DesigneeToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: userWithNoLFIDName, + ProjectName: projectName, + CompanyName: organizationName, + }, + RequesterUserName: requesterUsername, + RequesterEmail: requesterEmail, + }) + + if err != nil { + log.Warnf("rendering template : %s failed : %v", emails.V2DesigneeToUserWithNoLFIDTemplateName, err) + return err + } + acsClient := v2AcsService.GetClient() automate := false log.WithFields(f).Debug("sending user invite request...") @@ -1395,7 +1406,7 @@ func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, requesterUsername, r } // sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID -func sendEmailToUserWithNoLFID(ctx context.Context, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error { +func sendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error { f := logrus.Fields{ "functionName": "cla_manager.service.sendEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1411,20 +1422,12 @@ func sendEmailToUserWithNoLFID(ctx context.Context, projectName, requesterUserna // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", role) - body := fmt.Sprintf(` -

    Hello %s,

    -

    This is a notification email from EasyCLA regarding the Project %s in the EasyCLA system.

    -

    User %s (%s) was trying to add you as a CLA Manager for Project %s but was unable to identify your account details in -the EasyCLA system. In order to become a CLA Manager for Project %s, you will need to accept invite below. -Once complete, notify the user %s and they will be able to add you as a CLA Manager.

    -

    Accept Invite

    -%s -%s`, - userWithNoLFIDName, projectName, - requesterUsername, requesterEmail, projectName, projectName, - requesterUsername, - utils.GetEmailHelpContent(true), utils.GetEmailSignOffContent()) + body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(repository, userWithNoLFIDName, projectName, *projectID, requesterUsername, requesterEmail) + if err != nil { + log.Warnf("rendering email : %s failed : %v", emails.V2CLAManagerToUserWithNoLFIDTemplateName, err) + return err + } acsClient := v2AcsService.GetClient() automate := false From 63c75435f76c087626f06c904a33d05bbea8e883 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 10 Feb 2021 00:59:28 +0300 Subject: [PATCH 0061/1276] [#2151] Bug/Enroll Project(CLA Manager permission) (#2601) - Resolved updating cla manager permission for newly enrolled SFProject event Signed-off-by: wanyaland --- .../v2/dynamo_events/projects_cla_groups.go | 4 ++-- cla-backend-go/v2/user-service/client.go | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index 8270cdc9e..cfdabd935 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -562,7 +562,7 @@ func (s *service) addCLAManagerPermissions(ctx context.Context, claGroupID, proj signatureUserModel.LfUsername, utils.CLAManagerRole) return } - if userModel == nil || userModel.ID == "" || userModel.Email == nil { + if userModel == nil || userModel.ID == "" || userClient.GetPrimaryEmail(userModel) == "" { log.WithFields(f).Warnf("unable to lookup user %s - user object is empty or missing either the ID or email - skipping %s role review/assigment for project: %s, company: %s", signatureUserModel.LfUsername, utils.CLAManagerRole, projectSFID, companySFID) return @@ -586,7 +586,7 @@ func (s *service) addCLAManagerPermissions(ctx context.Context, claGroupID, proj // Finally....assign the role to this user log.WithFields(f).Debugf("assiging role: %s to user %s/%s/%s for project: %s, company: %s...", - utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, *userModel.Email, projectSFID, companySFID) + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, userClient.GetPrimaryEmail(userModel), projectSFID, companySFID) roleErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, utils.StringValue(userModel.Email), projectSFID, companySFID, claManagerRoleID) if roleErr != nil { log.WithFields(f).WithError(roleErr).Warnf("%s, role assignment for user user %s/%s/%s failed for this project: %s, company: %s", diff --git a/cla-backend-go/v2/user-service/client.go b/cla-backend-go/v2/user-service/client.go index c96bfd973..6eb395f54 100644 --- a/cla-backend-go/v2/user-service/client.go +++ b/cla-backend-go/v2/user-service/client.go @@ -381,3 +381,18 @@ func (usc *Client) UpdateUserAccount(userSFID string, orgID string) error { log.WithFields(f).Debugf("successfully updated user: %s", result) return nil } + +// GetPrimaryEmail gets user primary email +func (usc *Client) GetPrimaryEmail(user *models.User) string { + f := logrus.Fields{ + "functionName": "GetPrimaryEmail", + } + primaryEmail := "" + for _, email := range user.Emails { + if *email.IsPrimary { + log.WithFields(f).Debugf("Found primary email : %s ", *email.EmailAddress) + primaryEmail = *email.EmailAddress + } + } + return primaryEmail +} From a5bfa246c897e249c56597aeee708e0b01420342 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 10 Feb 2021 02:24:44 +0300 Subject: [PATCH 0062/1276] [#2600] Bug/ Add|Update Repository (#2605) - Resolved issue raised on reenabling repository Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/repositories/repository.go | 120 ++++++++++++++++++ .../common/github-repository-input.yaml | 3 + cla-backend-go/v2/repositories/service.go | 20 +++ 4 files changed, 144 insertions(+) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index a751a0aa4..5555622a3 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -68,6 +68,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/communitybridge/easycla v1.0.99 h1:PkmkMV7cLH2Q2YNSFiGGmlyrHBXVYdsWMwbXNuMAyqw= github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9udsPZk8oLoQ= github.com/communitybridge/easycla v1.0.107 h1:dktHAji1yJ1nMEu54z4paPWOM4Q7A9rryc0OCADfAcY= +github.com/communitybridge/easycla v1.0.117 h1:o+rdmcNgZeMQ/N8HV/d5apNIBrkYH7eyM9UUYnEzewo= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 738d994fa..293f807e0 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -44,9 +44,13 @@ const ( RepositoryNameIndex = "repository-name-index" ) +// ErrRepositoryDoesNotExist ... +var ErrRepositoryDoesNotExist = errors.New("repository does not exist") + // Repository defines functions of Repositories type Repository interface { AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) + UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error EnableRepository(ctx context.Context, repositoryID string) error EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error @@ -142,6 +146,122 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, return repository.toModel(), nil } +// UpdateGithubRepository updates the repository record for given ID +func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { + + externalID := utils.StringValue(input.RepositoryExternalID) + projectSFID := utils.StringValue(input.RepositoryProjectID) + repositoryName := utils.StringValue(input.RepositoryName) + repositoryOrganizationName := utils.StringValue(input.RepositoryOrganizationName) + repositoryType := utils.StringValue(input.RepositoryType) + repositoryURL := utils.StringValue(input.RepositoryURL) + + f := logrus.Fields{ + "functionName": "repositories.repository.UpdateGitHubRepository", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "repositoryID": repositoryID, + "externalProjectID": externalID, + "projectSFID": projectSFID, + "repositoryName": repositoryName, + "repositoryOrganizationName": repositoryOrganizationName, + "repositoryType": repositoryType, + "repositoryURL": repositoryURL, + } + + log.WithFields(f).Debugf("updating Repository : %s... ", repositoryID) + + repoModel, repoErr := r.GetRepository(ctx, repositoryID) + if repoErr != nil { + log.WithFields(f).Warnf("update error locating the repository ID : %s , error: %+v ", repositoryID, repoErr) + return nil, repoErr + } + + if repoModel == nil { + log.WithFields(f).Warnf("Repository does not exist for repo: %s ", repositoryID) + return nil, ErrRepositoryDoesNotExist + } + + expressionAttributeNames := map[string]*string{} + expressionAttributeValues := map[string]*dynamodb.AttributeValue{} + updateExpression := "SET " + + if projectSFID != "" && repoModel.ProjectSFID != projectSFID { + log.WithFields(f).Debugf("adding projectSFID : %s ", projectSFID) + expressionAttributeNames["#P"] = aws.String("project_sfid") + expressionAttributeValues[":p"] = &dynamodb.AttributeValue{S: aws.String(projectSFID)} + updateExpression = updateExpression + " #P = :p, " + } + + if externalID != "" && repoModel.RepositoryExternalID != externalID { + log.WithFields(f).Debugf("adding externalID : %s ", externalID) + expressionAttributeNames["#E"] = aws.String("repository_external_id") + expressionAttributeValues[":e"] = &dynamodb.AttributeValue{S: aws.String(externalID)} + updateExpression = updateExpression + " #E = :e, " + } + + if repositoryName != "" && repoModel.RepositoryName != repositoryName { + log.WithFields(f).Debugf("adding repositoryName : %s ", repositoryName) + expressionAttributeNames["#N"] = aws.String("repository_name") + expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(repositoryName)} + updateExpression = updateExpression + " #N = :n, " + } + + if repositoryOrganizationName != "" && repoModel.RepositoryOrganizationName != repositoryOrganizationName { + log.WithFields(f).Debugf("adding repositoryOrganizationName : %s ", repositoryOrganizationName) + expressionAttributeNames["#O"] = aws.String("repository_organization_name") + expressionAttributeValues[":o"] = &dynamodb.AttributeValue{S: aws.String(repositoryOrganizationName)} + updateExpression = updateExpression + " #O = :o, " + } + + if repositoryType != "" && repoModel.RepositoryType != repositoryType { + log.WithFields(f).Debugf("adding repositoryType : %s ", repositoryType) + expressionAttributeNames["#T"] = aws.String("repository_type") + expressionAttributeValues[":t"] = &dynamodb.AttributeValue{S: aws.String(repositoryType)} + updateExpression = updateExpression + " #T = :t, " + } + + if repositoryURL != "" && repoModel.RepositoryURL != repositoryURL { + log.WithFields(f).Debugf("adding repositoryURL : %s ", repositoryURL) + expressionAttributeNames["#U"] = aws.String("repository_url") + expressionAttributeValues[":u"] = &dynamodb.AttributeValue{S: aws.String(repositoryURL)} + updateExpression = updateExpression + " #U = :u, " + } + + if input.Enabled != nil && repoModel.Enabled != *input.Enabled { + log.WithFields(f).Debugf("adding enabled flag: %+v", *input.Enabled) + expressionAttributeNames["#EN"] = aws.String("enabled") + expressionAttributeValues[":en"] = &dynamodb.AttributeValue{BOOL: input.Enabled} + updateExpression = updateExpression + " #EN = :en, " + } + + _, currentTimeString := utils.CurrentTime() + log.WithFields(f).Debugf("adding date_modified: %s", currentTimeString) + expressionAttributeNames["#M"] = aws.String("date_modified") + expressionAttributeValues[":m"] = &dynamodb.AttributeValue{S: aws.String(currentTimeString)} + updateExpression = updateExpression + " #M = :m " + + // Assemble the query input parameters + updateInput := &dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "repository_id": { + S: aws.String(repositoryID), + }, + }, + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + UpdateExpression: &updateExpression, + TableName: aws.String(r.repositoryTableName), + } + + _, updateErr := r.dynamoDBClient.UpdateItem(updateInput) + if updateErr != nil { + log.WithFields(f).Warnf("error updatingRepository by repositoryID: %s, error: %v", repositoryID, updateErr) + return nil, updateErr + } + + return r.GetRepository(ctx, repositoryID) +} + // UpdateClaGroupID updates the claGroupID of the repository func (r *repo) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { return r.setClaGroupIDGithubRepository(ctx, repositoryID, claGroupID) diff --git a/cla-backend-go/swagger/common/github-repository-input.yaml b/cla-backend-go/swagger/common/github-repository-input.yaml index 3f421679e..524277ee6 100644 --- a/cla-backend-go/swagger/common/github-repository-input.yaml +++ b/cla-backend-go/swagger/common/github-repository-input.yaml @@ -22,3 +22,6 @@ properties: type: string repositoryUrl: type: string + enabled: + type: boolean + default: true diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index fb940872e..ae5a3eb3c 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -9,6 +9,7 @@ import ( "fmt" "strconv" + "github.com/jinzhu/copier" "github.com/sirupsen/logrus" "github.com/go-openapi/swag" @@ -138,6 +139,25 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i } // We already have an existing repository model with the same name + if existingRepositoryModel != nil && !existingRepositoryModel.Enabled { + msg := fmt.Sprintf("Github repository : %s disabled and shall get re-enabled... ", utils.StringValue(ghRepo.FullName)) + log.WithFields(f).Debug(msg) + var v1Input v1Models.GithubRepositoryInput + err := copier.Copy(&v1Input, &input) + if err != nil { + log.WithFields(f).Error("unable to create v1GithubRepository input") + return nil, err + } + // Enabled repository + *v1Input.Enabled = true + // Update Repo details in case of any changes + updatedRepository, updateErr := s.repo.UpdateGithubRepository(ctx, existingRepositoryModel.RepositoryID, &v1Input) + if updateErr != nil { + return nil, updateErr + } + return updatedRepository, nil + } + if existingRepositoryModel != nil { msg := fmt.Sprintf("GitHub repository already exists with repository name: %s", utils.StringValue(ghRepo.FullName)) log.WithFields(f).Warn(msg) From e790ad277fecebf98381bd394d35171b1ebeb773 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 9 Feb 2021 18:43:39 -0500 Subject: [PATCH 0063/1276] Added Debug for User Subscribe Lambda (#2606) --- .github/workflows/snyk.yml | 57 +++++--- cla-backend-go/cla_manager/handlers.go | 157 +++++++++++++-------- cla-backend-go/userSubscribeLambda/main.go | 30 ++-- cla-backend-go/utils/constants.go | 8 +- cla-backend-go/utils/responses.go | 18 +++ cla-backend/cla/models/dynamo_models.py | 60 +++++--- cla-backend/serverless.yml | 4 +- 7 files changed, 223 insertions(+), 111 deletions(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 618f845af..388e28b4d 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -5,28 +5,39 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - - name: Run Snyk to check for vulnerabilities (Node) - uses: snyk/actions/node@master - continue-on-error: true +# - name: Run Snyk to check for vulnerabilities (Node) +# uses: snyk/actions/node@master +# continue-on-error: true +# env: +# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} +# with: +# command: monitor +# - name: Run Snyk to check for vulnerabilities (Golang) +# uses: snyk/actions/golang@master +# continue-on-error: true +# env: +# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} +# with: +# command: monitor + - name: Install dependencies (cla-backend) + run: | + python -m pip install --upgrade pip wheel + pip install -r cla-backend/requirements.txt + - uses: snyk/actions/setup@master + - name: Run Snyk to check for vulnerabilities (all) env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - with: - command: monitor - - name: Run Snyk to check for vulnerabilities (Golang) - uses: snyk/actions/golang@master - continue-on-error: true - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - with: - command: monitor - - name: Run Snyk to check for vulnerabilities (Python) - uses: snyk/actions/python@master - continue-on-error: true - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - with: - command: monitor - - name: Upload result to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@v1 - with: - sarif_file: snyk.sarif + run: | + snyk monitor --detection-depth=99 --dev --all-projects --org=${{ secrets.SNYK_ORG }} + +# - name: Run Snyk to check for vulnerabilities (Python) +# uses: snyk/actions/python@master +# continue-on-error: true +# env: +# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} +# with: +# command: monitor +# - name: Upload result to GitHub Code Scanning +# uses: github/codeql-action/upload-sarif@v1 +# with: +# sarif_file: snyk.sarif diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index d66f65225..c39a6baa8 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -7,6 +7,9 @@ import ( "context" "fmt" + user_service "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/cla_manager" @@ -254,10 +257,18 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "cla_manager.handler.ClaManagerApproveCLAManagerRequestHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "CompanyID": params.CompanyID, + "RequestID": params.RequestID, + } + companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) if companyErr != nil || companyModel == nil { msg := buildErrorMessageForApprove(params, companyErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewCreateCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -267,7 +278,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. claGroupModel, projectErr := projectService.GetCLAGroupByID(ctx, params.ProjectID) if projectErr != nil || claGroupModel == nil { msg := buildErrorMessageForApprove(params, projectErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewCreateCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -284,14 +295,14 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. }) if sigErr != nil || sigModels == nil { msg := buildErrorMessageForApprove(params, sigErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewApproveCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "CLA Manager Approve Request - error reading CCLA Signatures - " + msg, Code: "400", }) } if len(sigModels.Signatures) > 1 { - log.Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s for request ID: %s", + log.WithFields(f).Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s for request ID: %s", params.CompanyID, params.ProjectID, params.RequestID) } @@ -309,7 +320,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. request, err := service.ApproveRequest(params.CompanyID, params.ProjectID, params.RequestID) if err != nil { msg := buildErrorMessageForApprove(params, err) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewApproveCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -320,7 +331,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. _, aclErr := sigService.AddCLAManager(ctx, sigModel.SignatureID, request.UserID) if aclErr != nil { msg := buildErrorMessageForApprove(params, aclErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewApproveCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -360,11 +371,18 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. api.ClaManagerDenyCLAManagerRequestHandler = cla_manager.DenyCLAManagerRequestHandlerFunc(func(params cla_manager.DenyCLAManagerRequestParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "cla_manager.handler.ClaManagerDenyCLAManagerRequestHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "CompanyID": params.CompanyID, + "RequestID": params.RequestID, + } companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) if companyErr != nil || companyModel == nil { msg := buildErrorMessageForDeny(params, companyErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDenyCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -374,7 +392,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. claGroupModel, projectErr := projectService.GetCLAGroupByID(ctx, params.ProjectID) if projectErr != nil || claGroupModel == nil { msg := buildErrorMessageForDeny(params, projectErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDenyCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -391,14 +409,14 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. }) if sigErr != nil || sigModels == nil { msg := buildErrorMessageForDeny(params, sigErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewApproveCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: "CLA Manager Deny Request - error reading CCLA Signatures - " + msg, Code: "400", }) } if len(sigModels.Signatures) > 1 { - log.Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s for request ID: %s", + log.WithFields(f).Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s for request ID: %s", params.CompanyID, params.ProjectID, params.RequestID) } @@ -415,7 +433,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. request, err := service.DenyRequest(params.CompanyID, params.ProjectID, params.RequestID) if err != nil { msg := buildErrorMessageForDeny(params, err) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDenyCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -455,6 +473,13 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. api.ClaManagerDeleteCLAManagerRequestHandler = cla_manager.DeleteCLAManagerRequestHandlerFunc(func(params cla_manager.DeleteCLAManagerRequestParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "cla_manager.handler.ClaManagerDeleteCLAManagerRequestHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "CompanyID": params.CompanyID, + "RequestID": params.RequestID, + } // Make sure the company id exists... companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) @@ -470,7 +495,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. claGroupModel, projectErr := projectService.GetCLAGroupByID(ctx, params.ProjectID) if projectErr != nil || claGroupModel == nil { msg := buildErrorMessageForDelete(params, projectErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDenyCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -481,7 +506,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. request, err := service.GetRequest(params.RequestID) if err != nil { msg := buildErrorMessageForDelete(params, err) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDeleteCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -490,7 +515,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. if request == nil { msg := buildErrorMessageForDelete(params, err) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDeleteCLAManagerRequestNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "404", @@ -507,14 +532,16 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. }) if sigErr != nil || sigModels == nil { msg := buildErrorMessageForDelete(params, sigErr) - log.Warn(msg) - return cla_manager.NewDeleteCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - CLA Manager Delete Request - error reading CCLA Signatures - " + msg, - Code: "400", - }) + log.WithFields(f).Warn(msg) + return cla_manager.NewDeleteCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse( + utils.ErrorResponseBadRequest( + reqID, + "CLA Manager Delete Request - error reading CCLA Signatures - "+msg, + ))) } if len(sigModels.Signatures) > 1 { - log.Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s for request ID: %s", + log.WithFields(f).Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s for request ID: %s", params.CompanyID, params.ProjectID, params.RequestID) } @@ -523,7 +550,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. if !currentUserInACL(claUser, claManagers) { msg := fmt.Sprintf("EasyCLA - 401 Unauthorized - CLA Manager %s / %s / %s not authorized to delete requests for company ID: %s, project ID: %s", claUser.UserID, claUser.Name, claUser.LFEmail, params.CompanyID, params.ProjectID) - log.Debug(msg) + log.WithFields(f).Debug(msg) return cla_manager.NewDeleteCLAManagerRequestUnauthorized().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "401", @@ -534,7 +561,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. deleteErr := service.DeleteRequest(params.RequestID) if deleteErr != nil { msg := buildErrorMessageForDelete(params, deleteErr) - log.Warn(msg) + log.WithFields(f).Warn(msg) return cla_manager.NewDeleteCLAManagerRequestBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Message: msg, Code: "400", @@ -558,41 +585,50 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. }, }) - log.Debug("CLA Manager Delete - Returning Success") + log.WithFields(f).Debug("CLA Manager Delete - Returning Success") return cla_manager.NewDeleteCLAManagerRequestNoContent().WithXRequestID(reqID) }) api.ClaManagerAddCLAManagerHandler = cla_manager.AddCLAManagerHandlerFunc(func(params cla_manager.AddCLAManagerParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "cla_manager.handler.ClaManagerAddCLAManagerHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "CompanyID": params.CompanyID, + "UserLFID": params.Body.UserLFID, + "UserEmail": params.Body.UserEmail, + "UserName": params.Body.UserName, + } + log.WithFields(f).Debug("looking up user by user id...") userModel, userErr := usersService.GetUserByLFUserName(params.Body.UserLFID) if userErr != nil || userModel == nil { - msg := fmt.Sprintf("User lookup for user by LFID: %s failed ", params.Body.UserLFID) - log.Warn(msg) - return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - Add CLA Manager - error getting user - " + msg, - Code: "400", - }) + log.WithFields(f).Warnf("Add CLA Manager - user lookup by LFID: %s failed - attempting to lookup in SF...", params.Body.UserLFID) + userServiceClient := user_service.GetClient() + sfdcUserObject, userServiceLookupErr := userServiceClient.GetUserByUsername(params.Body.UserLFID) + if userServiceLookupErr != nil || sfdcUserObject == nil { + msg := fmt.Sprintf("Add CLA Manager - user lookup by LFID: %s failed ", params.Body.UserLFID) + log.WithFields(f).Warn(msg) + return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) + } } companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) if companyErr != nil || companyModel == nil { - msg := fmt.Sprintf("User lookup for company by ID: %s failed ", params.CompanyID) + msg := fmt.Sprintf("Add CLA Manager - error getting company by ID: %s failed ", params.CompanyID) log.Warn(msg) - return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - Add CLA Manager - error getting company - " + msg, - Code: "400", - }) + return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) } claGroupModel, projectErr := projectService.GetCLAGroupByID(ctx, params.ProjectID) if projectErr != nil || claGroupModel == nil { - msg := fmt.Sprintf("User lookup for project by ID: %s failed ", params.ProjectID) + msg := fmt.Sprintf("Add CLA Manager - error getting project - lookup for project by ID: %s failed ", params.ProjectID) log.Warn(msg) - return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - Add CLA Manager - error getting project - " + msg, - Code: "400", - }) + return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) } // Look up signature ACL to ensure the user is allowed to add CLA managers @@ -606,10 +642,8 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. if sigErr != nil || sigModels == nil { msg := buildErrorMessageAddManager("Add CLA Manager - signature lookup error", params, sigErr) log.Warn(msg) - return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - Add CLA Manager - error reading CCLA Signatures - " + msg, - Code: "400", - }) + return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) } if len(sigModels.Signatures) > 1 { log.Warnf("returned multiple CCLA signature models for company ID: %s, project ID: %s", @@ -622,10 +656,8 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. msg := fmt.Sprintf("EasyCLA - 401 Unauthorized - User %s / %s / %s is not authorized to add a CLA Manager for company ID: %s, project ID: %s", claUser.UserID, claUser.Name, claUser.LFEmail, params.CompanyID, params.ProjectID) log.Debug(msg) - return cla_manager.NewAddCLAManagerUnauthorized().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: msg, - Code: "401", - }) + return cla_manager.NewAddCLAManagerUnauthorized().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseUnauthorized(reqID, msg))) } // Audit Event sent from service upon success @@ -633,10 +665,8 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. if addErr != nil { msg := buildErrorMessageAddManager("Add CLA Manager - Service Error", params, addErr) log.Warn(msg) - return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: msg, - Code: "400", - }) + return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) } return cla_manager.NewAddCLAManagerOK().WithXRequestID(reqID).WithPayload(signature) @@ -646,16 +676,29 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. api.ClaManagerDeleteCLAManagerHandler = cla_manager.DeleteCLAManagerHandlerFunc(func(params cla_manager.DeleteCLAManagerParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "cla_manager.handler.ClaManagerDeleteCLAManagerHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "CompanyID": params.CompanyID, + "UserLFID": params.UserLFID, + } + + log.WithFields(f).Debug("looking up user by user id...") userModel, userErr := usersService.GetUserByLFUserName(params.UserLFID) if userErr != nil || userModel == nil { - msg := fmt.Sprintf("User lookup for user by LFID: %s failed ", params.UserLFID) - log.Warn(msg) - return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - Delete CLA Manager - error getting user - " + msg, - Code: "400", - }) + log.WithFields(f).Warnf("user lookup by LFID: %s failed - attempting to lookup in SF...", params.UserLFID) + userServiceClient := user_service.GetClient() + sfdcUserObject, userServiceLookupErr := userServiceClient.GetUserByUsername(params.UserLFID) + if userServiceLookupErr != nil || sfdcUserObject == nil { + msg := fmt.Sprintf("Delete CLA Manager - user lookup by LFID: %s failed ", params.UserLFID) + log.WithFields(f).Warn(msg) + return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) + } } + companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) if companyErr != nil || companyModel == nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed ", params.CompanyID) diff --git a/cla-backend-go/userSubscribeLambda/main.go b/cla-backend-go/userSubscribeLambda/main.go index f2699e77f..dec549704 100644 --- a/cla-backend-go/userSubscribeLambda/main.go +++ b/cla-backend-go/userSubscribeLambda/main.go @@ -61,7 +61,8 @@ func init() { // Handler is the user subscribe handler lambda entry function func Handler(ctx context.Context, snsEvent events.SNSEvent) error { f := logrus.Fields{ - "functionName": "userSubscribeLambda.main.Handler", + "functionName": "userSubscribeLambda.main.Handler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } if len(snsEvent.Records) == 0 { log.WithFields(f).Warn("SNS event contained 0 records - ignoring message.") @@ -79,14 +80,17 @@ func Handler(ctx context.Context, snsEvent events.SNSEvent) error { return err } - log.WithFields(f).Debugf("Processing model.Type: %s", model.Type) + f["modelType"] = model.Type + log.WithFields(f).Debugf("Processing message type: %s", model.Type) switch model.Type { case "UserSignedUp": - log.WithFields(f).Debugf("Detected model.Type: %s - processing...", model.Type) - Create(model) + log.WithFields(f).Debugf("Detected message type: %s - processing...", model.Type) + Create(ctx, model) case "UserUpdatedProfile": - log.WithFields(f).Debugf("Detected model.Type: %s - processing...", model.Type) - Update(model) + log.WithFields(f).Debugf("Detected message type: %s - processing...", model.Type) + Update(ctx, model) + case "UserAuthenticated": + log.WithFields(f).Debugf("Ignoring message type: %s", model.Type) default: log.WithFields(f).Warnf("unrecognized message type: %s - unable to process message ", model.Type) } @@ -96,9 +100,10 @@ func Handler(ctx context.Context, snsEvent events.SNSEvent) error { } // Create saves the user data model to persistent storage -func Create(user event.Event) { +func Create(ctx context.Context, user event.Event) { f := logrus.Fields{ - "functionName": "userSubscribeLambda.main.Create", + "functionName": "userSubscribeLambda.main.Create", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } uc := &usersModels.UserCreated{} @@ -118,6 +123,7 @@ func Create(user event.Event) { } usersRepo := users.NewRepository(awsSession, stage) + log.WithFields(f).Debugf("locating user by username: %s in EasyCLA's database...", uc.Username) userDetails, userErr = usersRepo.GetUserByLFUserName(uc.Username) if userErr != nil { log.WithFields(f).WithError(userErr).Warnf("unable to locate user by LfUsername: %s", uc.Username) @@ -128,9 +134,10 @@ func Create(user event.Event) { } userServiceClient := user_service.GetClient() + log.WithFields(f).Debugf("locating user by username: %s in the user service...", uc.Username) sfdcUserObject, err := userServiceClient.GetUserByUsername(uc.Username) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to locate user by SFID: %s", uc.Username) + log.WithFields(f).WithError(err).Warnf("unable to locate user by username: %s", uc.Username) return } if sfdcUserObject == nil { @@ -173,9 +180,10 @@ func Create(user event.Event) { } // Update saves the user data model to persistent storage -func Update(user event.Event) { +func Update(ctx context.Context, user event.Event) { f := logrus.Fields{ - "functionName": "userSubscribeLambda.main.Update", + "functionName": "userSubscribeLambda.main.Update", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } uc := &usersModels.UserUpdated{} diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index fab25e9b9..edb04e560 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -6,7 +6,10 @@ package utils // String400 string version of 400 - http bad request const String400 = "400" -// String403 string version of 403 - http not authorized +// String401 string version of 401 - http unauthorized +const String401 = "401" + +// String403 string version of 403 - http forbidden const String403 = "403" // String404 string version of 404 - http not found @@ -21,6 +24,9 @@ const String500 = "500" // EasyCLA400BadRequest common string for handler bad request error messages const EasyCLA400BadRequest = "EasyCLA - 400 Bad Request" +// EasyCLA401Unauthorized common string for handler unauthorized error messages +const EasyCLA401Unauthorized = "EasyCLA - 401 Unauthorized" + // EasyCLA403Forbidden common string for handler forbidden error messages const EasyCLA403Forbidden = "EasyCLA - 403 Forbidden" diff --git a/cla-backend-go/utils/responses.go b/cla-backend-go/utils/responses.go index fd8ac3add..c8c699a8c 100644 --- a/cla-backend-go/utils/responses.go +++ b/cla-backend-go/utils/responses.go @@ -37,6 +37,24 @@ func ErrorResponseBadRequestWithError(reqID, msg string, err error) *models.Erro } } +// ErrorResponseUnauthorized Helper function to generate an unauthorized error response +func ErrorResponseUnauthorized(reqID, msg string) *models.ErrorResponse { + return &models.ErrorResponse{ + Code: String401, + Message: fmt.Sprintf("%s - %s", EasyCLA401Unauthorized, msg), + XRequestID: reqID, + } +} + +// ErrorResponseUnauthorizedWithError Helper function to generate an unauthorized error response +func ErrorResponseUnauthorizedWithError(reqID, msg string, err error) *models.ErrorResponse { + return &models.ErrorResponse{ + Code: String401, + Message: fmt.Sprintf("%s - %s - error: %+v", EasyCLA401Unauthorized, msg, err), + XRequestID: reqID, + } +} + // ErrorResponseForbidden Helper function to generate a forbidden error response func ErrorResponseForbidden(reqID, msg string) *models.ErrorResponse { return &models.ErrorResponse{ diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index b7aa12a16..214c46a25 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -1103,7 +1103,7 @@ def to_dict(self): return project_dict - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -1529,7 +1529,7 @@ def log_warning(self, msg): """ cla.log.warning("{} for user: {}".format(msg, self)) - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -2030,7 +2030,7 @@ def __init__( def to_dict(self): return dict(self.model) - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -2433,7 +2433,7 @@ def to_dict(self): del d[k] return d - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -2960,18 +2960,28 @@ def get_managers_by_signature_acl(self, signature_acl): def get_managers(self): return self.get_managers_by_signature_acl(self.get_signature_acl()) - def all(self, ids=None): + def all(self, ids: str = None) -> List[Signature]: if ids is None: signatures = self.model.scan() else: signatures = SignatureModel.batch_get(ids) ret = [] for signature in signatures: - agr = Signature() - agr.model = signature - ret.append(agr) + sig = Signature() + sig.model = signature + ret.append(sig) return ret + def all_limit(self, limit: Optional[int] = None, last_evaluated_key: Optional[str] = None) -> \ + (List[Signature], str, int): + result_iterator = self.model.scan(limit=limit, last_evaluated_key=last_evaluated_key) + ret = [] + for signature in result_iterator: + sig = Signature() + sig.model = signature + ret.append(sig) + return ret, result_iterator.last_evaluated_key, result_iterator.total_count + class ProjectCLAGroupModel(BaseModel): """ @@ -3175,6 +3185,7 @@ class Meta: signing_entity_name_index = SigningEntityNameIndex() company_external_id_index = ExternalCompanyIndex() company_acl = UnicodeSetAttribute(default=set()) + note = UnicodeAttribute(null=True) class Company(model_interfaces.Company): # pylint: disable=too-many-public-methods @@ -3190,6 +3201,7 @@ def __init__( company_name=None, signing_entity_name=None, company_acl=None, + note=None, ): super(Company).__init__() self.model = CompanyModel() @@ -3202,6 +3214,7 @@ def __init__( else: self.model.signing_entity_name = company_name self.model.company_acl = company_acl + self.model.note = note def __str__(self) -> str: return ( @@ -3210,7 +3223,8 @@ def __str__(self) -> str: f"signing_entity_name: {self.model.signing_entity_name}, " f"external id: {self.model.company_external_id}, " f"manager id: {self.model.company_manager_id}, " - f"acl: {self.model.company_acl}" + f"acl: {self.model.company_acl}, " + f"note: {self.model.note}" ) def to_dict(self) -> dict: @@ -3254,13 +3268,16 @@ def get_company_name(self) -> str: return self.model.company_name def get_signing_entity_name(self) -> str: - if self.model.signing_entity_name is None: - return self.model.company_name + # if self.model.signing_entity_name is None: + # return self.model.company_name return self.model.signing_entity_name def get_company_acl(self) -> Optional[List[str]]: return self.model.company_acl + def get_note(self) -> str: + return self.model.note + def set_company_id(self, company_id: str) -> None: self.model.company_id = company_id @@ -3279,6 +3296,15 @@ def set_signing_entity_name(self, signing_entity_name: str) -> None: def set_company_acl(self, company_acl_username: str) -> None: self.model.company_acl = set([company_acl_username]) + def set_note(self, note: str) -> None: + self.model.note = note + + def update_note(self, note: str) -> None: + if self.model.note: + self.model.note = self.model.note + ' ' + note + else: + self.model.note = note + def set_date_modified(self) -> None: """ Updates the company modified date/time to the current time. @@ -3513,7 +3539,7 @@ def to_dict(self): ret["organization_sfid"] = None return ret - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -3740,7 +3766,7 @@ def set_group_name_icla(self, group_name_icla): def set_group_name_ccla(self, group_name_ccla): self.model.group_name_ccla = group_name_ccla - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -3942,7 +3968,7 @@ def set_user_email(self, user_email): def set_status(self, status): self.model.status = status - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -4002,7 +4028,7 @@ def to_dict(self): ret = dict(self.model) return ret - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -4092,7 +4118,7 @@ def get_invites_by_company(self, requested_company_id): invites.append(invite) return invites - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() @@ -4198,7 +4224,7 @@ def __str__(self): def to_dict(self): return dict(self.model) - def save(self): + def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index b44046948..478d8ee05 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -577,10 +577,10 @@ functions: # User Subscribe event for dynamodb cla-stage-users table. easyClaUserSubscribe: - name: easy-cla-user-subscribe + handler: user-subscribe-lambda + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-user-subscribe-lambda runtime: go1.x description: Update easycla user data to user object in dynamodb - handler: user-subscribe-lambda package: individually: true include: From 1e9f27c4b2904615348bbad074e18be4fc5c0800 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 10 Feb 2021 03:13:31 +0300 Subject: [PATCH 0064/1276] [#2151] Bug/Enroll Project (#2607) - Resolved email issue(create cla-manager role) when enrolling salesforce project Signed-off-by: wanyaland --- cla-backend-go/v2/dynamo_events/projects_cla_groups.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index cfdabd935..0bde09763 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -587,16 +587,16 @@ func (s *service) addCLAManagerPermissions(ctx context.Context, claGroupID, proj // Finally....assign the role to this user log.WithFields(f).Debugf("assiging role: %s to user %s/%s/%s for project: %s, company: %s...", utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, userClient.GetPrimaryEmail(userModel), projectSFID, companySFID) - roleErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, utils.StringValue(userModel.Email), projectSFID, companySFID, claManagerRoleID) + roleErr := orgClient.CreateOrgUserRoleOrgScopeProjectOrg(ctx, userClient.GetPrimaryEmail(userModel), projectSFID, companySFID, claManagerRoleID) if roleErr != nil { log.WithFields(f).WithError(roleErr).Warnf("%s, role assignment for user user %s/%s/%s failed for this project: %s, company: %s", - utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, utils.StringValue(userModel.Email), projectSFID, companySFID) + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, userClient.GetPrimaryEmail(userModel), projectSFID, companySFID) return } msg := fmt.Sprintf("assigned role: %s to user %s/%s/%s for project: %s with SFID:%s, company: %s with SFID: %s", - utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, utils.StringValue(userModel.Email), projectName, projectSFID, companyModel.CompanyName, companySFID) + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, userClient.GetPrimaryEmail(userModel), projectName, projectSFID, companyModel.CompanyName, companySFID) msgSummary := fmt.Sprintf("assigned role: %s to user %s/%s/%s for project: %s, company: %s", - utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, utils.StringValue(userModel.Email), projectName, companyModel.CompanyName) + utils.CLAManagerRole, signatureUserModel.LfUsername, userModel.ID, userClient.GetPrimaryEmail(userModel), projectName, companyModel.CompanyName) log.WithFields(f).Debug(msg) // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ From 9fe034c7f8c6d216662fbeb12c8995f409d7d876 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 9 Feb 2021 16:33:53 -0800 Subject: [PATCH 0065/1276] Updated Snyk Scan Setup Signed-off-by: David Deal --- .github/workflows/snyk.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 388e28b4d..7217cf1ce 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -3,6 +3,10 @@ on: push jobs: security: runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ 3.7 ] + steps: - uses: actions/checkout@master # - name: Run Snyk to check for vulnerabilities (Node) @@ -19,10 +23,23 @@ jobs: # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # with: # command: monitor + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} - name: Install dependencies (cla-backend) + uses: snyk/actions/python@master run: | + cd cla-backend python -m pip install --upgrade pip wheel - pip install -r cla-backend/requirements.txt + pip install -r requirements.txt + - name: Install dependencies (cla-backend-go) + uses: actions/setup-go@v2 + with: + go-version: '1.14.1' + run: | + cd cla-backend-go + make setup deps - uses: snyk/actions/setup@master - name: Run Snyk to check for vulnerabilities (all) env: From 6eb5a01fc7254caf065d3750558360e4276e065c Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 9 Feb 2021 16:39:17 -0800 Subject: [PATCH 0066/1276] Testing Snyk Setup Signed-off-by: David Deal --- .github/workflows/snyk.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 7217cf1ce..4a4515b94 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -28,18 +28,18 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install dependencies (cla-backend) - uses: snyk/actions/python@master run: | cd cla-backend python -m pip install --upgrade pip wheel pip install -r requirements.txt - - name: Install dependencies (cla-backend-go) + - name: Install Go uses: actions/setup-go@v2 with: - go-version: '1.14.1' + go-version: '1.15.7' + - name: Install Dependencies (cla-backend-go) run: | cd cla-backend-go - make setup deps + go mod download - uses: snyk/actions/setup@master - name: Run Snyk to check for vulnerabilities (all) env: From af39ef34f372ff66c58c5937f482ae01a28c7ffc Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 9 Feb 2021 20:23:11 -0500 Subject: [PATCH 0067/1276] Resolved #2593 - Autocreate user from auth0 (#2608) * Resolved #2593 - Autocreate user from auth0 Signed-off-by: David Deal --- cla-backend-go/cla_manager/handlers.go | 45 +++++++++++++++++++++++- cla-backend-go/v2/user-service/client.go | 12 +++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index c39a6baa8..764e728c4 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -612,9 +612,31 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. msg := fmt.Sprintf("Add CLA Manager - user lookup by LFID: %s failed ", params.Body.UserLFID) log.WithFields(f).Warn(msg) return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( - utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequestWithError(reqID, msg, userServiceLookupErr))) + } + + _, nowStr := utils.CurrentTime() + userModel, userErr = usersService.CreateUser(&models.User{ + Admin: false, + DateCreated: nowStr, + DateModified: nowStr, + Emails: userServiceClient.EmailsToSlice(sfdcUserObject), + GithubUsername: sfdcUserObject.GithubID, //this is the github username + LfEmail: userServiceClient.GetPrimaryEmail(sfdcUserObject), + LfUsername: sfdcUserObject.Username, + Note: "created from SF record", + UserExternalID: sfdcUserObject.ID, + Username: sfdcUserObject.Username, + Version: "v1", + }, claUser) + if userErr != nil || userModel == nil { + msg := fmt.Sprintf("Add CLA Manager - user lookup by LFID: %s failed ", params.Body.UserLFID) + log.WithFields(f).Warn(msg) + return cla_manager.NewAddCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequestWithError(reqID, msg, userErr))) } } + companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) if companyErr != nil || companyModel == nil { msg := fmt.Sprintf("Add CLA Manager - error getting company by ID: %s failed ", params.CompanyID) @@ -697,6 +719,27 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( utils.ToV1ErrorResponse(utils.ErrorResponseBadRequest(reqID, msg))) } + + _, nowStr := utils.CurrentTime() + userModel, userErr = usersService.CreateUser(&models.User{ + Admin: false, + DateCreated: nowStr, + DateModified: nowStr, + Emails: userServiceClient.EmailsToSlice(sfdcUserObject), + GithubUsername: sfdcUserObject.GithubID, //this is the github username + LfEmail: userServiceClient.GetPrimaryEmail(sfdcUserObject), + LfUsername: sfdcUserObject.Username, + Note: "created from SF record", + UserExternalID: sfdcUserObject.ID, + Username: sfdcUserObject.Username, + Version: "v1", + }, claUser) + if userErr != nil || userModel == nil { + msg := fmt.Sprintf("Add CLA Manager - user lookup by LFID: %s failed ", params.UserLFID) + log.WithFields(f).Warn(msg) + return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseBadRequestWithError(reqID, msg, userErr))) + } } companyModel, companyErr := companyService.GetCompany(ctx, params.CompanyID) diff --git a/cla-backend-go/v2/user-service/client.go b/cla-backend-go/v2/user-service/client.go index 6eb395f54..45b95930f 100644 --- a/cla-backend-go/v2/user-service/client.go +++ b/cla-backend-go/v2/user-service/client.go @@ -396,3 +396,15 @@ func (usc *Client) GetPrimaryEmail(user *models.User) string { } return primaryEmail } + +// EmailsToSlice converts a user model's email addresses to a string slice +func (usc *Client) EmailsToSlice(user *models.User) []string { + var emailList []string + for _, email := range user.Emails { + if email.EmailAddress != nil { + emailList = append(emailList, *email.EmailAddress) + } + } + + return emailList +} From 767ef64062e760209f530ee003cad48bb81f17ce Mon Sep 17 00:00:00 2001 From: wanyaland Date: Wed, 10 Feb 2021 13:58:14 +0300 Subject: [PATCH 0068/1276] [#2600] Bug/Add GH Repo - Resolved panic caused by different interfaces for gh-repo-input between v1 & v2 Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/v2/repositories/service.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 5555622a3..9554c70c3 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -69,6 +69,7 @@ github.com/communitybridge/easycla v1.0.99 h1:PkmkMV7cLH2Q2YNSFiGGmlyrHBXVYdsWMw github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9udsPZk8oLoQ= github.com/communitybridge/easycla v1.0.107 h1:dktHAji1yJ1nMEu54z4paPWOM4Q7A9rryc0OCADfAcY= github.com/communitybridge/easycla v1.0.117 h1:o+rdmcNgZeMQ/N8HV/d5apNIBrkYH7eyM9UUYnEzewo= +github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNutidR+yy2HwW0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index ae5a3eb3c..de09e6fcf 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -9,7 +9,6 @@ import ( "fmt" "strconv" - "github.com/jinzhu/copier" "github.com/sirupsen/logrus" "github.com/go-openapi/swag" @@ -142,16 +141,15 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i if existingRepositoryModel != nil && !existingRepositoryModel.Enabled { msg := fmt.Sprintf("Github repository : %s disabled and shall get re-enabled... ", utils.StringValue(ghRepo.FullName)) log.WithFields(f).Debug(msg) - var v1Input v1Models.GithubRepositoryInput - err := copier.Copy(&v1Input, &input) - if err != nil { - log.WithFields(f).Error("unable to create v1GithubRepository input") - return nil, err + enabled := true + v1Input := &v1Models.GithubRepositoryInput{ + Enabled: &enabled, + RepositoryProjectID: input.ClaGroupID, + RepositoryOrganizationName: input.GithubOrganizationName, } - // Enabled repository - *v1Input.Enabled = true + // Update Repo details in case of any changes - updatedRepository, updateErr := s.repo.UpdateGithubRepository(ctx, existingRepositoryModel.RepositoryID, &v1Input) + updatedRepository, updateErr := s.repo.UpdateGithubRepository(ctx, existingRepositoryModel.RepositoryID, v1Input) if updateErr != nil { return nil, updateErr } From a01cd86644b6d1c66a6a18672ce1a8504486d95c Mon Sep 17 00:00:00 2001 From: makkalot Date: Wed, 10 Feb 2021 13:44:30 +0200 Subject: [PATCH 0069/1276] email changes requested Signed-off-by: makkalot --- cla-backend-go/emails/v2_cla_manager_templates.go | 6 +++--- cla-backend-go/utils/email.go | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 683060d68..23bc1f21b 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -27,7 +27,7 @@ const ( and is requesting to be approved as a contributor for your organization:

    {{.CLAGroupName}} - Signing Entity Name: {{.UserDetails}}

    Approval can be done at {{.LfxPortalURL}}

    -

    Please notify the contributor once they are added so that they may complete the contribution process.

    +

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

    ` ) @@ -79,7 +79,7 @@ const (

    {{.UserDetails}}

    Before the user contribution can be accepted, your organization must sign a CLA.

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the projects {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    -

    Please notify the contributor once they are added so that they may complete the contribution process.

    +

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

    ` ) @@ -141,7 +141,7 @@ const (

    {{.ContributorID}} ({{.ContributorName}})

    Before the user contribution can be accepted, your organization must sign a CLA.

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s) {{ range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    -

    Please notify the contributor once they are added so that they may complete the contribution process.

    +

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

    ` ) diff --git a/cla-backend-go/utils/email.go b/cla-backend-go/utils/email.go index 9333b7827..476b3d25b 100644 --- a/cla-backend-go/utils/email.go +++ b/cla-backend-go/utils/email.go @@ -123,6 +123,5 @@ support.

    ` // GetEmailSignOffContent returns the standard email sign-off details func GetEmailSignOffContent() string { - return `

    Thanks,

    -

    The LF Engineering Team

    ` + return `

    EasyCLA Support Team

    ` } From 8d26c7eacda9b2ea2bfdd74e7c23843422437108 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 10 Feb 2021 18:20:35 +0300 Subject: [PATCH 0070/1276] [#2611] Bug/Get GH orgs for given Project (#2613) - Resolved not found response for SF Projects of type Project Group Signed-off-by: wanyaland --- cla-backend-go/v2/github_organizations/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 51a0b11ec..a80e133ca 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,7 +85,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } var parentProjectSFID string - if projectServiceRecord.Foundation != nil && projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation { + if (projectServiceRecord.Foundation != nil && projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation) || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent From 07e7b169f81921dd3cdc357833b547b08bc0e2c9 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 10 Feb 2021 18:46:51 +0300 Subject: [PATCH 0071/1276] [#2612] Bug/Sign Flow (#2614) - Resolved repository issue caused in the corporate sign flow Signed-off-by: wanyaland --- cla-backend-go/repositories/repository.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 293f807e0..1c60c73d3 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -150,7 +150,6 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { externalID := utils.StringValue(input.RepositoryExternalID) - projectSFID := utils.StringValue(input.RepositoryProjectID) repositoryName := utils.StringValue(input.RepositoryName) repositoryOrganizationName := utils.StringValue(input.RepositoryOrganizationName) repositoryType := utils.StringValue(input.RepositoryType) @@ -161,7 +160,6 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "externalProjectID": externalID, - "projectSFID": projectSFID, "repositoryName": repositoryName, "repositoryOrganizationName": repositoryOrganizationName, "repositoryType": repositoryType, @@ -185,13 +183,6 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, expressionAttributeValues := map[string]*dynamodb.AttributeValue{} updateExpression := "SET " - if projectSFID != "" && repoModel.ProjectSFID != projectSFID { - log.WithFields(f).Debugf("adding projectSFID : %s ", projectSFID) - expressionAttributeNames["#P"] = aws.String("project_sfid") - expressionAttributeValues[":p"] = &dynamodb.AttributeValue{S: aws.String(projectSFID)} - updateExpression = updateExpression + " #P = :p, " - } - if externalID != "" && repoModel.RepositoryExternalID != externalID { log.WithFields(f).Debugf("adding externalID : %s ", externalID) expressionAttributeNames["#E"] = aws.String("repository_external_id") From 5240f86468e445eb92228f2d7fadd777c7910964 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Feb 2021 19:27:05 -0500 Subject: [PATCH 0072/1276] Always Use UTC when Creating Records (#2615) - Updatd the python database base model to ensure that times are in UTC for create and modified records Signed-off-by: David Deal --- .github/workflows/snyk.yml | 60 ------------------------- cla-backend/cla/models/dynamo_models.py | 6 +-- 2 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 .github/workflows/snyk.yml diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml deleted file mode 100644 index 4a4515b94..000000000 --- a/.github/workflows/snyk.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Snyk Scanning -on: push -jobs: - security: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ 3.7 ] - - steps: - - uses: actions/checkout@master -# - name: Run Snyk to check for vulnerabilities (Node) -# uses: snyk/actions/node@master -# continue-on-error: true -# env: -# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} -# with: -# command: monitor -# - name: Run Snyk to check for vulnerabilities (Golang) -# uses: snyk/actions/golang@master -# continue-on-error: true -# env: -# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} -# with: -# command: monitor - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies (cla-backend) - run: | - cd cla-backend - python -m pip install --upgrade pip wheel - pip install -r requirements.txt - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: '1.15.7' - - name: Install Dependencies (cla-backend-go) - run: | - cd cla-backend-go - go mod download - - uses: snyk/actions/setup@master - - name: Run Snyk to check for vulnerabilities (all) - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - run: | - snyk monitor --detection-depth=99 --dev --all-projects --org=${{ secrets.SNYK_ORG }} - -# - name: Run Snyk to check for vulnerabilities (Python) -# uses: snyk/actions/python@master -# continue-on-error: true -# env: -# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} -# with: -# command: monitor -# - name: Upload result to GitHub Code Scanning -# uses: github/codeql-action/upload-sarif@v1 -# with: -# sarif_file: snyk.sarif diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 214c46a25..771ce9aea 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -583,8 +583,8 @@ class BaseModel(Model): Base pynamodb model used for all CLA models. """ - date_created = UTCDateTimeAttribute(default=datetime.datetime.now()) - date_modified = UTCDateTimeAttribute(default=datetime.datetime.now()) + date_created = UTCDateTimeAttribute(default=datetime.datetime.utcnow()) + date_modified = UTCDateTimeAttribute(default=datetime.datetime.utcnow()) version = UnicodeAttribute(default="v1") # Schema version. def __iter__(self): @@ -4149,7 +4149,7 @@ class Meta: event_project_name_lower = UnicodeAttribute(null=True) event_user_name = UnicodeAttribute(null=True) event_user_name_lower = UnicodeAttribute(null=True) - event_time = UTCDateTimeAttribute(default=datetime.datetime.now()) + event_time = UTCDateTimeAttribute(default=datetime.datetime.utcnow()) event_time_epoch = NumberAttribute(default=int(time.time())) event_data = UnicodeAttribute(null=True) event_summary = UnicodeAttribute(null=True) From de0afc29029be59d8f49556db4c51655730c878a Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Feb 2021 19:57:02 -0500 Subject: [PATCH 0073/1276] Resolved #2616 - Corporate Console Support for Linux LLC Projects (#2617) - Added support for projects with 'LF Projects, LLC' parent Signed-off-by: David Deal --- .../github_organizations/service.go | 3 ++- cla-backend-go/utils/constants.go | 3 +++ cla-backend-go/v2/cla_groups/helpers.go | 22 +++++++++---------- .../v2/github_organizations/service.go | 6 +++-- cla-backend-go/v2/project-service/client.go | 14 ++++++------ cla-backend-go/v2/project/handlers.go | 2 +- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 4 ++-- 8 files changed, 31 insertions(+), 25 deletions(-) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 562d7b9b0..3aa8fb11c 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -100,7 +100,8 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) } - log.WithFields(f).Debugf("no parent or parent is %s - search criteria exhausted", utils.TheLinuxFoundation) + log.WithFields(f).Debugf("no parent or parent is %s or %s - search criteria exhausted", + utils.TheLinuxFoundation, utils.TheLinuxFoundation) return gitHubOrgModels, err } diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index edb04e560..98b23103b 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -45,6 +45,9 @@ const GitHubBotName = "EasyCLA" // TheLinuxFoundation is the name of the super parent for many Salesforce Foundations/Project Groups const TheLinuxFoundation = "The Linux Foundation" +// LFProjectsLLC is the LF project LLC name of the super parent for many Salesforce Foundations/Project Groups +const LFProjectsLLC = "LF Projects, LLC" + // ProjectUnfunded is a constant that represents a SF project that is unfunded const ProjectUnfunded = "Unfunded" diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 6bb1ad257..dfe13ce9d 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -136,7 +136,7 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat log.WithFields(f).Debugf("looking up LF parent project record...") isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) if err != nil { - log.WithFields(f).Warnf("validation failure - unable to lookup %s project, error: %+v", utils.TheLinuxFoundation, err) + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) return false, err } @@ -201,16 +201,16 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI log.WithFields(f).Debugf("looking up LF parent project record...") isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) if err != nil { - log.WithFields(f).Warnf("validation failure - unable to lookup %s project, error: %+v", utils.TheLinuxFoundation, err) + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) return err } // Let's check the foundation provided - does it have a parent? Only allowed parent is TLF if foundationProjectDetails.Parent != "" && !isLFParent { - log.WithFields(f).Warnf("input validation failure - foundation_sfid of %s has a parent other than %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, foundationProjectDetails.Parent) - return fmt.Errorf("bad request: input validation failure - foundation_sfid of %s has a parent other than %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, foundationProjectDetails.Parent) + log.WithFields(f).Warnf("input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", + foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) + return fmt.Errorf("bad request: input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", + foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) } // Comment out the below as we want to support stand-alone projects @@ -304,15 +304,15 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS log.WithFields(f).Debugf("looking up LF parent project record...") isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) if err != nil { - log.WithFields(f).Warnf("validation failure - unable to lookup %s project, error: %+v", utils.TheLinuxFoundation, err) + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) return err } if foundationProjectDetails.Parent != "" && !isLFParent { - log.WithFields(f).Warnf("input validation failure - foundation_sfid of %s has a parent other than %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, foundationProjectDetails.Parent) - return fmt.Errorf("bad request: input validation failure - foundation_sfid of %s has a parent other than %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, foundationProjectDetails.Parent) + log.WithFields(f).Warnf("input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", + foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) + return fmt.Errorf("bad request: input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", + foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) } // Comment out the below as we want to support stand-alone projects diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index a80e133ca..88d47b320 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,7 +85,9 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } var parentProjectSFID string - if (projectServiceRecord.Foundation != nil && projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation) || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { + if (projectServiceRecord.Foundation != nil && + (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || + projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent @@ -261,7 +263,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || project.Parent == utils.TheLinuxFoundation { + if project.Parent == "" || project.Parent == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index cae1349fc..2f5711d09 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -104,8 +104,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Do they have a parent? - if projectModel.Parent == "" || projectModel.Parent == utils.TheLinuxFoundation { - log.WithFields(f).Debugf("no parent for projectSFID or %s is the parent...", utils.TheLinuxFoundation) + if projectModel.Parent == "" || projectModel.Parent == utils.TheLinuxFoundation || projectModel.Parent == utils.LFProjectsLLC { + log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } @@ -116,7 +116,7 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { // IsTheLinuxFoundation returns true if the specified project SFID is the The Linux Foundation project func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { f := logrus.Fields{ - "functionName": "IsTheLinuxFoundation", + "functionName": "project-service.IsTheLinuxFoundation", } log.WithFields(f).Debug("querying project...") @@ -126,9 +126,9 @@ func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { return false, err } - if projectModel.Name == utils.TheLinuxFoundation { + if projectModel.Name == utils.TheLinuxFoundation || projectModel.Name == utils.LFProjectsLLC { // Save into our cache for next time - log.WithFields(f).Debug("project is the linux foundation...") + log.WithFields(f).Debugf("project is %s or %s...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return true, nil } @@ -158,9 +158,9 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) return false, err } - if parentProjectModel.Name == utils.TheLinuxFoundation { + if parentProjectModel.Name == utils.TheLinuxFoundation || parentProjectModel.Name == utils.LFProjectsLLC { // Save into our cache for next time - log.WithFields(f).Debug("parent project is the linux foundation...") + log.WithFields(f).Debugf("parent project is %s or %s...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return true, nil } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 1eb1eeda1..e7b7351f5 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -319,6 +319,6 @@ func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetail Slug: sfProject.Slug, Status: sfProject.Status, Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || sfProject.Parent == utils.TheLinuxFoundation), + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || sfProject.Parent == utils.TheLinuxFoundation || sfProject.Parent == utils.LFProjectsLLC), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index de09e6fcf..3a16fdcfb 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -86,7 +86,7 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i return nil, err } var externalProjectID string - if project.Parent == "" || project.Parent == utils.TheLinuxFoundation { + if project.Parent == "" || project.Parent == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { externalProjectID = projectSFID } else { externalProjectID = project.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index fa7384486..0d37c4b06 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -123,7 +123,7 @@ func validateCorporateSignatureInput(input *models.CorporateSignatureInput) erro return nil } -func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername string, authorizationHeader string, input *models.CorporateSignatureInput) (*models.CorporateSignatureOutput, error) { +func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername string, authorizationHeader string, input *models.CorporateSignatureInput) (*models.CorporateSignatureOutput, error) { // nolint f := logrus.Fields{ "functionName": "sign.RequestCorporateSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -173,7 +173,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || project.Parent == utils.TheLinuxFoundation { + if project.Parent == "" || project.Parent == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { From 88e60cdb669ce45f2bdb5bb39ca216a851d752c3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Feb 2021 20:33:32 -0500 Subject: [PATCH 0074/1276] Added Additional Logging (#2618) Signed-off-by: David Deal --- cla-backend-go/v2/cla_manager/handlers.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 3e3a4e142..be09ea7dc 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -192,14 +192,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C log.WithFields(f).Debugf("getting project IDs for CLA group") projectCLAGroups, getErr := projectClaGroupRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) if getErr != nil { - msg := fmt.Sprintf("Error getting SF projects for claGroup: %s ", params.ClaGroupID) - log.WithFields(f).Warn(msg) + msg := fmt.Sprintf("error getting SF projects for claGroup: %s ", params.ClaGroupID) + log.WithFields(f).WithError(getErr).Warn(msg) return cla_manager.NewCreateCLAManagerDesigneeByGroupBadRequest().WithXRequestID(reqID).WithPayload( - &models.ErrorResponse{ - Message: msg, - Code: BadRequest, - XRequestID: reqID, - }) + utils.ErrorResponseBadRequestWithError(reqID, msg, getErr)) } log.WithFields(f).Debugf("found %d project IDs for CLA group", len(projectCLAGroups)) @@ -211,10 +207,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C designeeScopes, msg, err := service.CreateCLAManagerDesigneeByGroup(ctx, params, projectCLAGroups) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating cla manager designee for CLA Group: %s with user email: %s", params.ClaGroupID, params.Body.UserEmail) if err == ErrCLAManagerDesigneeConflict { return cla_manager.NewCreateCLAManagerDesigneeByGroupConflict().WithXRequestID(reqID).WithPayload(utils.ErrorResponseConflictWithError(reqID, msg, err)) } - log.WithFields(f).Warn(msg) return cla_manager.NewCreateCLAManagerDesigneeByGroupBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } From 07e507752e1337b74c5b96e48a56c7e6c8d2425e Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 12 Feb 2021 11:44:33 +0300 Subject: [PATCH 0075/1276] [#2611] Bug/Add GH org Standalone Project - Resolved issue caused when adding standlone projects - Updated organization-service client functions for logo pointer fields Signed-off-by: wanyaland --- cla-backend-go/v2/github_organizations/service.go | 2 +- cla-backend-go/v2/organization-service/client.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 88d47b320..229730a2e 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -263,7 +263,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || project.Parent == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { + if project.Parent == "" || project.Foundation.Name == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index 8ffcb077a..131ac0565 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -600,7 +600,7 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName Industry: &industry, Source: &companySource, Type: &companyType, - LogoURL: &logoURL, + LogoURL: logoURL, SigningEntityName: []string{signingEntityName}, }, Context: ctx, @@ -613,7 +613,7 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName Industry: &industry, Source: &companySource, Type: &companyType, - LogoURL: &logoURL, + LogoURL: logoURL, SigningEntityName: []string{signingEntityName}, }) result, err := osc.cl.Organizations.CreateOrg(params, clientAuth) From 95b21cddf77691af93846ad860f1f9cacea93b43 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 12 Feb 2021 14:44:10 +0300 Subject: [PATCH 0076/1276] Bug/Foundation Name check - Resolved unexpected behaviour when enforcing CLA for repositories and orgs Signed-off-by: wanyaland --- .../v2/github_organizations/service.go | 3 ++- cla-backend-go/v2/project-service/client.go | 3 ++- cla-backend-go/v2/project/handlers.go | 25 ++++++++++--------- cla-backend-go/v2/repositories/service.go | 3 ++- cla-backend-go/v2/sign/service.go | 3 ++- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 229730a2e..0640fc3ed 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -263,7 +263,8 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || project.Foundation.Name == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 2f5711d09..8b682a9ea 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -104,7 +104,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Do they have a parent? - if projectModel.Parent == "" || projectModel.Parent == utils.TheLinuxFoundation || projectModel.Parent == utils.LFProjectsLLC { + if projectModel.Parent == "" || (projectModel.Foundation != nil && + (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index e7b7351f5..6fa24ff72 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -308,17 +308,18 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetailed, parentName string) *models.SfProjectSummary { return &models.SfProjectSummary{ - EntityName: sfProject.EntityName, - EntityType: sfProject.EntityType, - Funding: sfProject.Funding, - ID: sfProject.ID, - LfSupported: sfProject.LFSponsored, - Name: sfProject.Name, - ParentID: sfProject.Parent, - ParentName: parentName, - Slug: sfProject.Slug, - Status: sfProject.Status, - Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || sfProject.Parent == utils.TheLinuxFoundation || sfProject.Parent == utils.LFProjectsLLC), + EntityName: sfProject.EntityName, + EntityType: sfProject.EntityType, + Funding: sfProject.Funding, + ID: sfProject.ID, + LfSupported: sfProject.LFSponsored, + Name: sfProject.Name, + ParentID: sfProject.Parent, + ParentName: parentName, + Slug: sfProject.Slug, + Status: sfProject.Status, + Type: sfProject.Type, + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.Foundation != nil && + (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 3a16fdcfb..5f128e9cd 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -86,7 +86,8 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i return nil, err } var externalProjectID string - if project.Parent == "" || project.Parent == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { externalProjectID = projectSFID } else { externalProjectID = project.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 0d37c4b06..f77044e59 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,7 +173,8 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || project.Parent == utils.TheLinuxFoundation || project.Parent == utils.LFProjectsLLC { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { From 4e0e8621e22d0588b9a1120375ba21c8029d1bbf Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 12 Feb 2021 17:13:33 +0300 Subject: [PATCH 0077/1276] Bug/SF Parent Hierachy - Use 'ParentHeirachy' key in salesforce Project when checking Parent Name (LF Projects,LLC && TLF) Signed-off-by: wanyaland --- cla-backend-go/v2/github_organizations/service.go | 8 ++++---- cla-backend-go/v2/project-service/client.go | 4 ++-- cla-backend-go/v2/project/handlers.go | 4 ++-- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 0640fc3ed..b2018c187 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,8 +85,8 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } var parentProjectSFID string - if (projectServiceRecord.Foundation != nil && - (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || + if (projectServiceRecord.ParentHierarchy != nil && + (projectServiceRecord.ParentHierarchy.Name == utils.TheLinuxFoundation || projectServiceRecord.ParentHierarchy.Name == utils.LFProjectsLLC)) || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { parentProjectSFID = projectSFID } else { @@ -263,8 +263,8 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.ParentHierarchy != nil && + (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 8b682a9ea..68fc1c569 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -104,8 +104,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Do they have a parent? - if projectModel.Parent == "" || (projectModel.Foundation != nil && - (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { + if projectModel.Parent == "" || (projectModel.ParentHierarchy != nil && + (projectModel.ParentHierarchy.Name == utils.TheLinuxFoundation || projectModel.ParentHierarchy.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 6fa24ff72..c158afbe9 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -319,7 +319,7 @@ func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetail Slug: sfProject.Slug, Status: sfProject.Status, Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.Foundation != nil && - (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.ParentHierarchy != nil && + (sfProject.ParentHierarchy.Name == utils.TheLinuxFoundation || sfProject.ParentHierarchy.Name == utils.LFProjectsLLC))), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 5f128e9cd..48437b30f 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -87,7 +87,7 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i } var externalProjectID string if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { externalProjectID = projectSFID } else { externalProjectID = project.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index f77044e59..b9a051963 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,8 +173,8 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.ParentHierarchy != nil && + (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { From 60bde592a6f77609614f8be5e52122451778d9b1 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 12 Feb 2021 17:29:27 +0300 Subject: [PATCH 0078/1276] Revert "Bug/SF Parent Hierachy" --- cla-backend-go/v2/github_organizations/service.go | 8 ++++---- cla-backend-go/v2/project-service/client.go | 4 ++-- cla-backend-go/v2/project/handlers.go | 4 ++-- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index b2018c187..0640fc3ed 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,8 +85,8 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } var parentProjectSFID string - if (projectServiceRecord.ParentHierarchy != nil && - (projectServiceRecord.ParentHierarchy.Name == utils.TheLinuxFoundation || projectServiceRecord.ParentHierarchy.Name == utils.LFProjectsLLC)) || + if (projectServiceRecord.Foundation != nil && + (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { parentProjectSFID = projectSFID } else { @@ -263,8 +263,8 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || (project.ParentHierarchy != nil && - (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 68fc1c569..8b682a9ea 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -104,8 +104,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Do they have a parent? - if projectModel.Parent == "" || (projectModel.ParentHierarchy != nil && - (projectModel.ParentHierarchy.Name == utils.TheLinuxFoundation || projectModel.ParentHierarchy.Name == utils.LFProjectsLLC)) { + if projectModel.Parent == "" || (projectModel.Foundation != nil && + (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index c158afbe9..6fa24ff72 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -319,7 +319,7 @@ func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetail Slug: sfProject.Slug, Status: sfProject.Status, Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.ParentHierarchy != nil && - (sfProject.ParentHierarchy.Name == utils.TheLinuxFoundation || sfProject.ParentHierarchy.Name == utils.LFProjectsLLC))), + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.Foundation != nil && + (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 48437b30f..5f128e9cd 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -87,7 +87,7 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i } var externalProjectID string if project.Parent == "" || (project.Foundation != nil && - (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { externalProjectID = projectSFID } else { externalProjectID = project.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index b9a051963..f77044e59 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,8 +173,8 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || (project.ParentHierarchy != nil && - (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { From c5a82b41bd7e62ad52c28959c231295b108a2f82 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 12 Feb 2021 14:52:35 -0500 Subject: [PATCH 0079/1276] Added Search Company to Include Organization Service (#2629) Signed-off-by: David Deal --- cla-backend-go/v2/company/handlers.go | 35 ++++++++++++++++++++++----- cla-backend-go/v2/company/service.go | 33 ++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index d68fe3866..3533154b8 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -9,6 +9,8 @@ import ( "fmt" "strings" + organization_service "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" + "github.com/aws/aws-sdk-go/aws" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -344,7 +346,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("creating company...") - companyModel, err := service.CreateCompany(ctx, *params.Input.CompanyName, params.Input.SigningEntityName, *params.Input.CompanyWebsite, params.Input.UserEmail.String(), params.UserID) + companyModel, err := service.CreateCompany(ctx, *params.Input.CompanyName, params.Input.SigningEntityName, *params.Input.CompanyWebsite, params.Input.UserEmail.String(), params.UserID, "") if err != nil { log.Warnf("error returned from create company api: %+v", err) if strings.Contains(err.Error(), "website already exists") { @@ -373,14 +375,35 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo log.WithFields(f).Debug("loading company by name") companyModel, err := service.GetCompanyByName(ctx, params.CompanyName) - if err != nil { - msg := fmt.Sprintf("unable to locate company by name: %s", params.CompanyName) - log.WithFields(f).WithError(err).Warn(msg) - return company.NewGetCompanyByNameBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + if err != nil || companyModel == nil { + log.WithFields(f).Warn("unable to lookup company by name in local database. trying organization service...") + osClient := organization_service.GetClient() + orgModels, orgLookupErr := osClient.SearchOrganization(ctx, params.CompanyName, "", "") + if orgLookupErr != nil || len(orgModels) == 0 { + msg := fmt.Sprintf("unable to locate organization '%s' in the organization service", params.CompanyName) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyByNameNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + log.WithFields(f).Debugf("found company: '%s' in the organization service - creating local record...", params.CompanyName) + companyModels, companyCreateErr := service.CreateCompanyFromSFModel(ctx, orgModels[0]) + if companyCreateErr != nil || companyModels == nil { + msg := fmt.Sprintf("unable to create company '%s' from salesforce record", params.CompanyName) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyByNameInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, companyCreateErr)) + } + + log.WithFields(f).Debugf("loading company: %s by name after creation...", params.CompanyName) + companyModel, err = service.GetCompanyByName(ctx, params.CompanyName) + if err != nil { + msg := fmt.Sprintf("unable to locate company '%s' after creating...", params.CompanyName) + log.WithFields(f).WithError(err).Warn(msg) + return company.NewGetCompanyByNameNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } } if companyModel == nil { - msg := fmt.Sprintf("unable to locate company by name: %s", params.CompanyName) + msg := fmt.Sprintf("unable to load company by name: %s", params.CompanyName) log.WithFields(f).Warn(msg) return company.NewGetCompanyByNameNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) } diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index f41d43d12..409351c9f 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -37,6 +37,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/utils" acs_service "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" + orgModels "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service/models" orgService "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service/client/organizations" @@ -89,7 +90,8 @@ type Service interface { GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) - CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string) (*models.CompanyOutput, error) + CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID, note string) (*models.CompanyOutput, error) + CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization) (*models.CompanyOutput, error) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) GetCompanyByID(ctx context.Context, companyID string) (*models.Company, error) @@ -369,7 +371,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID }, nil } -func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string) (*models.CompanyOutput, error) { +func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string, note string) (*models.CompanyOutput, error) { f := logrus.Fields{ "functionName": "CreateCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -378,6 +380,7 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN "companyWebsite": companyWebsite, "userEmail": userEmail, "userID": userID, + "note": note, } var lfUser *v2UserServiceModels.User @@ -399,11 +402,11 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN log.WithFields(f).Warn(msg) } if lfUser != nil && lfUser.Username == "" { - msg := fmt.Sprintf("User: %s has no LF username", userEmail) + msg := fmt.Sprintf("User: %+v has no LF login/username", lfUser) log.WithFields(f).Warn(msg) } if lfUser != nil && lfUser.Username != "" { - log.WithFields(f).Debugf("User :%s has been assigned the %s role to organization: %s ", + log.WithFields(f).Debugf("User: %s has been assigned the %s role to organization: %s ", userEmail, utils.CompanyAdminRole, org.Name) // Assign company-admin to user roleID, adminErr := acsClient.GetRoleID(utils.CompanyAdminRole) @@ -444,6 +447,12 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN CompanyName: companyName, SigningEntityName: signingEntityName, } + if lfUser != nil && lfUser.Username != "" { + createCompanyModel.CompanyACL = []string{lfUser.Username} + } + if note != "" { + createCompanyModel.Note = note + } _, createErr := s.companyRepo.CreateCompany(ctx, createCompanyModel) //easyCLAErr := s.repo.CreateCompany(companyName, org.ID, userID) @@ -462,6 +471,22 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN }, nil } +func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization) (*models.CompanyOutput, error) { + f := logrus.Fields{ + "functionName": "company.service.CreateCompanyFromSFModel", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "organizationID": orgModel.Name, + "organizationName": orgModel.Name, + "organizationType": orgModel.Type, + "organizationLink": orgModel.Link, + "organizationStatus": orgModel.Status, + } + + log.WithFields(f).Debug("Creating company...") + return s.CreateCompany(ctx, orgModel.Name, orgModel.Name, orgModel.Link, + "", "", fmt.Sprintf("created from platform organization service model: %s", orgModel.ID)) +} + // GetCompanyByName deletes the company by name func (s *service) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) { f := logrus.Fields{ From 56bc865ddf7f178081dfcbda6193470af5e77134 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 12 Feb 2021 20:04:28 -0500 Subject: [PATCH 0080/1276] Resolved #2630 Enrolled Repositories Note (#2631) - Added note as optional input to data model - Updated repos update reposistory logic to set or append note if provided Signed-off-by: David Deal --- cla-backend-go/repositories/repository.go | 21 +++++++++++++++++++ cla-backend-go/swagger/cla.v2.yaml | 7 ++++++- .../common/github-repository-input.yaml | 17 ++++++++++++++- cla-backend-go/v2/repositories/service.go | 8 +++++-- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 1c60c73d3..d16381a84 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/sirupsen/logrus" @@ -154,6 +155,7 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, repositoryOrganizationName := utils.StringValue(input.RepositoryOrganizationName) repositoryType := utils.StringValue(input.RepositoryType) repositoryURL := utils.StringValue(input.RepositoryURL) + note := input.Note f := logrus.Fields{ "functionName": "repositories.repository.UpdateGitHubRepository", @@ -218,6 +220,25 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, updateExpression = updateExpression + " #U = :u, " } + if note != "" { + log.WithFields(f).Debugf("adding note: %s ", note) + noteValue := note + if !strings.HasSuffix(noteValue, ".") { + noteValue = fmt.Sprintf("%s.", noteValue) + } + // If we have a previous value - just concat the value to the end + if repoModel.Note != "" { + if strings.HasSuffix(strings.TrimSpace(repoModel.Note), ".") { + noteValue = fmt.Sprintf("%s %s", repoModel.Note, noteValue) + } else { + noteValue = fmt.Sprintf("%s. %s", repoModel.Note, noteValue) + } + } + expressionAttributeNames["#N"] = aws.String("note") + expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(noteValue)} + updateExpression = updateExpression + " #N = :n, " + } + if input.Enabled != nil && repoModel.Enabled != *input.Enabled { log.WithFields(f).Debugf("adding enabled flag: %+v", *input.Enabled) expressionAttributeNames["#EN"] = aws.String("enabled") diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index abbad12d2..3113d1416 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3785,10 +3785,15 @@ definitions: properties: repository_github_id: type: string + description: the repository external identifier, such as the GitHub ID of the repo + example: '337730995' github_organization_name: type: string + description: the repository organization + example: 'cncf' cla_group_id: - type: string + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' github-repository-branch-protection-status-checks: type: object diff --git a/cla-backend-go/swagger/common/github-repository-input.yaml b/cla-backend-go/swagger/common/github-repository-input.yaml index 524277ee6..c861af306 100644 --- a/cla-backend-go/swagger/common/github-repository-input.yaml +++ b/cla-backend-go/swagger/common/github-repository-input.yaml @@ -12,16 +12,31 @@ required: properties: repositoryExternalID: type: string + description: the repository external identifier, such as the GitHub ID of the repo + example: '337730995' repositoryName: type: string + description: the repository name + example: 'cncf/landscape' repositoryOrganizationName: type: string + description: the repository organization + example: 'cncf' repositoryProjectID: - type: string + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' repositoryType: type: string + description: the repository type + example: 'github' repositoryUrl: type: string + description: the repository URL + example: 'https://github.com/cncf/landscape' enabled: type: boolean + description: 'the enabled flag: true or false Repositories can be disabled from CLA to prevent CLA checks' default: true + note: + description: optional note added to the record + type: string diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 5f128e9cd..7a54a4cec 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -140,13 +140,17 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i // We already have an existing repository model with the same name if existingRepositoryModel != nil && !existingRepositoryModel.Enabled { - msg := fmt.Sprintf("Github repository : %s disabled and shall get re-enabled... ", utils.StringValue(ghRepo.FullName)) + msg := fmt.Sprintf("Github repository: %s previously disabled - will re-enabled... ", utils.StringValue(ghRepo.FullName)) log.WithFields(f).Debug(msg) enabled := true + + _, now := utils.CurrentTime() + v1Input := &v1Models.GithubRepositoryInput{ Enabled: &enabled, - RepositoryProjectID: input.ClaGroupID, RepositoryOrganizationName: input.GithubOrganizationName, + RepositoryProjectID: input.ClaGroupID, + Note: fmt.Sprintf("re-enabling repository on %s.", now), } // Update Repo details in case of any changes From c85b4bccf5471612b465a58aafd4de1dd388c0e4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Sun, 14 Feb 2021 14:42:06 -0800 Subject: [PATCH 0081/1276] Updated Logging - Resolved Logging error Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index c4a12dcdf..6f47e7a17 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -934,8 +934,6 @@ def request_corporate_signature(self, auth_user: object, f'send email: {send_as_email}, ', f'signatory name: {signatory_name}, ' f'signatory email: {signatory_email}, ' - f'return url type: {return_url_type}, ', - f'return url: {return_url}' ) # Auth user is the currently logged in user - the user who started the signing process @@ -1140,9 +1138,10 @@ def populate_sign_url(self, signature, callback_url=None, default_values: Optional[Dict[str, Any]] = None, preferred_email: str = None): # pylint: disable=too-many-locals + fn = 'populate_sign_url' sig_type = signature.get_signature_reference_type() - cla.log.debug(f'populate_sign_url - Populating sign_url for signature {signature.get_signature_id()} ' + cla.log.debug(f'{fn} - Populating sign_url for signature {signature.get_signature_id()} ' f'using callback: {callback_url} ' f'with authority_or_signatory_name {authority_or_signatory_name} ' f'with authority_or_signatory_email {authority_or_signatory_email} ' @@ -1160,35 +1159,35 @@ def populate_sign_url(self, signature, callback_url=None, user_signature_name = 'Unknown' user_signature_email = 'Unknown' - cla.log.debug(f'populate_sign_url - {sig_type} - processing signing request...') + cla.log.debug(f'{fn} - {sig_type} - processing signing request...') if sig_type == 'company': # For CCLA - use provided CLA Manager information user_signature_name = cla_manager_name user_signature_email = cla_manager_email - cla.log.debug(f'populate_sign_url - {sig_type} - user_signature name/email will be CLA Manager name/info: ' + cla.log.debug(f'{fn} - {sig_type} - user_signature name/email will be CLA Manager name/info: ' f'{user_signature_name} / {user_signature_email}...') try: # Grab the company id from the signature - cla.log.debug('populate_sign_url - CCLA - ' + cla.log.debug('{fn} - CCLA - ' f'Loading company id: {signature.get_signature_reference_id()}') company.load(signature.get_signature_reference_id()) - cla.log.debug(f'populate_sign_url - {sig_type} - loaded company: {company}') + cla.log.debug(f'{fn} - {sig_type} - loaded company: {company}') except DoesNotExist: - cla.log.warning(f'populate_sign_url - {sig_type} - ' + cla.log.warning(f'{fn} - {sig_type} - ' 'No CLA manager associated with this company - can not sign CCLA') return except Exception as e: - cla.log.warning(f'populate_sign_url - {sig_type} - No CLA manager lookup error: {e}') + cla.log.warning(f'{fn} - {sig_type} - No CLA manager lookup error: {e}') return elif sig_type == 'user': if not send_as_email: try: - cla.log.debug(f'populate_sign_url - {sig_type} - ' + cla.log.debug(f'{fn} - {sig_type} - ' f'loading user by reference id: {signature.get_signature_reference_id()}') user.load(signature.get_signature_reference_id()) - cla.log.debug(f'populate_sign_url - {sig_type} - loaded user by ' + cla.log.debug(f'{fn} - {sig_type} - loaded user by ' f'id: {user.get_user_id()}, ' f'name: {user.get_user_name()}, ' f'email: {user.get_user_email()}') @@ -1197,45 +1196,45 @@ def populate_sign_url(self, signature, callback_url=None, if not user.get_user_email() is None: user_signature_email = user.get_user_email() except DoesNotExist: - cla.log.warning(f'populate_sign_url - {sig_type} - no user associated with this signature ' + cla.log.warning(f'{fn} - {sig_type} - no user associated with this signature ' f'id: {signature.get_signature_reference_id()} - can not sign ICLA') return except Exception as e: - cla.log.warning(f'populate_sign_url - {sig_type} - no user associated with this signature - ' + cla.log.warning(f'{fn} - {sig_type} - no user associated with this signature - ' f'id: {signature.get_signature_reference_id()}, ' f'error: {e}') return cla.log.debug( - f'populate_sign_url - {sig_type} - user_signature name/email will be user from signature: ' + f'{fn} - {sig_type} - user_signature name/email will be user from signature: ' f'{user_signature_name} / {user_signature_email}...') else: - cla.log.warning(f'populate_sign_url - unsupported signature type: {sig_type}') + cla.log.warning(f'{fn} - unsupported signature type: {sig_type}') return # Fetch the document template to sign. project = Project() - cla.log.debug(f'populate_sign_url - {sig_type} - ' + cla.log.debug(f'{fn} - {sig_type} - ' f'loading project by id: {signature.get_signature_project_id()}') project.load(signature.get_signature_project_id()) - cla.log.debug(f'populate_sign_url - {sig_type} - ' + cla.log.debug(f'{fn} - {sig_type} - ' f'loaded project by id: {signature.get_signature_project_id()} - ' f'project: {project}') # Load the appropriate document if sig_type == 'company': - cla.log.debug(f'populate_sign_url - {sig_type} - loading project_corporate_document...') + cla.log.debug(f'{fn} - {sig_type} - loading project_corporate_document...') document = project.get_project_corporate_document() if document is None: - cla.log.error(f'populate_sign_url - {sig_type} - Could not get sign url for project: {project}. ' + cla.log.error(f'{fn} - {sig_type} - Could not get sign url for project: {project}. ' 'Project has no corporate CLA document set. Returning...') return - cla.log.debug(f'populate_sign_url - {sig_type} - loaded project_corporate_document...') + cla.log.debug(f'{fn} - {sig_type} - loaded project_corporate_document...') else: # sig_type == 'user' - cla.log.debug(f'populate_sign_url - {sig_type} - loading project_individual_document...') + cla.log.debug(f'{fn} - {sig_type} - loading project_individual_document...') document = project.get_project_individual_document() if document is None: - cla.log.error(f'populate_sign_url - {sig_type} - Could not get sign url for project: {project}. ' + cla.log.error(f'{fn} - {sig_type} - Could not get sign url for project: {project}. ' 'Project has no individual CLA document set. Returning...') return cla.log.debug(f'populate_sign_url - {sig_type} - loaded project_individual_document...') @@ -1250,7 +1249,7 @@ def populate_sign_url(self, signature, callback_url=None, cla.log.debug(message) self.client.void_envelope(envelope_id, message) except Exception as e: - cla.log.warning(f'populate_sign_url - {sig_type} - DocuSign error while voiding the envelope - ' + cla.log.warning(f'{fn} - {sig_type} - DocuSign error while voiding the envelope - ' f'regardless, continuing on..., error: {e}') # Not sure what should be put in as documentId. @@ -1258,7 +1257,7 @@ def populate_sign_url(self, signature, callback_url=None, tabs = get_docusign_tabs_from_document(document, document_id, default_values=default_values) if send_as_email: - cla.log.warning(f'populate_sign_url - {sig_type} - assigning signatory name/email: ' + cla.log.warning(f'{fn} - {sig_type} - assigning signatory name/email: ' f'{authority_or_signatory_name} / {authority_or_signatory_email}') # Sending email to authority signatory_email = authority_or_signatory_email @@ -1268,7 +1267,7 @@ def populate_sign_url(self, signature, callback_url=None, project_name = project.get_project_name() company_name = company.get_company_name() - cla.log.debug(f'populate_sign_url - {sig_type} - sending document as email with ' + cla.log.debug(f'{fn} - {sig_type} - sending document as email with ' f'name: {signatory_name}, email: {signatory_email} ' f'project name: {project_name}, company: {company_name}') @@ -1330,7 +1329,7 @@ def populate_sign_url(self, signature, callback_url=None, pdf = io.BytesIO(content) doc_name = document.get_document_name() - cla.log.debug(f'populate_sign_url - {sig_type} - docusign document ' + 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) @@ -1373,9 +1372,10 @@ def populate_sign_url(self, signature, callback_url=None, signature.set_signature_sign_url(sign_url) # Save Envelope ID in signature. - cla.log.debug(f'populate_sign_url - {sig_type} - saving signature to database...') + cla.log.debug(f'{fn} - {sig_type} - saving signature to database...') signature.set_signature_envelope_id(envelope.envelopeId) 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') def signed_individual_callback(self, content, installation_id, github_repository_id, change_request_id): From 99782049aa2d8efa7e824bab61d298617d5172cf Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 12 Feb 2021 17:57:11 +0300 Subject: [PATCH 0082/1276] Revert "Revert "Bug/SF Parent Hierachy"" This reverts commit 60bde592a6f77609614f8be5e52122451778d9b1. --- cla-backend-go/v2/github_organizations/service.go | 8 ++++---- cla-backend-go/v2/project-service/client.go | 4 ++-- cla-backend-go/v2/project/handlers.go | 4 ++-- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 0640fc3ed..b2018c187 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,8 +85,8 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } var parentProjectSFID string - if (projectServiceRecord.Foundation != nil && - (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || + if (projectServiceRecord.ParentHierarchy != nil && + (projectServiceRecord.ParentHierarchy.Name == utils.TheLinuxFoundation || projectServiceRecord.ParentHierarchy.Name == utils.LFProjectsLLC)) || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { parentProjectSFID = projectSFID } else { @@ -263,8 +263,8 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.ParentHierarchy != nil && + (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 8b682a9ea..68fc1c569 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -104,8 +104,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Do they have a parent? - if projectModel.Parent == "" || (projectModel.Foundation != nil && - (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { + if projectModel.Parent == "" || (projectModel.ParentHierarchy != nil && + (projectModel.ParentHierarchy.Name == utils.TheLinuxFoundation || projectModel.ParentHierarchy.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 6fa24ff72..c158afbe9 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -319,7 +319,7 @@ func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetail Slug: sfProject.Slug, Status: sfProject.Status, Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.Foundation != nil && - (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.ParentHierarchy != nil && + (sfProject.ParentHierarchy.Name == utils.TheLinuxFoundation || sfProject.ParentHierarchy.Name == utils.LFProjectsLLC))), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 7a54a4cec..9567a2bea 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -87,7 +87,7 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i } var externalProjectID string if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { externalProjectID = projectSFID } else { externalProjectID = project.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index f77044e59..b9a051963 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,8 +173,8 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.ParentHierarchy != nil && + (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { From 52d4c11d674ffb3c44fe801c05691f3a10860106 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 15 Feb 2021 06:09:29 +0300 Subject: [PATCH 0083/1276] [#2637] Bug/Get GH Orgs - Resolved get gh-orgs issue for standalone SF Project - Deprecated Parent Heirachy check, reverted to Foundation key Signed-off-by: wanyaland --- cla-backend-go/v2/github_organizations/service.go | 12 +++++++----- cla-backend-go/v2/project-service/client.go | 4 ++-- cla-backend-go/v2/project/handlers.go | 4 ++-- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index b2018c187..46dd3e082 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -84,10 +84,12 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return nil, err } + log.Debugf("project record: %+v ", projectServiceRecord) + var parentProjectSFID string - if (projectServiceRecord.ParentHierarchy != nil && - (projectServiceRecord.ParentHierarchy.Name == utils.TheLinuxFoundation || projectServiceRecord.ParentHierarchy.Name == utils.LFProjectsLLC)) || - projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { + if (projectServiceRecord.Foundation != nil && + (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || + projectServiceRecord.Parent == "" { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent @@ -263,8 +265,8 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || (project.ParentHierarchy != nil && - (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { parentProjectSFID = project.Parent diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 68fc1c569..8b682a9ea 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -104,8 +104,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Do they have a parent? - if projectModel.Parent == "" || (projectModel.ParentHierarchy != nil && - (projectModel.ParentHierarchy.Name == utils.TheLinuxFoundation || projectModel.ParentHierarchy.Name == utils.LFProjectsLLC)) { + if projectModel.Parent == "" || (projectModel.Foundation != nil && + (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index c158afbe9..6fa24ff72 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -319,7 +319,7 @@ func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetail Slug: sfProject.Slug, Status: sfProject.Status, Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.ParentHierarchy != nil && - (sfProject.ParentHierarchy.Name == utils.TheLinuxFoundation || sfProject.ParentHierarchy.Name == utils.LFProjectsLLC))), + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.Foundation != nil && + (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 9567a2bea..7a54a4cec 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -87,7 +87,7 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i } var externalProjectID string if project.Parent == "" || (project.Foundation != nil && - (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { externalProjectID = projectSFID } else { externalProjectID = project.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index b9a051963..f77044e59 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,8 +173,8 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || (project.ParentHierarchy != nil && - (project.ParentHierarchy.Name == utils.TheLinuxFoundation || project.ParentHierarchy.Name == utils.LFProjectsLLC)) { + if project.Parent == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { From 17f21bdc638b3bd71eb121ef48f1b9a7ec4cba00 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 15 Feb 2021 14:48:16 +0300 Subject: [PATCH 0084/1276] [#2636] Bug/Sign Standalone Project - Resolved CCLA sign issue for standalone projects Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/v2/sign/service.go | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 9554c70c3..50f8cca2c 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -70,6 +70,7 @@ github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9u github.com/communitybridge/easycla v1.0.107 h1:dktHAji1yJ1nMEu54z4paPWOM4Q7A9rryc0OCADfAcY= github.com/communitybridge/easycla v1.0.117 h1:o+rdmcNgZeMQ/N8HV/d5apNIBrkYH7eyM9UUYnEzewo= github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNutidR+yy2HwW0= +github.com/communitybridge/easycla v1.0.123 h1:Lh5i/9aajrTYItxNpVCmi9T1yyIfnQIOk0tC2Wtslvk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index f77044e59..185730c3a 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -175,6 +175,14 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri var claGroupID string if project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) + if perr != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") + return nil, perr + } + claGroupID = cgm.ClaGroupID + + } else { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { @@ -195,13 +203,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri return nil, errors.New("invalid project_sfid. multiple cla-groups are associated with this project_sfid") } claGroupID = (claGroups.List())[0] - } else { - cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) - if perr != nil { - log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") - return nil, perr - } - claGroupID = cgm.ClaGroupID + } f["claGroupID"] = claGroupID From 7cb6a44156327ce6826459cf17160012b37aaf53 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 15 Feb 2021 18:12:31 +0300 Subject: [PATCH 0085/1276] [#2642] Feature/Contributors GH details - Udpated GitHubID value with GItHub Username Signed-off-by: wanyaland --- cla-backend-go/v2/company/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 409351c9f..47964191b 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1472,7 +1472,7 @@ func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepos } var contributor models.CorporateContributor var sigSignedTime = sig.SignatureCreated - contributor.GithubID = user.GithubID + contributor.GithubID = user.GithubUsername contributor.LinuxFoundationID = user.LfUsername contributor.Name = user.Username t, err := utils.ParseDateTime(sig.SignatureCreated) From 1bdbcae8fb115eb76b67d836f8bd09f4a6af0a1d Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 15 Feb 2021 18:54:22 +0300 Subject: [PATCH 0086/1276] [#2643] Bug/Sign Parent Project Under TLF - Resolved issue for Parent project sign flow under TLF Signed-off-by: wanyaland --- cla-backend-go/v2/github_organizations/service.go | 2 +- cla-backend-go/v2/sign/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 46dd3e082..734891751 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -89,7 +89,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) var parentProjectSFID string if (projectServiceRecord.Foundation != nil && (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || - projectServiceRecord.Parent == "" { + projectServiceRecord.Parent == "" || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 185730c3a..765e27da6 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -174,7 +174,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri var claGroupID string if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) || project.ProjectType == utils.ProjectTypeProjectGroup { cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) if perr != nil { log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") From 2bdc3024b09197c419d31e5247c83ad609d33ba0 Mon Sep 17 00:00:00 2001 From: makkalot Date: Tue, 16 Feb 2021 13:17:10 +0200 Subject: [PATCH 0087/1276] change the text Signed-off-by: makkalot --- cla-backend/cla/models/docusign_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 6f47e7a17..cafdc60cb 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -1275,7 +1275,7 @@ def populate_sign_url(self, signature, callback_url=None, email_body = f'

    Hello {signatory_name},

    ' email_body += f'

    This is a notification email from EasyCLA regarding the project {project_name}. {cla_manager_name} has designated you as being an authorized signatory for {company_name}. In order for employees of your company to contribute to the open source project {project_name}, they must do so under a Contributor License Agreement signed by someone with authority to sign on behalf of your company.

    ' email_body += f'

    After you sign, {cla_manager_name} (as the initial CLA Manager for your company) will be able to maintain the list of specific employees authorized to contribute to the project under this signed CLA.

    ' - email_body += f'

    If you are authorized to sign on your company’s behalf, and if you approve {cla_manager_name} as your initial CLA Manager for {project_name}, please click the link below to review and sign the CLA.If you have questions, or if you are not an authorized signatory of this company, please contact the requester at {cla_manager_email}.

    ' + email_body += f'

    If you are authorized to sign on your company’s behalf, and if you approve {cla_manager_name} as your initial CLA Manager for {project_name}, please review the document and sign the CLA.If you have questions, or if you are not an authorized signatory of this company, please contact the requester at {cla_manager_email}.

    ' email_body = append_email_help_sign_off_content(email_body, project.get_version()) 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}') From 764cd9815666b923caf944443c3be972a18b7be4 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 16 Feb 2021 16:37:30 +0300 Subject: [PATCH 0088/1276] Bug/Sign CCLA Revert - Reverted fix for [#2643] that caused regression Signed-off-by: wanyaland --- cla-backend-go/v2/github_organizations/service.go | 2 +- cla-backend-go/v2/sign/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 734891751..46dd3e082 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -89,7 +89,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) var parentProjectSFID string if (projectServiceRecord.Foundation != nil && (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || - projectServiceRecord.Parent == "" || projectServiceRecord.ProjectType == utils.ProjectTypeProjectGroup { + projectServiceRecord.Parent == "" { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 765e27da6..185730c3a 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -174,7 +174,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri var claGroupID string if project.Parent == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) || project.ProjectType == utils.ProjectTypeProjectGroup { + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) if perr != nil { log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") From 27427b737f4b9a8f6d6b245c700663d806e2efb6 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 16 Feb 2021 17:03:59 +0300 Subject: [PATCH 0089/1276] Bug/Org Service Email - Resolved bug due to organization service update on email parameter Signed-off-by: wanyaland --- cla-backend-go/v2/organization-service/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index 131ac0565..d309e23aa 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -71,7 +71,7 @@ func (osc *Client) CreateOrgUserRoleOrgScope(ctx context.Context, emailID string params := &organizations.CreateOrgUsrRoleScopesParams{ CreateRoleScopes: &models.CreateRolescopes{ - EmailAddress: &emailID, + EmailAddress: emailID, ObjectID: &organizationID, ObjectType: aws.String("organization"), RoleID: &roleID, @@ -219,7 +219,7 @@ func (osc *Client) CreateOrgUserRoleOrgScopeProjectOrg(ctx context.Context, emai params := &organizations.CreateOrgUsrRoleScopesParams{ CreateRoleScopes: &models.CreateRolescopes{ - EmailAddress: &emailID, + EmailAddress: emailID, ObjectID: aws.String(fmt.Sprintf("%s|%s", projectID, organizationID)), ObjectType: aws.String("project|organization"), RoleID: &roleID, From 968e6f703549a363cdda7a687e2dedfb0a6718bd Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 16 Feb 2021 17:57:10 +0300 Subject: [PATCH 0090/1276] [#2643] Bug/Sign CCLA Flow Resolved use case for Parent Project with no parent set Signed-off-by: wanyaland --- cla-backend-go/v2/sign/service.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 185730c3a..733166aa9 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -175,14 +175,6 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri var claGroupID string if project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { - cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) - if perr != nil { - log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") - return nil, perr - } - claGroupID = cgm.ClaGroupID - - } else { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) if perr != nil { @@ -204,6 +196,13 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } claGroupID = (claGroups.List())[0] + } else { + cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) + if perr != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") + return nil, perr + } + claGroupID = cgm.ClaGroupID } f["claGroupID"] = claGroupID From 9de8b081008acac2433783fec1459280a58d0bfc Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 16 Feb 2021 17:23:11 +0200 Subject: [PATCH 0091/1276] use the signatory name instead of the cla manager name (#2660) Signed-off-by: makkalot --- cla-backend/cla/models/docusign_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index cafdc60cb..642bf03fd 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -1668,12 +1668,12 @@ def signed_corporate_callback(self, content, project_id, company_id): event_data = (f'Corporate signature ' f'signed for project {project.get_project_name()} ' f'and company {company.get_company_name()} ' - f'by user {user.get_user_name()}, ' + f'by {signature.get_signatory_name()}, ' f'params: {param_str}') event_summary = (f'A corporate signature ' f'was signed for project {project.get_project_name()} ' f'and company {company.get_company_name()} ' - f'by user {user.get_user_name()}.') + f'by {signature.get_signatory_name()}.') Event.create_event( event_type=EventType.CompanySignatureSigned, event_project_id=project_id, From 2ec2b5dc8db346c5eb89fa469cce66376471c2cc Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 16 Feb 2021 18:41:44 +0200 Subject: [PATCH 0092/1276] [#2649]approved list email text fixes (#2662) Signed-off-by: makkalot --- cla-backend-go/cmd/server.go | 2 +- .../emails/v2_cla_manager_templates.go | 12 +++++----- .../emails/v2_cla_manager_templates_test.go | 22 ++++++++++++++----- cla-backend-go/v2/cla_manager/handlers.go | 4 ++-- cla-backend-go/v2/cla_manager/service.go | 14 ++++++------ 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index f0c641092..816ce5150 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -324,7 +324,7 @@ func server(localMode bool) http.Handler { v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, projectClaGroupRepo) v2Company.Configure(v2API, v2CompanyService, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleURL) cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) - v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, projectClaGroupRepo, userRepo) + v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, configFile.CorporateConsoleV2URL, projectClaGroupRepo, userRepo) sign.Configure(v2API, v2SignService) cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, projectClaGroupRepo, eventsService) v2GithubActivity.Configure(v2API, v2GithubActivityService) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 23bc1f21b..b790ece07 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -11,9 +11,9 @@ import ( // V2ContributorApprovalRequestTemplateParams is email template params for V2ContributorApprovalRequestTemplate type V2ContributorApprovalRequestTemplateParams struct { CLAManagerTemplateParams - SigningEntityName string - UserDetails string - LfxPortalURL string + SigningEntityName string + UserDetails string + CorporateConsoleV2URL string } const ( @@ -23,10 +23,10 @@ const ( V2ContributorApprovalRequestTemplate = `

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

    -

    The following contributor would like to submit a contribution to the {{.SigningEntityName}} CLA Group +

    The following contributor would like to submit a contribution to the {{if .SigningEntityName}}{{.SigningEntityName}}{{else}}{{.CompanyName}}{{end}} CLA Group {{.CLAGroupName}} and is requesting to be approved as a contributor for your organization:

    -

    {{.CLAGroupName}} - Signing Entity Name: {{.UserDetails}}

    -

    Approval can be done at {{.LfxPortalURL}}

    +

    {{.CLAGroupName}} - {{.UserDetails}}

    +

    Approval can be done at {{.CorporateConsoleV2URL}}

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

    ` ) diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/emails/v2_cla_manager_templates_test.go index a65723807..3ac5d9d7c 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates_test.go +++ b/cla-backend-go/emails/v2_cla_manager_templates_test.go @@ -18,9 +18,8 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, - SigningEntityName: "SigningEntityNameValue", - UserDetails: "UserDetailsValue", - LfxPortalURL: "http://LfxPortalURL.com", + UserDetails: "UserDetailsValue", + CorporateConsoleV2URL: "http://CorporateConsoleV2URL.com", } result, err := RenderTemplate(utils.V1, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, @@ -28,9 +27,20 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the organization JohnsCompany") - assert.Contains(t, result, "contribution to the SigningEntityNameValue CLA Group") - assert.Contains(t, result, "JohnsCLAGroupName - Signing Entity Name: UserDetailsValue") - assert.Contains(t, result, "Approval can be done at http://LfxPortalURL.com") + assert.Contains(t, result, "contribution to the JohnsCompany CLA Group JohnsCLAGroupName") + assert.Contains(t, result, "JohnsCLAGroupName - UserDetailsValue") + assert.Contains(t, result, "Approval can be done at http://CorporateConsoleV2URL.com") + + params.SigningEntityName = "SigningEntityNameValue" + + result, err = RenderTemplate(utils.V1, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the organization JohnsCompany") + assert.Contains(t, result, "contribution to the SigningEntityNameValue CLA Group JohnsCLAGroupName") + assert.Contains(t, result, "JohnsCLAGroupName - UserDetailsValue") + assert.Contains(t, result, "Approval can be done at http://CorporateConsoleV2URL.com") } func TestV2OrgAdminTemplate(t *testing.T) { diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index be09ea7dc..d3d279202 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -37,7 +37,7 @@ const ( ) // Configure is the API handler routine for CLA Manager routes -func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1Company.IService, LfxPortalURL string, projectClaGroupRepo projects_cla_groups.Repository, easyCLAUserRepo v1User.RepositoryService) { // nolint +func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1Company.IService, LfxPortalURL, CorporateConsoleV2URL string, projectClaGroupRepo projects_cla_groups.Repository, easyCLAUserRepo v1User.RepositoryService) { // nolint api.ClaManagerCreateCLAManagerHandler = cla_manager.CreateCLAManagerHandlerFunc(func(params cla_manager.CreateCLAManagerParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint @@ -377,7 +377,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C "claGroupName": params.Body.ClaGroupName, } log.WithFields(f).Debug("notifying CLA managers...") - err := service.NotifyCLAManagers(ctx, params.Body, LfxPortalURL) + err := service.NotifyCLAManagers(ctx, params.Body, CorporateConsoleV2URL) if err != nil { if err == ErrCLAUserNotFound { msg := fmt.Sprintf("unable to notify cla managers - user not found: %s", params.Body.UserID) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 6b1c30235..67942dae9 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -99,7 +99,7 @@ type Service interface { InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL string) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) - NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, LfxPortalURL string) error + NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, CorporateConsoleV2URL string) error CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) } @@ -1067,7 +1067,7 @@ func validateInviteCompanyAdmin(contactAdmin bool, userEmail string, name string return nil } -func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, LfxPortalURL string) error { +func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, CorporateConsoleV2URL string) error { f := logrus.Fields{ "functionName": "cla_manager.service.NotifyCLAManagers", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1087,13 +1087,13 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode log.Debugf("Sending notification emails to CLA Managers: %+v", notifyCLAManagers.List) for _, claManager := range notifyCLAManagers.List { - sendEmailToCLAManager(ctx, claManager.Name, claManager.Email.String(), userModel, notifyCLAManagers.CompanyName, notifyCLAManagers.SigningEntityName, notifyCLAManagers.ClaGroupName, LfxPortalURL) + sendEmailToCLAManager(ctx, claManager.Name, claManager.Email.String(), userModel, notifyCLAManagers.CompanyName, notifyCLAManagers.SigningEntityName, notifyCLAManagers.ClaGroupName, CorporateConsoleV2URL) } return nil } -func sendEmailToCLAManager(ctx context.Context, manager string, managerEmail string, userModel *v1Models.User, company, signingEntityName, claGroupName, lfxPortalURL string) { +func sendEmailToCLAManager(ctx context.Context, manager string, managerEmail string, userModel *v1Models.User, company, signingEntityName, claGroupName, corporateConsoleV2URL string) { f := logrus.Fields{ "functionName": "cla_manager.service.sendEmailToCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1115,9 +1115,9 @@ func sendEmailToCLAManager(ctx context.Context, manager string, managerEmail str CompanyName: company, CLAGroupName: claGroupName, }, - SigningEntityName: signingEntityName, - UserDetails: getFormattedUserDetails(userModel), - LfxPortalURL: lfxPortalURL, + SigningEntityName: signingEntityName, + UserDetails: getFormattedUserDetails(userModel), + CorporateConsoleV2URL: corporateConsoleV2URL, }, ) if err != nil { From 4b8c4ec88cf775722f843de406418585f682e160 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 16 Feb 2021 11:17:01 -0800 Subject: [PATCH 0093/1276] Updated new user create logic/logging (#2663) Signed-off-by: David Deal --- cla-backend-go/cmd/server.go | 25 ++++++++++++++---- cla-backend-go/users/repository.go | 12 +++++++-- cla-backend-go/users/service.go | 42 ++++++++++++------------------ 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 816ce5150..82cea5da4 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -591,13 +591,23 @@ func createUserFromRequest(authorizer auth.Authorizer, usersService users.Servic log.WithFields(f).WithError(err).Warn("parsing failed") return } + f["claUserName"] = claUser.Name + f["claUserID"] = claUser.UserID + f["claUserLFUsername"] = claUser.LFUsername + f["claUserLFEmail"] = claUser.LFEmail + f["claUserEmails"] = strings.Join(claUser.Emails, ",") // search if user exist in database by username userModel, err := usersService.GetUserByLFUserName(claUser.LFUsername) if err != nil { - log.WithFields(f).WithError(err).Warn("searching user by lf-username failed") - return + if err, ok := err.(*utils.UserNotFound); ok { + log.WithFields(f).Debug("unable to locate user by lf-email") + } else { + log.WithFields(f).WithError(err).Warn("searching user by lf-username failed") + return + } } + // If found - just return if userModel != nil { return } @@ -605,9 +615,14 @@ func createUserFromRequest(authorizer auth.Authorizer, usersService users.Servic // search if user exist in database by username userModel, err = usersService.GetUserByEmail(claUser.LFEmail) if err != nil { - log.WithFields(f).WithError(err).Warn("searching user by lf-email failed") - return + if err, ok := err.(*utils.UserNotFound); ok { + log.WithFields(f).Debug("unable to locate user by lf-email") + } else { + log.WithFields(f).WithError(err).Warn("searching user by lf-email failed") + return + } } + // If found - just return if userModel != nil { return } @@ -618,7 +633,7 @@ func createUserFromRequest(authorizer auth.Authorizer, usersService users.Servic LfUsername: claUser.LFUsername, Username: claUser.Name, } - log.WithFields(f).WithField("user", newUser).Debug("creating new user") + log.WithFields(f).Debug("creating new user") userModel, err = usersService.CreateUser(newUser, nil) if err != nil { log.WithFields(f).WithField("user", newUser).WithError(err).Warn("creating new user failed") diff --git a/cla-backend-go/users/repository.go b/cla-backend-go/users/repository.go index 51dc21b76..1412b91d4 100644 --- a/cla-backend-go/users/repository.go +++ b/cla-backend-go/users/repository.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" "github.com/go-openapi/errors" @@ -678,9 +680,15 @@ func (repo repository) GetUserByEmail(userEmail string) (*models.User, error) { } if len(dbUserModels) == 0 { - return nil, errors.NotFound("user not found when searching by lf_email: %s", userEmail) + return nil, &utils.UserNotFound{ + Message: fmt.Sprintf("user not found when searching by lf email: %s", userEmail), + UserLFID: "", + UserName: "", + UserEmail: userEmail, + Err: nil, + } } else if len(dbUserModels) > 1 { - log.WithFields(f).WithError(err).Warnf("retrieved %d results for the lf_email query when we should return 0 or 1", len(dbUserModels)) + log.WithFields(f).Warnf("retrieved %d results for the lf_email query when we should return 0 or 1", len(dbUserModels)) } return convertDBUserModel(dbUserModels[0]), nil diff --git a/cla-backend-go/users/service.go b/cla-backend-go/users/service.go index 19b918628..d1e17158f 100644 --- a/cla-backend-go/users/service.go +++ b/cla-backend-go/users/service.go @@ -81,6 +81,9 @@ func (s service) Save(user *models.UserUpdate, claUser *user.CLAUser) (*models.U // Delete deletes the user record func (s service) Delete(userID string, claUser *user.CLAUser) error { + if userID == "" { + return errors.New("userID is empty") + } err := s.repo.Delete(userID) if err != nil { return err @@ -100,12 +103,10 @@ func (s service) Delete(userID string, claUser *user.CLAUser) error { // GetUser attempts to locate the user by the user id field func (s service) GetUser(userID string) (*models.User, error) { - userModel, err := s.repo.GetUser(userID) - if err != nil { - return nil, err + if userID == "" { + return nil, errors.New("userID is empty") } - - return userModel, nil + return s.repo.GetUser(userID) } // GetuserByLFUserName returns the user record associated with the LF Username value @@ -118,40 +119,29 @@ func (s service) GetUserByLFUserName(lfUserName string) (*models.User, error) { // GetUserByUserName attempts to locate the user by the user name field func (s service) GetUserByUserName(userName string, fullMatch bool) (*models.User, error) { - userModel, err := s.repo.GetUserByUserName(userName, fullMatch) - if err != nil { - return nil, err + if userName == "" { + return nil, errors.New("username is empty") } - - return userModel, nil + return s.repo.GetUserByUserName(userName, fullMatch) } // GetUserByEmail fetches the user by email func (s service) GetUserByEmail(userEmail string) (*models.User, error) { - userModel, err := s.repo.GetUserByEmail(userEmail) - if err != nil { - return nil, err + if userEmail == "" { + return nil, errors.New("userEmail is empty") } - - return userModel, nil + return s.repo.GetUserByEmail(userEmail) } // GetUserByGitHubUsername fetches the user by GitHub username func (s service) GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) { - userModel, err := s.repo.GetUserByGitHubUsername(gitHubUsername) - if err != nil { - return nil, err + if gitHubUsername == "" { + return nil, errors.New("gitHubUsername is empty") } - - return userModel, nil + return s.repo.GetUserByGitHubUsername(gitHubUsername) } // SearchUsers attempts to locate the user by the searchField and searchTerm fields func (s service) SearchUsers(searchField string, searchTerm string, fullMatch bool) (*models.Users, error) { - userModel, err := s.repo.SearchUsers(searchField, searchTerm, fullMatch) - if err != nil { - return nil, err - } - - return userModel, nil + return s.repo.SearchUsers(searchField, searchTerm, fullMatch) } From 831c861b5fc6bb61d4b87c414bb93e1449f09356 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 16 Feb 2021 14:00:47 -0800 Subject: [PATCH 0094/1276] Resolved Create Company Issue when Company Name Was Updated By Clearbit (#2665) - handled scenario when the company name was changed by the organization service/clearbit on create - EasyCLA company record now honors the SF/Clearbit name that was assigned - Updated logging Signed-off-by: David Deal --- cla-backend-go/v2/company/handlers.go | 3 ++- cla-backend-go/v2/company/service.go | 18 ++++++++++++++---- .../v2/organization-service/client.go | 8 ++------ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 3533154b8..3d519fd72 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -333,7 +333,8 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo "userID": params.UserID, "companyName": aws.StringValue(params.Input.CompanyName), "companyWebsite": aws.StringValue(params.Input.CompanyWebsite), - "signingEntityName": aws.StringValue(¶ms.Input.SigningEntityName), + "signingEntityName": params.Input.SigningEntityName, + "userEmail": params.Input.UserEmail.String(), } // No permissions needed - anyone can create a company diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 47964191b..46324ed6c 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -373,7 +373,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string, note string) (*models.CompanyOutput, error) { f := logrus.Fields{ - "functionName": "CreateCompany", + "functionName": "service.CreateCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": companyName, "signingEntityName": signingEntityName, @@ -386,13 +386,22 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN // Create SalesForce company orgClient := orgService.GetClient() - log.WithFields(f).Debugf("Creating Organization: %s, Signing Entity Name: %s, Website: %s", companyName, signingEntityName, companyWebsite) + log.WithFields(f).Debugf("Creating Organization: %s, Signing Entity Name: %s, Website: %s in SalesForce...", companyName, signingEntityName, companyWebsite) org, err := orgClient.CreateOrg(ctx, companyName, signingEntityName, companyWebsite) if err != nil { log.WithFields(f).Warnf("unable to create platform organization service, error: %+v", err) return nil, err } + // Company Service switched the company name based on ClearBit??? + if org.Name != companyName { + log.WithFields(f).Debugf("create SalesForce company changed the company name - new name is: %s", org.Name) + companyName = org.Name + signingEntityName = org.Name + f["updatedCompanyName"] = org.Name + f["updatedSigningEntityName"] = org.Name + } + acsClient := acs_service.GetClient() userClient := v2UserService.GetClient() @@ -432,12 +441,13 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN } // Create Easy CLA Company - log.WithFields(f).Debugf("Creating EasyCLA company: %s ", companyName) + log.WithFields(f).Debugf("Creating EasyCLA company: %s", companyName) if signingEntityName == "" { - log.WithFields(f).Debugf("Setting signing entity with company name value :%s ", companyName) + log.WithFields(f).Debugf("Setting signing entity with company name value: %s", companyName) signingEntityName = companyName } + // OrgID used as externalID for the easyCLA Company // Create a new company model for the create function createCompanyModel := &v1Models.Company{ diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index d309e23aa..eae267238 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -703,12 +703,8 @@ func (osc *Client) ListOrg(ctx context.Context, orgName string) (*models.Organiz func (osc *Client) SearchOrgLookup(ctx context.Context, orgName, websiteName *string) (*organizations.LookupOK, error) { f := logrus.Fields{ "functionName": "organization_service.Lookup", - } - if orgName != nil { - f["orgName"] = *orgName - } - if websiteName != nil { - f["websiteName"] = *websiteName + "orgName": utils.StringValue(orgName), + "websiteName": utils.StringValue(websiteName), } tok, err := token.GetToken() From 2dd3bdbd563586b41fef1161c7e9a77b7aa1377e Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 16 Feb 2021 17:12:06 -0800 Subject: [PATCH 0095/1276] Resolved #2427 Email to NonLF User (#2666) Signed-off-by: David Deal --- .../emails/v2_cla_manager_templates.go | 16 +++++++-------- .../emails/v2_cla_manager_templates_test.go | 7 ++++--- cla-backend-go/swagger/cla.v2.yaml | 8 +++----- .../swagger/common/properties/user-name.yaml | 8 ++++++++ cla-backend-go/v2/cla_manager/service.go | 20 ++++++++++--------- 5 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 cla-backend-go/swagger/common/properties/user-name.yaml diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index b790ece07..1d122f04d 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -150,6 +150,7 @@ type V2DesigneeToUserWithNoLFIDTemplateParams struct { CLAManagerTemplateParams RequesterUserName string RequesterEmail string + CorporateConsole string } const ( @@ -157,14 +158,13 @@ const ( V2DesigneeToUserWithNoLFIDTemplateName = "V2DesigneeToUserWithNoLFIDTemplateName" // V2DesigneeToUserWithNoLFIDTemplate is email template for V2DesigneeToUserWithNoLFIDTemplate = ` -

    Hello {{.RecipientName}},

    -

    User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for Project {{.GetProjectNameOrFoundation}}, CLA Group {{.CLAGroupName}} and Company {{.CompanyName}} but was unable to identify your account details in the EasyCLA system

    -

    This email will guide you to completing the CLA Manager role assignment

    -

    1. Accept Invite link below will take you SSO login page where you can login with your LF Login or create a LF Login and then login.

    -

    2. After logging in SSO screen should direct you to CLA Corporate Console page where you will see the project you a re associated with.

    -

    3. Click on workflow steps to complete the signup process. Please follow this documentation to help you guide through the process - https://docs.linuxfoundation.org/lfx/v/v2/easycla/corporate-cla-manager-designee-or-initial-cla-manager/sign-corporate-cla-for-a-company

    -

    4. Once you have completed CLA Manager workflow you will be able to manage the approved list of contributors

    -

    Accept Invite

    +

    Hello {{.RecipientName}},

    +

    This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}}.

    +

    The following contributor would like to contribute to {{.GetProjectNameOrFoundation}} on behalf of your organization: {{.CompanyName}}.

    +

    {{.RequesterUserName}} ({{.RequesterEmail}})

    +

    Before the user's contribution can be accepted, your organization must sign a CLA.

    +

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for the project {{.GetProjectNameOrFoundation}}.

    +

    After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

    ` ) diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/emails/v2_cla_manager_templates_test.go index 3ac5d9d7c..e30e91021 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates_test.go +++ b/cla-backend-go/emails/v2_cla_manager_templates_test.go @@ -152,14 +152,15 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { }, RequesterUserName: "RequesterUserNameValue", RequesterEmail: "RequesterEmailValue", + CorporateConsole: "https://corporate.dev.lfcla.com", } result, err := RenderTemplate(utils.V1, V2DesigneeToUserWithNoLFIDTemplateName, V2DesigneeToUserWithNoLFIDTemplate, params) assert.NoError(t, err) - assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "User RequesterUserNameValue (RequesterEmailValue) was trying") - assert.Contains(t, result, "CLA Manager for Project JohnsProjectExternal, CLA Group JohnsCLAGroupName and Company JohnsCompany") + assert.Contains(t, result, "Hello JohnsClaManager,") + assert.Contains(t, result, "The following contributor would like to contribute to JohnsProjectExternal on behalf of your organization: JohnsCompany.") + assert.Contains(t, result, "Kindly login to this portal https://corporate.dev.lfcla.com and sign the CLA for the project JohnsProjectExternal.") } func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 3113d1416..9752b6d4d 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4763,13 +4763,11 @@ definitions: example: false description: send signing request as email. This should be set to true when requestor is not signatory. authority_name: - type: string - example: 'John Doe' - description: name of the cla signatory - pattern: "^[a-zA-Z0-9]+(([',. -][a-zA-Z0-9 ])?[a-zA-Z0-9]*)*$" + description: the name of the CLA signatory + $ref: './common/properties/user-name.yaml' authority_email: $ref: './common/properties/email.yaml' - description: Email of the CLA Signatory + description: the email of the CLA Signatory return_url: type: string example: 'https://corporate.dev.lfcla.com/#/company/eb4d7d71-693f-4047-bf8d-10d0e7764969' diff --git a/cla-backend-go/swagger/common/properties/user-name.yaml b/cla-backend-go/swagger/common/properties/user-name.yaml new file mode 100644 index 000000000..a3f1f4819 --- /dev/null +++ b/cla-backend-go/swagger/common/properties/user-name.yaml @@ -0,0 +1,8 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: string +example: "Derk Miyamoto" +pattern: ^[a-zA-Z0-9][a-zA-Z0-9 ',.;:-_&@\#\$\(\)\+]*$ +minLength: 2 +maxLength: 255 diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 67942dae9..bda760359 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -970,7 +970,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com } contibutorEmail := GetNonNoReplyUserEmail(contributor.UserEmails) - sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee") + sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee", LfxPortalURL) if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) @@ -1370,15 +1370,16 @@ func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, } } -func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string) error { +func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string, corporateConsoleV2URL string) error { f := logrus.Fields{ - "functionName": "cla_manager.service.sendDesigneeEmailToUserWithNoLFID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "userWithNoLFIDName": userWithNoLFIDName, - "userWithNoLFIDEmail": userWithNoLFIDEmail, - "organizationID": organizationID, - "projectID": utils.StringValue(projectID), - "role": role, + "functionName": "cla_manager.service.sendDesigneeEmailToUserWithNoLFID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "userWithNoLFIDName": userWithNoLFIDName, + "userWithNoLFIDEmail": userWithNoLFIDEmail, + "organizationID": organizationID, + "projectID": utils.StringValue(projectID), + "role": role, + "corporateConsoleV2URL": corporateConsoleV2URL, } subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager for project: %s ", projectName) @@ -1391,6 +1392,7 @@ func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_ }, RequesterUserName: requesterUsername, RequesterEmail: requesterEmail, + CorporateConsole: corporateConsoleV2URL, }) if err != nil { From 2c00a25cb3ce5f3f375d745efb216a36685aaecd Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 16 Feb 2021 17:51:59 -0800 Subject: [PATCH 0096/1276] Sorted Search Results (#2667) Signed-off-by: David Deal --- cla-backend-go/company/service.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index 68c5b154b..2d3c69072 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -6,6 +6,8 @@ package company import ( "context" "fmt" + "sort" + "strings" "github.com/sirupsen/logrus" @@ -727,6 +729,18 @@ func (s service) SearchOrganizationByName(ctx context.Context, orgName string, w }) } } + + // Sort the results + sort.Slice(result.List, func(i, j int) bool { + switch strings.Compare(strings.ToLower(result.List[i].OrganizationName), strings.ToLower(result.List[j].OrganizationName)) { + case -1: + return true + case 1: + return false + } + return strings.ToLower(result.List[i].OrganizationWebsite) > strings.ToLower(result.List[j].OrganizationWebsite) + }) + return result, nil } From f9981a45c9e8f119824f6b26bc978c00f5ae46e0 Mon Sep 17 00:00:00 2001 From: makkalot Date: Wed, 17 Feb 2021 12:47:46 +0200 Subject: [PATCH 0097/1276] signature signed email content changed Signed-off-by: makkalot --- cla-backend/cla/models/docusign_models.py | 61 +++++++++++------ .../cla/tests/unit/test_docusign_models.py | 68 ++++++++++++++++++- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 642bf03fd..9ffbced79 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -1775,31 +1775,11 @@ def send_signed_document(self, signature, document_data, user, icla=True): project = Project() project.load(signature.get_signature_project_id()) except DoesNotExist as err: - cla.log.warning(f'{fn} - unable to load project by id: {project.get_project_id()} - ' + cla.log.warning(f'{fn} - unable to load project by id: {signature.get_signature_project_id()} - ' 'unable to send email to user') return - # subject = 'EasyCLA: Signed Document' - # body = 'Thank you for signing the CLA! Your signed document is attached to this email.' - if icla: - pdf_link = (f'{cla.conf["API_BASE_URL"]}/v3/' - f'signatures/{project.get_project_id()}/' - f'{user.get_user_id()}/icla/pdf') - else: - pdf_link = (f'{cla.conf["API_BASE_URL"]}/v3/' - f'signatures/{project.get_project_id()}/' - f'{signature.get_signature_reference_id()}/ccla/pdf') - subject = f'EasyCLA: CLA Signature Signed for {project.get_project_name()}' - body = f''' -

    Hello {"Contributor" if icla else "CLA Signatory"},

    -

    This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

    -

    Thank you for signing the CLA. You can download the PDF document - - from our website. -

    - ''' - body = append_email_help_sign_off_content(body, project.get_version()) - + subject, body = document_signed_email_content(icla=icla, project=project, signature=signature, user=user) # Third, send the email. cla.log.debug(f'{fn} - sending signed CLA document to {recipient} with subject: {subject}') cla.utils.get_email_service().send(subject, body, recipient) @@ -2187,3 +2167,40 @@ def generate_manager_and_contributor_list(managers, contributors=None): lines = '\n'.join([str(line) for line in lines]) return lines + + +def document_signed_email_content(icla: bool, project: Project, signature: Signature, user: User) -> (str, str): + """ + document_signed_email_content prepares the email subject and body content for the signed documents + :return: + """ + # subject = 'EasyCLA: Signed Document' + # body = 'Thank you for signing the CLA! Your signed document is attached to this email.' + if icla: + pdf_link = (f'{cla.conf["API_BASE_URL"]}/v3/' + f'signatures/{project.get_project_id()}/' + f'{user.get_user_id()}/icla/pdf') + else: + pdf_link = (f'{cla.conf["API_BASE_URL"]}/v3/' + f'signatures/{project.get_project_id()}/' + f'{signature.get_signature_reference_id()}/ccla/pdf') + + recipient_name = user.get_user_name() or user.get_lf_username() or None + # some defensive code + if not recipient_name: + if icla: + recipient_name = "Contributor" + else: + recipient_name = "CLA Manager" + + subject = f'EasyCLA: CLA Signature Signed for {project.get_project_name()}' + body = f''' +

    Hello {recipient_name},

    +

    This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

    +

    The CLA for {project.get_project_name()} has been signed. You can download the PDF document + + from our website. +

    + ''' + body = append_email_help_sign_off_content(body, project.get_version()) + return subject, body diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index 04805d75d..3e87a9c51 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -4,8 +4,8 @@ import xml.etree.ElementTree as ET from cla.models.docusign_models import populate_signature_from_ccla_callback, populate_signature_from_icla_callback, \ - create_default_company_values -from cla.models.dynamo_models import Signature, Company + create_default_company_values, document_signed_email_content +from cla.models.dynamo_models import Signature, Company, Project, User content_icla_agreement_date = """ Date: Wed, 17 Feb 2021 19:09:46 +0200 Subject: [PATCH 0098/1276] cla manager designee request email fixes (#2670) Signed-off-by: makkalot --- .../emails/v2_cla_manager_templates.go | 17 ++++++++++++++--- .../emails/v2_cla_manager_templates_test.go | 14 ++++++++++++-- cla-backend-go/v2/cla_manager/handlers.go | 2 +- cla-backend-go/v2/cla_manager/service.go | 8 ++++---- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 1d122f04d..4ba4f5fdb 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -4,6 +4,8 @@ package emails import ( + "strings" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" ) @@ -130,18 +132,27 @@ type V2ToCLAManagerDesigneeTemplateParams struct { CorporateConsole string } +// GetProjectsOrProject returns the single Project or comma saparated projects if more than one +func (p V2ToCLAManagerDesigneeTemplateParams) GetProjectsOrProject() string { + if len(p.ProjectNames) == 1 { + return " " + p.ProjectNames[0] + } + + return "s " + strings.Join(p.ProjectNames, ", ") +} + const ( // V2ToCLAManagerDesigneeTemplateName is email template name for V2ToCLAManagerDesigneeTemplate V2ToCLAManagerDesigneeTemplateName = "V2ToCLAManagerDesigneeTemplateName" // V2ToCLAManagerDesigneeTemplate is email template for V2ToCLAManagerDesigneeTemplate = `

    Hello {{.RecipientName}},

    -

    This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    +

    This is a notification email from EasyCLA regarding the project{{.GetProjectsOrProject}}.

    The following contributor is requesting to sign CLA for organization:

    {{.ContributorID}} ({{.ContributorName}})

    Before the user contribution can be accepted, your organization must sign a CLA. -

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s) {{ range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

    -

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

    +

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project{{.GetProjectsOrProject}}.

    +

    After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

    ` ) diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/emails/v2_cla_manager_templates_test.go index e30e91021..09f037de5 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates_test.go +++ b/cla-backend-go/emails/v2_cla_manager_templates_test.go @@ -134,10 +134,20 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the project(s) Project1,Project2") + assert.Contains(t, result, "regarding the projects Project1, Project2") + assert.Contains(t, result, "

    ContributorIDValue (ContributorNameValue)

    ") + assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") + assert.Contains(t, result, "CLA for one of the projects Project1, Project2") + + params.ProjectNames = []string{"Project1"} + result, err = RenderTemplate(utils.V1, V2ToCLAManagerDesigneeTemplateName, V2ToCLAManagerDesigneeTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello JohnsClaManager") + assert.Contains(t, result, "regarding the project Project1") assert.Contains(t, result, "

    ContributorIDValue (ContributorNameValue)

    ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, "CLA for one of the project(s) Project1,Project2") + assert.Contains(t, result, "CLA for one of the project Project1") } diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index d3d279202..cf06296a0 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -248,7 +248,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C return cla_manager.NewInviteCompanyAdminBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, userErr)) } - claManagerDesignees, err := service.InviteCompanyAdmin(ctx, params.Body.ContactAdmin, params.Body.CompanyID, *params.Body.ClaGroupID, params.Body.UserEmail.String(), params.Body.Name, &user, LfxPortalURL) + claManagerDesignees, err := service.InviteCompanyAdmin(ctx, params.Body.ContactAdmin, params.Body.CompanyID, *params.Body.ClaGroupID, params.Body.UserEmail.String(), params.Body.Name, &user, LfxPortalURL, CorporateConsoleV2URL) if err != nil { statusCode := buildErrorStatusCode(err) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index bda760359..65152da6c 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -96,7 +96,7 @@ type service struct { type Service interface { CreateCLAManager(ctx context.Context, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse - InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL string) ([]*models.ClaManagerDesignee, error) + InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, CorporateConsoleV2URL string) error @@ -836,7 +836,7 @@ func (s *service) ValidateInviteCompanyAdminCheck(ctx context.Context, f logrus. return nil } -func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, LfxPortalURL string) ([]*models.ClaManagerDesignee, error) { +func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, LfxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) { f := logrus.Fields{ "functionName": "cla_manager.service.InviteCompanyAdmin", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1006,10 +1006,10 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com log.Debugf("Sending Email to CLA Manager Designee email: %s ", userEmail) if contributor.LFUsername != "" && contributor.LFEmail != "" && len(projectSFs) > 0 { - sendEmailToCLAManagerDesignee(ctx, LfxPortalURL, organization.Name, projectSFs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) + sendEmailToCLAManagerDesignee(ctx, CorporateConsoleV2URL, organization.Name, projectSFs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) } else { contributorUserName, contributorEmail := getContributorPublicEmail(contributor) - sendEmailToCLAManagerDesignee(ctx, LfxPortalURL, organization.Name, projectSFs, userEmail, user.Name, contributorUserName, contributorEmail) + sendEmailToCLAManagerDesignee(ctx, CorporateConsoleV2URL, organization.Name, projectSFs, userEmail, user.Name, contributorUserName, contributorEmail) } log.Debugf("CLA Manager designee created : %+v", designeeScopes) From 380be38330c2a4342d1d000cd7a39a08798dccc0 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 17 Feb 2021 20:10:18 +0300 Subject: [PATCH 0099/1276] [#2634] Bug/Contact Admin (#2671) --- cla-backend-go/v2/cla_manager/service.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 65152da6c..b33e917a7 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -932,8 +932,17 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com } for _, admin := range scopes.Userroles { - // Check if is Gerrit User or GH User - contributorEmailToOrgAdmin(admin.Contact.EmailAddress, admin.Contact.Name, organization.Name, projectSFs, userModel, LfxPortalURL) + // Email details are masked so an extra query to get user details is used + log.WithFields(f).Debugf("Getting email for user with ID: %s ", admin.Contact.ID) + + adminUser, adminErr := userService.GetUser(admin.Contact.ID) + if adminErr != nil { + msg := fmt.Sprintf("Failed to get user for ID: %s ", admin.Contact.ID) + log.Warn(msg) + return nil, adminErr + } + + contributorEmailToOrgAdmin(userService.GetPrimaryEmail(adminUser), admin.Contact.Name, organization.Name, projectSFs, userModel, LfxPortalURL) designeeScope := models.ClaManagerDesignee{ Email: strfmt.Email(admin.Contact.EmailAddress), Name: admin.Contact.Name, From 49d558879aee12dc900a918273cc238cf40317c8 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Thu, 18 Feb 2021 15:52:33 +0300 Subject: [PATCH 0100/1276] [#2673] Feature/Sign off - Updated Sign off content to 'EasyCLA Support Team' Signed-off-by: wanyaland --- cla-backend/cla/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index e9d097d90..5f49be87e 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1541,7 +1541,7 @@ def get_email_help_content(show_v2_help_link: bool) -> str: def get_email_sign_off_content() -> str: - return '

    Thanks,

    The LF Engineering Team

    ' + return '

    Thanks,

    EasyCLA Support Team

    ' def append_email_help_sign_off_content(body: str, project_version: str) -> str: From c542c5beeab48b4ae9fe9b09ba87b40732ef36a1 Mon Sep 17 00:00:00 2001 From: makkalot Date: Thu, 18 Feb 2021 15:46:53 +0200 Subject: [PATCH 0101/1276] add "Accept Invite" link to Designee to User with no lfid email template Signed-off-by: makkalot --- cla-backend-go/emails/v2_cla_manager_templates.go | 3 ++- cla-backend-go/emails/v2_cla_manager_templates_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 4ba4f5fdb..bcec889e8 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -174,7 +174,8 @@ const (

    The following contributor would like to contribute to {{.GetProjectNameOrFoundation}} on behalf of your organization: {{.CompanyName}}.

    {{.RequesterUserName}} ({{.RequesterEmail}})

    Before the user's contribution can be accepted, your organization must sign a CLA.

    -

    Kindly login to this portal {{.CorporateConsole}} and sign the CLA for the project {{.GetProjectNameOrFoundation}}.

    +

    Please click on Accept Invite to create your LF Login.

    +

    After login, you will be redirected to this portal {{.CorporateConsole}} where you can sign the CLA for the project {{.GetProjectNameOrFoundation}}.

    After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

    ` ) diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/emails/v2_cla_manager_templates_test.go index 09f037de5..ee8170b66 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates_test.go +++ b/cla-backend-go/emails/v2_cla_manager_templates_test.go @@ -170,7 +170,7 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager,") assert.Contains(t, result, "The following contributor would like to contribute to JohnsProjectExternal on behalf of your organization: JohnsCompany.") - assert.Contains(t, result, "Kindly login to this portal https://corporate.dev.lfcla.com and sign the CLA for the project JohnsProjectExternal.") + assert.Contains(t, result, "you will be redirected to this portal https://corporate.dev.lfcla.com where you can sign the CLA for the project JohnsProjectExternal") } func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { From 3effd3d4316b455b004b226136169a7e90cc8d6d Mon Sep 17 00:00:00 2001 From: wanyaland Date: Thu, 18 Feb 2021 17:51:20 +0300 Subject: [PATCH 0102/1276] [#2647] Feature/Signatory Email - Updated email content sent to CLA Manager when they are not signatory Signed-off-by: wanyaland --- cla-backend/cla/config.py | 1 + cla-backend/cla/models/docusign_models.py | 14 +++++++++----- cla-backend/cla/tests/unit/test_docusign_models.py | 2 +- cla-backend/cla/utils.py | 9 +++++++++ cla-backend/serverless.yml | 1 + 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/cla-backend/cla/config.py b/cla-backend/cla/config.py index b143d5cd9..8208f019d 100644 --- a/cla-backend/cla/config.py +++ b/cla-backend/cla/config.py @@ -61,6 +61,7 @@ def get_ssm_key(region, key): # Corporate Console base URL CORPORATE_BASE_URL = os.environ.get('CLA_CORPORATE_BASE', '') +CORPORATE_V2_BASE_URL = os.environ.get('CLA_CORPORATE_V2_BASE', '') # Landing Page CLA_LANDING_PAGE = os.environ.get('CLA_LANDING_PAGE', '') diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 9ffbced79..5ddaf461b 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -29,7 +29,7 @@ from cla.models.event_types import EventType from cla.models.s3_storage import S3Storage from cla.user_service import UserService -from cla.utils import get_email_help_content, append_email_help_sign_off_content +from cla.utils import get_email_help_content, append_email_help_sign_off_content, get_corporate_url api_base_url = os.environ.get('CLA_API_BASE', '') root_url = os.environ.get('DOCUSIGN_ROOT_URL', '') @@ -44,6 +44,8 @@ lf_group = LFGroup(lf_group_client_url, lf_group_client_id, lf_group_client_secret, lf_group_refresh_token) + + class ProjectDoesNotExist(Exception): pass @@ -1895,7 +1897,7 @@ def get_org_from_return_url(repo_provider_type, return_url, orgs): :param return_url: The URL will be redirected after signature done. :type return_url: string :return: List of Organizations of any repo service provider. - :rtype: [any_repo_service_provider.Organization] + :rtype: [any_repo_service_provider.Organization] """ if repo_provider_type == 'github': split_url = return_url.split('/') # parse repo name from URL @@ -2184,6 +2186,8 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa pdf_link = (f'{cla.conf["API_BASE_URL"]}/v3/' f'signatures/{project.get_project_id()}/' f'{signature.get_signature_reference_id()}/ccla/pdf') + + corporate_url = get_corporate_url(project.get_version()) recipient_name = user.get_user_name() or user.get_lf_username() or None # some defensive code @@ -2194,12 +2198,12 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa recipient_name = "CLA Manager" subject = f'EasyCLA: CLA Signature Signed for {project.get_project_name()}' - body = f''' + body = f'''

    Hello {recipient_name},

    This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

    -

    The CLA for {project.get_project_name()} has been signed. You can download the PDF document +

    The CLA has now been signed. You can download the signed CLA as a PDF - from our website. + here, or from within the EasyCLA CLA Manager console .

    ''' body = append_email_help_sign_off_content(body, project.get_version()) diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index 3e87a9c51..1c0f6437c 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -825,7 +825,7 @@ def test_document_signed_email_content(): assert "Signature Signed for JohnsProject" in subject assert "Hello john" in body assert "EasyCLA regarding the project JohnsProject" in body - assert "The CLA for JohnsProject has been signed" in body + assert "The CLA has now been signed." in body # try with different recipient names user.set_user_name(None) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 5f49be87e..2c8d0a5c7 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -28,6 +28,8 @@ API_BASE_URL = os.environ.get('CLA_API_BASE', '') CLA_LOGO_URL = os.environ.get('CLA_BUCKET_LOGO_URL', '') +CORPORATE_BASE = os.environ.get('CLA_CORPORATE_BASE', '') +CORPORATE_V2_BASE = os.environ.get('CLA_CORPORATE_V2_BASE', '') def get_cla_path(): @@ -1543,6 +1545,13 @@ def get_email_help_content(show_v2_help_link: bool) -> str: def get_email_sign_off_content() -> str: return '

    Thanks,

    EasyCLA Support Team

    ' +def get_corporate_url(project_version: str) -> str: + """ + helper method that returns appropriate corporate link based on EasyCLA version + :param project_version: cla_group version(v1|v2) + :return: default is v1 corporate console + """ + return CORPORATE_V2_BASE if project_version == 'v2' else CORPORATE_BASE def append_email_help_sign_off_content(body: str, project_version: str) -> str: """ diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 478d8ee05..9044a474e 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -313,6 +313,7 @@ provider: CLA_CONTRIBUTOR_BASE: ${file(./env.json):cla-contributor-base, ssm:/cla-contributor-base-${opt:stage}} CLA_CONTRIBUTOR_V2_BASE: ${file(./env.json):cla-contributor-v2-base, ssm:/cla-contributor-v2-base-${opt:stage}} CLA_CORPORATE_BASE: ${file(./env.json):cla-corporate-base, ssm:/cla-corporate-base-${opt:stage}} + CLA_CORPORATE_V2_BASE: ${file(./env.json):cla-corporate-v2-base, ssm:/cla-corporate-v2-base-${opt:stage}} CLA_LANDING_PAGE: ${file(./env.json):cla-landing-page, ssm:/cla-landing-page-${opt:stage}} CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${opt:stage}~true} CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${opt:stage}~true} From 7ecd71699b0bcdef0008c9c9b1cbe1143363b2ac Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 18 Feb 2021 18:00:49 -0800 Subject: [PATCH 0103/1276] Added Support for Bulk Add GitHub Repositories (#2679) Signed-off-by: David Deal --- cla-backend-go/swagger/cla.v2.yaml | 9 +- cla-backend-go/tests/project_helpers_test.go | 178 +++++++++++++++++++ cla-backend-go/utils/project_helpers.go | 25 +++ cla-backend-go/v2/repositories/handlers.go | 63 ++++--- cla-backend-go/v2/repositories/service.go | 151 ++++++++++------ 5 files changed, 346 insertions(+), 80 deletions(-) create mode 100644 cla-backend-go/tests/project_helpers_test.go create mode 100644 cla-backend-go/utils/project_helpers.go diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 9752b6d4d..70b97d040 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1433,7 +1433,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/github-repository' + $ref: '#/definitions/list-github-repositories' '400': $ref: '#/responses/invalid-request' '401': @@ -3779,10 +3779,15 @@ definitions: github-repository-input: type: object required: - - repository_github_id - github_organization_name - cla_group_id properties: + repository_github_ids: + type: array + items: + description: the repository external identifier, such as the GitHub ID of the repo + type: string + example: '337730995' repository_github_id: type: string description: the repository external identifier, such as the GitHub ID of the repo diff --git a/cla-backend-go/tests/project_helpers_test.go b/cla-backend-go/tests/project_helpers_test.go new file mode 100644 index 000000000..4c7bfe472 --- /dev/null +++ b/cla-backend-go/tests/project_helpers_test.go @@ -0,0 +1,178 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/models" + "github.com/go-openapi/strfmt" + "github.com/stretchr/testify/assert" +) + +const ( + testProjectParentID = "abc1234" + testProjectID = "def13456" + testProjectLogo = "testlogurl.com" +) + +func TestIsProjectHasRootParentNoParent(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = "" + project.Foundation = nil + assert.True(t, utils.IsProjectHasRootParent(project), "Project Has Root Parent - Empty Parent") +} + +func TestIsProjectHasRootParentLF(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: utils.TheLinuxFoundation, + } + assert.True(t, utils.IsProjectHasRootParent(project), "Project Has Root Parent - LF Parent") +} + +func TestIsProjectHasRootParentLFProjectsLLC(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: utils.LFProjectsLLC, + } + assert.True(t, utils.IsProjectHasRootParent(project), "Project Has Root Parent - LF Projects LLC Parent") +} + +func TestIsProjectHasRootParentNonLF(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: "other", + } + assert.False(t, utils.IsProjectHasRootParent(project), "Project Has Root Parent - Non LF Project Parent") +} + +func TestIsStandaloneProject(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = "" + project.Foundation = nil + project.Projects = []*models.ProjectOutput{} + assert.True(t, utils.IsStandaloneProject(project), "Standalone Project with No Parent with No Children") +} + +func TestLFParent(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: utils.TheLinuxFoundation, + } + project.Projects = []*models.ProjectOutput{} + assert.True(t, utils.IsStandaloneProject(project), "Standalone Project with LF Parent with No Children") +} + +func TestLFProjectsLLCParent(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: utils.LFProjectsLLC, + } + project.Projects = []*models.ProjectOutput{} + assert.True(t, utils.IsStandaloneProject(project), "Standalone Project with LF Projects LLC parent with No Children") +} + +func TestLFParentWithChildren(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: utils.TheLinuxFoundation, + } + project.Projects = []*models.ProjectOutput{} + assert.True(t, utils.IsStandaloneProject(project), "Standalone Project with LF Parent with Children") +} + +func TestLFProjectsLLCParentWithChildren(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = &models.Foundation{ + ID: testProjectID, + LogoURL: testProjectLogo, + Name: utils.LFProjectsLLC, + } + project.Projects = []*models.ProjectOutput{} + child := &models.ProjectOutput{ + ProjectCommon: models.ProjectCommon{}, + CreatedDate: nil, + DocuSignStatus: nil, + EndDate: nil, + EntityType: "", + ExecutiveDirector: nil, + Foundation: nil, + HerokuConnectID: "", + ID: testProjectID, + IsDeleted: false, + LFSponsored: false, + LegalParent: nil, + ModifiedDate: nil, + OpportunityOwner: nil, + Owner: nil, + ProgramManager: nil, + ProjectType: "SubProject", + RenewalOwner: nil, + Slug: "another-slug", + SystemModStamp: strfmt.DateTime{}, + Type: "", + } + project.Projects = []*models.ProjectOutput{child} + assert.False(t, utils.IsStandaloneProject(project), "Standalone Project with LF Projects LLC parent with Children") +} + +func TestIsProjectHaveChildrenNoChildren(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = nil + project.Projects = []*models.ProjectOutput{} + assert.False(t, utils.IsProjectHaveChildren(project), "Project has no children") +} + +func TestIsProjectHaveChildrenWithChildren(t *testing.T) { + project := &models.ProjectOutputDetailed{} + project.Parent = testProjectParentID + project.Foundation = nil + child := &models.ProjectOutput{ + ProjectCommon: models.ProjectCommon{}, + CreatedDate: nil, + DocuSignStatus: nil, + EndDate: nil, + EntityType: "", + ExecutiveDirector: nil, + Foundation: nil, + HerokuConnectID: "", + ID: testProjectID, + IsDeleted: false, + LFSponsored: false, + LegalParent: nil, + ModifiedDate: nil, + OpportunityOwner: nil, + Owner: nil, + ProgramManager: nil, + ProjectType: "SubProject", + RenewalOwner: nil, + Slug: "random-slug", + SystemModStamp: strfmt.DateTime{}, + Type: "", + } + project.Projects = []*models.ProjectOutput{child} + assert.True(t, utils.IsProjectHaveChildren(project), "Project has Children") +} diff --git a/cla-backend-go/utils/project_helpers.go b/cla-backend-go/utils/project_helpers.go new file mode 100644 index 000000000..e90f3b3a1 --- /dev/null +++ b/cla-backend-go/utils/project_helpers.go @@ -0,0 +1,25 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +import "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/models" + +// IsProjectHasRootParent determines if the a given project has a root parent. A root parent is a parent that is empty parent or the parent is TLF or LFProjects +func IsProjectHasRootParent(project *models.ProjectOutputDetailed) bool { + return project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC)) +} + +// IsStandaloneProject determines if a given project is a standalone project. A standalone project is a project with no parent or the parent is TLF/LFProjects and does not have any children +func IsStandaloneProject(project *models.ProjectOutputDetailed) bool { + // standalone: No parent or parent is TLF/LFProjects....and no children + return (project.Parent == "" || + (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC))) && + len(project.Projects) == 0 +} + +// IsProjectHaveChildren determines if a given project has children +func IsProjectHaveChildren(project *models.ProjectOutputDetailed) bool { + // a project model with a project list means it has children + return len(project.Projects) > 0 +} diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 8636a2f3e..14aae468c 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -82,11 +82,15 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubRepositoriesAddProjectGithubRepositoryHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "authUser": authUser.UserName, - "authEmail": authUser.Email, - "projectSFID": params.ProjectSFID, + "functionName": "GitHubRepositoriesAddProjectGithubRepositoryHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "claGroupID": utils.StringValue(params.GithubRepositoryInput.ClaGroupID), + "githubOrganizationName": utils.StringValue(params.GithubRepositoryInput.GithubOrganizationName), + "repositoryGitHubID": params.GithubRepositoryInput.RepositoryGithubID, + "repositoryGitHubIDs": strings.Join(params.GithubRepositoryInput.RepositoryGithubIds, ","), } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { @@ -97,7 +101,17 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.AddGithubRepository(ctx, params.ProjectSFID, params.GithubRepositoryInput) + // If no repository GitHub ID values provided... + // RepositoryGithubID - provided by the older retool UI which provides only one value + // RepositoryGithubIds - provided by new PCC which passes multiple values + if params.GithubRepositoryInput.RepositoryGithubID == "" && len(params.GithubRepositoryInput.RepositoryGithubIds) == 0 { + msg := "missing repository GitHub ID value(s)" + log.WithFields(f).Warn(msg) + return github_repositories.NewAddProjectGithubRepositoryBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + results, err := service.AddGithubRepositories(ctx, params.ProjectSFID, params.GithubRepositoryInput) if err != nil { if _, ok := err.(*utils.GitHubRepositoryExists); ok { msg := fmt.Sprintf("unable to add repository - repository already exists for projectSFID: %s", params.ProjectSFID) @@ -111,23 +125,25 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - // Log the event - eventService.LogEvent(&events.LogEventArgs{ - EventType: events.RepositoryAdded, - ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), - ExternalProjectID: params.ProjectSFID, - LfUsername: authUser.UserName, - ClaGroupModel: &v1Models.ClaGroup{ - ProjectExternalID: params.ProjectSFID, + // Log the events + for _, result := range results { + eventService.LogEvent(&events.LogEventArgs{ + EventType: events.RepositoryAdded, ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), - }, - EventData: &events.RepositoryAddedEventData{ - RepositoryName: result.RepositoryName, - }, - }) + ExternalProjectID: params.ProjectSFID, + LfUsername: authUser.UserName, + ClaGroupModel: &v1Models.ClaGroup{ + ProjectExternalID: params.ProjectSFID, + ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), + }, + EventData: &events.RepositoryAddedEventData{ + RepositoryName: result.RepositoryName, + }, + }) + } - response := &models.GithubRepository{} - err = copier.Copy(response, result) + var v2ResponseList []*models.GithubRepository + err = copier.Copy(&v2ResponseList, results) if err != nil { msg := fmt.Sprintf("problem converting response for projectSFID: %s", params.ProjectSFID) log.WithFields(f).WithError(err).Warn(msg) @@ -135,7 +151,10 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) } - return github_repositories.NewAddProjectGithubRepositoryOK().WithPayload(response) + v2Response := &models.ListGithubRepositories{} + v2Response.List = v2ResponseList + + return github_repositories.NewAddProjectGithubRepositoryOK().WithPayload(v2Response) }) api.GithubRepositoriesDeleteProjectGithubRepositoryHandler = github_repositories.DeleteProjectGithubRepositoryHandlerFunc( diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 7a54a4cec..f42da1796 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -31,7 +31,7 @@ import ( // Service contains functions of GitHub Repositories service type Service interface { - AddGithubRepository(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) (*v1Models.GithubRepository, error) + AddGithubRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) EnableRepository(ctx context.Context, repositoryID string) error DisableRepository(ctx context.Context, repositoryID string) error ListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.ListGithubRepositories, error) @@ -70,21 +70,24 @@ func NewService(repo v1Repositories.Repository, pcgRepo projects_cla_groups.Repo } } -func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) (*v1Models.GithubRepository, error) { +func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "AddGitHubRepository", + "functionName": "AddGithubRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "claGroupID": utils.StringValue(input.ClaGroupID), "githubOrganizationName": utils.StringValue(input.GithubOrganizationName), - "repositoryGitHubID": utils.StringValue(input.RepositoryGithubID), + "repositoryGitHubID": input.RepositoryGithubID, + "repositoryGithubIds": input.RepositoryGithubIds, } + psc := v2ProjectService.GetClient() project, err := psc.GetProject(projectSFID) if err != nil { - log.WithFields(f).WithError(err).Warn("unable to load projectSFID") + log.WithFields(f).WithError(err).Warn("unable to load projectSFID from the platform project service") return nil, err } + var externalProjectID string if project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { @@ -92,6 +95,7 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i } else { externalProjectID = project.Parent } + allMappings, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(aws.StringValue(input.ClaGroupID)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to get project IDs for CLA Group") @@ -115,69 +119,104 @@ func (s *service) AddGithubRepository(ctx context.Context, projectSFID string, i if len(org.List) == 0 { return nil, errors.New("github app not installed on github organization") } - repoGithubID, err := strconv.ParseInt(utils.StringValue(input.RepositoryGithubID), 10, 64) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to convert repository github ID to an integer - invalid value") - return nil, err - } - ghRepo, err := github.GetRepositoryByExternalID(ctx, org.List[0].OrganizationInstallationID, repoGithubID) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to get repository by external ID") - return nil, err - } - // Check if exists already - existingRepositoryModel, lookupErr := s.GetRepositoryByName(ctx, utils.StringValue(ghRepo.FullName)) - if lookupErr != nil { - // If we have the repository not found error - this is ok - we are expecting this - if notFoundErr, ok := lookupErr.(*utils.GitHubRepositoryNotFound); ok { - log.WithFields(f).WithError(notFoundErr).Debugf("GitHub repository lookup didn't find a match for existing repository name: %s - ok to create", utils.StringValue(ghRepo.FullName)) - } else { - // Some other error - not good... - return nil, lookupErr - } + // Updated to process a list of repository IDs - take the list (may be empty) and add the single repository GH ID if it was set + repositoryIDList := input.RepositoryGithubIds + if input.RepositoryGithubID != "" { + repositoryIDList = append(repositoryIDList, input.RepositoryGithubID) } - // We already have an existing repository model with the same name - if existingRepositoryModel != nil && !existingRepositoryModel.Enabled { - msg := fmt.Sprintf("Github repository: %s previously disabled - will re-enabled... ", utils.StringValue(ghRepo.FullName)) - log.WithFields(f).Debug(msg) - enabled := true + // Remove any silly duplicates that may come + repositoryIDList = utils.RemoveDuplicates(repositoryIDList) - _, now := utils.CurrentTime() + var response []*v1Models.GithubRepository - v1Input := &v1Models.GithubRepositoryInput{ - Enabled: &enabled, - RepositoryOrganizationName: input.GithubOrganizationName, - RepositoryProjectID: input.ClaGroupID, - Note: fmt.Sprintf("re-enabling repository on %s.", now), + // For each repository ID provided... + // If this is slow, may want to optimize by making separate go routines for each item in the list + for _, repoID := range repositoryIDList { + // Convert the string value to an integer + repoGithubID, err := strconv.ParseInt(repoID, 10, 64) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to convert repository github ID %s to an integer - invalid value", repoID) + return nil, err } - // Update Repo details in case of any changes - updatedRepository, updateErr := s.repo.UpdateGithubRepository(ctx, existingRepositoryModel.RepositoryID, v1Input) - if updateErr != nil { - return nil, updateErr + log.WithFields(f).Debugf("loading GitHub repository by external id: %d", repoGithubID) + ghRepo, err := github.GetRepositoryByExternalID(ctx, org.List[0].OrganizationInstallationID, repoGithubID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to load repository by external ID: %d", repoGithubID) + return nil, err + } + log.WithFields(f).Debugf("loaded GitHub repository by external id: %d - url: %s", repoGithubID, utils.StringValue(ghRepo.URL)) + + // Check if this repository exists in our database + log.WithFields(f).Debugf("checking if GitHub repository by name: %s exists...", utils.StringValue(ghRepo.FullName)) + existingRepositoryModel, lookupErr := s.GetRepositoryByName(ctx, utils.StringValue(ghRepo.FullName)) + if lookupErr != nil { + // If we have the repository not found error - this is ok - we are expecting this + if notFoundErr, ok := lookupErr.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).WithError(notFoundErr).Debugf("GitHub repository lookup didn't find a match for existing repository name: %s - ok to create", utils.StringValue(ghRepo.FullName)) + } else { + // Some other error - not good... + log.WithFields(f).WithError(lookupErr).Warnf("GitHub repository lookup failed for repository name: %s", utils.StringValue(ghRepo.FullName)) + return nil, lookupErr + } } - return updatedRepository, nil - } - if existingRepositoryModel != nil { - msg := fmt.Sprintf("GitHub repository already exists with repository name: %s", utils.StringValue(ghRepo.FullName)) - log.WithFields(f).Warn(msg) - return nil, &utils.GitHubRepositoryExists{ - Message: msg, + // We already have an existing repository model with the same name + if existingRepositoryModel != nil { + if !existingRepositoryModel.Enabled { + msg := fmt.Sprintf("Github repository: %s previously disabled - will re-enabled... ", utils.StringValue(ghRepo.FullName)) + log.WithFields(f).Debug(msg) + enabled := true + + _, now := utils.CurrentTime() + + log.WithFields(f).Debugf("Updating GitHub repository - setting enabled: true, OrgName: %s, CLA Group ID: %s", + utils.StringValue(input.GithubOrganizationName), utils.StringValue(input.ClaGroupID)) + v1Input := &v1Models.GithubRepositoryInput{ + Enabled: &enabled, + RepositoryOrganizationName: input.GithubOrganizationName, + RepositoryProjectID: input.ClaGroupID, + Note: fmt.Sprintf("re-enabling repository on %s.", now), + } + + // Update Repo details in case of any changes + updatedRepository, updateErr := s.repo.UpdateGithubRepository(ctx, existingRepositoryModel.RepositoryID, v1Input) + if updateErr != nil { + log.WithFields(f).WithError(updateErr).Warnf("unable to update GitHub repository with name: %s, id: %s, using input: %+v", utils.StringValue(ghRepo.FullName), existingRepositoryModel.RepositoryID, v1Input) + return nil, updateErr + } + + // Append the results to our response model + response = append(response, updatedRepository) + } else { + log.WithFields(f).Warnf("GitHub repository already exists with repository name: %s and is already enabled - skipping update", utils.StringValue(ghRepo.FullName)) + continue + } + } else { + // No record exists... + log.WithFields(f).Debug("no existing GitHub repository configured - creating...") + in := &v1Models.GithubRepositoryInput{ + RepositoryExternalID: &repoID, // nolint + RepositoryName: ghRepo.FullName, + RepositoryOrganizationName: input.GithubOrganizationName, + RepositoryProjectID: input.ClaGroupID, + RepositoryType: aws.String("github"), + RepositoryURL: ghRepo.HTMLURL, + } + + addedModel, addErr := s.repo.AddGithubRepository(ctx, externalProjectID, projectSFID, in) + if addErr != nil { + return nil, addErr + } + + // Append the results to our response model + response = append(response, addedModel) } } - in := &v1Models.GithubRepositoryInput{ - RepositoryExternalID: input.RepositoryGithubID, - RepositoryName: ghRepo.FullName, - RepositoryOrganizationName: input.GithubOrganizationName, - RepositoryProjectID: input.ClaGroupID, - RepositoryType: aws.String("github"), - RepositoryURL: ghRepo.HTMLURL, - } - return s.repo.AddGithubRepository(ctx, externalProjectID, projectSFID, in) + return response, nil } func (s *service) EnableRepository(ctx context.Context, repositoryID string) error { From 9fd2fdb254ac2af29ddbe4adfae188d5ca31cd6e Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 19 Feb 2021 13:22:15 +0300 Subject: [PATCH 0104/1276] [#LFX3038] Bug/Get Company Project CLA - Catered for multi level (>3 levels) Projects to handle the ONAP use case Signed-off-by: wanyaland --- cla-backend-go/v2/company/service.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 46324ed6c..706e09135 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1145,8 +1145,27 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, proj } log.WithFields(f).Debug("loaded project SFID") + parentProject, err := psc.GetParentProject(projectSFID) + if err != nil { + return nil, err + } + + log.WithFields(f).Debug("loading parent project...") + + var parentProjectDetails *v2ProjectServiceModels.ProjectOutputDetailed + + if parentProject != "" { + parentProjectDetails, err = psc.GetProject(parentProject) + if err != nil { + return nil, err + } + } + log.WithFields(f).Debug("loaded parent Project") + var allProjectMapping []*projects_cla_groups.ProjectClaGroup - if projectDetails.ProjectType == FoundationType { + + // Determine query index (foundation or project) + if parentProjectDetails != nil && (parentProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup) { // get all projects for all cla group under foundation allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForFoundation(projectSFID) if err != nil { From 499543ff995d9220159a67b2db33710f19ba2d29 Mon Sep 17 00:00:00 2001 From: makkalot Date: Thu, 18 Feb 2021 16:02:54 +0200 Subject: [PATCH 0105/1276] include project corporate console links inside of the email templates + support the extra fetching of the data and the rendering Signed-off-by: makkalot --- cla-backend-go/Makefile | 2 +- cla-backend-go/approval_list/service.go | 4 +- cla-backend-go/cla_manager/handlers.go | 10 +- cla-backend-go/cla_manager/service.go | 6 +- .../emails/approval_list_templates.go | 10 +- .../emails/approval_list_templates_test.go | 13 +-- .../emails/cla_manager_templates.go | 105 ++++++------------ .../emails/cla_manager_templates_test.go | 76 ++++++------- cla-backend-go/emails/params.go | 85 ++++++++++++++ cla-backend-go/emails/params_test.go | 58 ++++++++++ cla-backend-go/emails/uitls.go | 56 +++++++++- .../emails/v2_cla_manager_templates.go | 103 +++++++++++++---- .../emails/v2_cla_manager_templates_test.go | 86 ++++++++------ cla-backend-go/v2/cla_manager/handlers.go | 2 +- cla-backend-go/v2/cla_manager/service.go | 79 ++++++------- 15 files changed, 453 insertions(+), 242 deletions(-) create mode 100644 cla-backend-go/emails/params.go create mode 100644 cla-backend-go/emails/params_test.go diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 25805ac3e..28124b956 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -293,6 +293,6 @@ $(LINT_TOOL): curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin $(LINT_VERSION) lint: $(LINT_TOOL) - @cd $(MAKEFILE_DIR) && echo "Running lint..." && $(LINT_TOOL) run --allow-parallel-runners --config=.golangci.yaml ./... && echo "Lint check passed." + @cd $(MAKEFILE_DIR) && echo "Running lint..." && $(LINT_TOOL) run --exclude="this method will not auto-escape HTML. Verify data is well formed" --allow-parallel-runners --config=.golangci.yaml ./... && echo "Lint check passed." @cd $(MAKEFILE_DIR) && ./check-headers.sh diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 1bb039f12..d9d5b34b7 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -269,7 +269,7 @@ func (s service) sendRequestEmailToRecipient(projectClaGroupRepository projects_ emails.RequestToAuthorizeTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, ContributorName: contributorName, @@ -327,7 +327,7 @@ func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Compan emails.ApprovalListRejectedTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, CLAManagers: emailCLAManagerParams, }, diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 764e728c4..0af1dcb55 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -893,7 +893,7 @@ func sendRequestAccessEmailToCLAManagers(companyModel *models.Company, claGroupM emails.RequestAccessToCLAManagersTemplate, emails.RequestAccessToCLAManagersTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, RequesterName: requesterName, @@ -927,7 +927,7 @@ func sendRequestApprovedEmailToCLAManagers(companyModel *models.Company, claGrou emails.RequestApprovedToCLAManagersTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, RequesterName: requesterName, @@ -957,7 +957,7 @@ func sendRequestApprovedEmailToRequester(companyModel *models.Company, claGroupM emails.RequestApprovedToRequesterTemplate, emails.RequestApprovedToRequesterTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: requesterName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), @@ -988,7 +988,7 @@ func sendRequestDeniedEmailToCLAManagers(companyModel *models.Company, claGroupM emails.RequestDeniedToCLAManagersTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, RequesterName: requesterName, @@ -1021,7 +1021,7 @@ func sendRequestDeniedEmailToRequester(companyModel *models.Company, claGroupMod emails.RequestDeniedToRequesterTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: requesterName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, }) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 6579c1370..7fc330117 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -368,7 +368,7 @@ func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel emails.ClaManagerAddedEToUserTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: requesterName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), @@ -399,7 +399,7 @@ func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGrou emails.ClaManagerAddedToCLAManagersTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, Name: name, @@ -503,7 +503,7 @@ func sendClaManagerDeleteEmailToCLAManagers(companyModel *models.Company, claGro emails.ClaManagerDeletedToCLAManagersTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, }, Name: name, diff --git a/cla-backend-go/emails/approval_list_templates.go b/cla-backend-go/emails/approval_list_templates.go index 2baf301c1..478cf4cac 100644 --- a/cla-backend-go/emails/approval_list_templates.go +++ b/cla-backend-go/emails/approval_list_templates.go @@ -16,8 +16,8 @@ const ( // ApprovalListRejectedTemplate is email template for ApprovalListRejectedTemplate = `

    Hello {{.RecipientName}},

    -

    This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

    -

    Your request to get added to the approval list from {{.CompanyName}} for {{.ProjectName}} was denied by one of the existing CLA Managers. +

    This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

    +

    Your request to get added to the approval list from {{.CompanyName}} for {{.Project.ExternalProjectName}} was denied by one of the existing CLA Managers. If you have further questions about this denial, please contact one of the existing CLA Managers from {{.CompanyName}} for {{.CompanyName}}:

      @@ -46,8 +46,8 @@ const (

      Hello {{.RecipientName}},

      This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}}.

      {{.ContributorName}} ({{.ContributorEmail}}) has requested to be added to the Approved List as an authorized contributor from -{{.CompanyName}} to the project {{.ProjectName}}. You are receiving this message as a CLA Manager from {{.CompanyName}} for -{{.ProjectName}}.

      +{{.CompanyName}} to the project {{.Project.ExternalProjectName}}. You are receiving this message as a CLA Manager from {{.CompanyName}} for +{{.Project.ExternalProjectName}}.

      {{if .OptionalMessage}}

      {{.ContributorName}} included the following message in the request:


      {{.OptionalMessage}}


      @@ -56,7 +56,7 @@ const ( log into the EasyCLA Corporate Console, where you can approve this user's request by selecting the 'Manage Approved List' and adding the contributor's email, the contributor's entire email domain, their GitHub ID or the entire GitHub Organization for the -repository. This will permit them to begin contributing to {{.ProjectName}} on behalf of {{.CompanyName}}.

      +repository. This will permit them to begin contributing to {{.Project.ExternalProjectName}} on behalf of {{.CompanyName}}.

      If you are not certain whether to add them to the Approved List, please reach out to them directly to discuss.

      ` ) diff --git a/cla-backend-go/emails/approval_list_templates_test.go b/cla-backend-go/emails/approval_list_templates_test.go index 270de899f..f2096da8f 100644 --- a/cla-backend-go/emails/approval_list_templates_test.go +++ b/cla-backend-go/emails/approval_list_templates_test.go @@ -14,7 +14,7 @@ func TestApprovalListRejectedTemplate(t *testing.T) { params := ApprovalListRejectedTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", + Project: CLAProjectParams{ExternalProjectName: "JohnsProject"}, CompanyName: "JohnsCompany", CLAManagers: []ClaManagerInfoParams{ {LfUsername: "LFUserName", Email: "LFEmail"}, @@ -34,11 +34,10 @@ func TestApprovalListRejectedTemplate(t *testing.T) { func TestRequestToAuthorizeTemplate(t *testing.T) { params := RequestToAuthorizeTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - CLAGroupName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", CLAManagers: []ClaManagerInfoParams{ {LfUsername: "LFUserName", Email: "LFEmail"}, }, @@ -56,7 +55,7 @@ func TestRequestToAuthorizeTemplate(t *testing.T) { assert.Contains(t, result, "regarding the project JohnsProjectExternal and CLA Group JohnsProject") assert.Contains(t, result, "ContributorNameValue (ContributorEmailValue) has requested") assert.Contains(t, result, "") - assert.Contains(t, result, "contributing to JohnsProject on behalf of JohnsCompany") + assert.Contains(t, result, "contributing to JohnsProjectExternal on behalf of JohnsCompany") params.OptionalMessage = "OptionalMessageValue" result, err = RenderTemplate(utils.V1, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go index fb11a71f9..ad357ef34 100644 --- a/cla-backend-go/emails/cla_manager_templates.go +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -5,45 +5,6 @@ package emails import "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" -// ClaManagerInfoParams represents the CLAManagerInfo used inside of the Email Templates -type ClaManagerInfoParams struct { - LfUsername string - Email string -} - -// CLAManagerTemplateParams includes the params for the CLAManagerTemplateParams -type CLAManagerTemplateParams struct { - RecipientName string - CLAGroupName string - // ProjectName is same as CLAGroupName in this context it's a legacy naming - // convention we used to have so we keep it here, for the actual project name - // we'll be using ExternalProjectName - ProjectName string - ExternalProjectName string - CompanyName string - FoundationName string - CLAManagers []ClaManagerInfoParams - // ChildProjectCount indicates how many childProjects are under this CLAGroup - // this is important for some of the email rendering knowing if claGroup has - // multiple children - ChildProjectCount int -} - -// GetProjectNameOrFoundation returns if the foundationName is set it gets back -// the foundation Name otherwise the ProjectName is returned -func (claParams CLAManagerTemplateParams) GetProjectNameOrFoundation() string { - if claParams.ChildProjectCount == 0 { - return claParams.ExternalProjectName - } - - // if multiple return the foundation if present - if claParams.FoundationName != "" { - return claParams.FoundationName - } - //default to project name if nothing works - return claParams.ExternalProjectName -} - // RemovedCLAManagerTemplateParams is email params for RemovedCLAManagerTemplate type RemovedCLAManagerTemplateParams struct { CLAManagerTemplateParams @@ -56,7 +17,7 @@ const ( RemovedCLAManagerTemplate = `

      Hello {{.RecipientName}},

      This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}}.

      -

      You have been removed as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}.

      +

      You have been removed as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}.

      If you have further questions about this, please contact one of the existing managers from {{.CompanyName}}:

        @@ -97,14 +58,14 @@ const ( // RequestAccessToCLAManagersTemplate is email template for RequestAccessToCLAManagersTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        You are currently listed as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you are able to maintain the -list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the list of -your company’s CLA Managers for {{.ProjectName}}.

        -

        {{.RequesterName}} ({{.RequesterEmail}}) has requested to be added as another CLA Manager from {{.CompanyName}} for {{.ProjectName}}. This would permit them to maintain the +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        You are currently listed as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that you are able to maintain the +list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of +your company’s CLA Managers for {{.Project.ExternalProjectName}}.

        +

        {{.RequesterName}} ({{.RequesterEmail}}) has requested to be added as another CLA Manager from {{.CompanyName}} for {{.Project.ExternalProjectName}}. This would permit them to maintain the lists of approved contributors and CLA Managers as well.

        If you want to permit this, please log into the EasyCLA Corporate Console, -select your company, then select the {{.ProjectName}} project. From the CLA Manager requests, you can approve this user as an +select your company, then select the {{.Project.ExternalProjectName}} project. From the CLA Manager requests, you can approve this user as an additional CLA Manager.

        ` ) @@ -122,10 +83,10 @@ const ( // RequestApprovedToCLAManagersTemplate is email template for RequestApprovedToCLAManagersTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        The following user has been approved as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that they can now -maintain the list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the -list of company’s CLA Managers for {{.ProjectName}}.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        The following user has been approved as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that they can now +maintain the list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the +list of company’s CLA Managers for {{.Project.ExternalProjectName}}.

        • {{.RequesterName}} ({{.RequesterEmail}})
        @@ -144,12 +105,12 @@ const ( // RequestApprovedToRequesterTemplate is email template for RequestApprovedToRequesterTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        You have now been approved as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you can now maintain the -list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the list of your -company’s CLA Managers for {{.ProjectName}}.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        You have now been approved as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that you can now maintain the +list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your +company’s CLA Managers for {{.Project.ExternalProjectName}}.

        To get started, please log into the EasyCLA Corporate Console, and select your -company and then the project {{.ProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

        +company and then the project {{.Project.ExternalProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

        ` ) @@ -166,9 +127,9 @@ const ( // RequestDeniedToCLAManagersTemplate is email template for RequestDeniedToCLAManagersTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        The following user has been denied as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that they will not -be able to maintain the list of employees allowed to contribute to {{.ProjectName}} on behalf of your company.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        The following user has been denied as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that they will not +be able to maintain the list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company.

        • {{.RequesterName}} ({{.RequesterEmail}})
        @@ -186,9 +147,9 @@ const ( // RequestDeniedToRequesterTemplate is email template for RequestDeniedToRequesterTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        You have been denied as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you can not maintain the -list of employees allowed to contribute to {{.ProjectName}} on behalf of your company.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        You have been denied as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that you can not maintain the +list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company.

        ` ) @@ -204,12 +165,12 @@ const ( // ClaManagerAddedEToUserTemplate is email template for ClaManagerAddedEToUserTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        You have been added as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that you can now maintain the -list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the list of your -company’s CLA Managers for {{.ProjectName}}.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        You have been added as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that you can now maintain the +list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your +company’s CLA Managers for {{.Project.ExternalProjectName}}.

        To get started, please log into the EasyCLA Corporate Console, and select your -company and then the project {{.ProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

        +company and then the project {{.Project.ExternalProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

        ` ) @@ -226,10 +187,10 @@ const ( // ClaManagerAddedToCLAManagersTemplate is email template for ClaManagerAddedToCLAManagersTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        The following user has been added as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}. This means that they can now -maintain the list of employees allowed to contribute to {{.ProjectName}} on behalf of your company, as well as view and manage the -list of company’s CLA Managers for {{.ProjectName}}.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        The following user has been added as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that they can now +maintain the list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the +list of company’s CLA Managers for {{.Project.ExternalProjectName}}.

        • {{.Name}} ({{.Email}})
        @@ -249,7 +210,7 @@ const ( // ClaManagerDeletedToCLAManagersTemplate is template for ClaManagerDeletedToCLAManagersTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.ProjectName}}.

        -

        {{.Name}} ({{.Email}}) has been removed as a CLA Manager from {{.CompanyName}} for the project {{.ProjectName}}.

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        +

        {{.Name}} ({{.Email}}) has been removed as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}.

        ` ) diff --git a/cla-backend-go/emails/cla_manager_templates_test.go b/cla-backend-go/emails/cla_manager_templates_test.go index 0d962af55..913882052 100644 --- a/cla-backend-go/emails/cla_manager_templates_test.go +++ b/cla-backend-go/emails/cla_manager_templates_test.go @@ -13,10 +13,10 @@ import ( func TestRemovedCLAManagerTemplate(t *testing.T) { params := RemovedCLAManagerTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", CLAManagers: []ClaManagerInfoParams{ {LfUsername: "LFUserName", Email: "LFEmail"}, }, @@ -33,7 +33,7 @@ func TestRemovedCLAManagerTemplate(t *testing.T) { // even if the foundation is set we should show the project name // because 0 child projects under the claGroup - params.CLAManagerTemplateParams.FoundationName = "CNCF" + params.CLAManagerTemplateParams.Project.FoundationName = "CNCF" result, err = RenderTemplate(utils.V1, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, params) assert.NoError(t, err) @@ -52,10 +52,10 @@ func TestRemovedCLAManagerTemplate(t *testing.T) { func TestRequestAccessToCLAManagersTemplate(t *testing.T) { params := RequestAccessToCLAManagersTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, RequesterName: "RequesterName", RequesterEmail: "RequesterEmail", @@ -73,17 +73,17 @@ func TestRequestAccessToCLAManagersTemplate(t *testing.T) { assert.Contains(t, result, "RequesterName (RequesterEmail) has requested") assert.Contains(t, result, "another CLA Manager from JohnsCompany for JohnsProject") assert.Contains(t, result, "") - assert.Contains(t, result, "then select the JohnsProject project") + assert.Contains(t, result, "then select the JohnsProjectExternal project") } func TestRequestApprovedToCLAManagersTemplate(t *testing.T) { params := RequestApprovedToCLAManagersTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, RequesterName: "RequesterName", RequesterEmail: "RequesterEmail", @@ -103,10 +103,10 @@ func TestRequestApprovedToCLAManagersTemplate(t *testing.T) { func TestRequestApprovedToRequesterTemplateParams(t *testing.T) { params := RequestApprovedToRequesterTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, CorporateURL: "http://CorporateURL.com", } @@ -126,10 +126,10 @@ func TestRequestApprovedToRequesterTemplateParams(t *testing.T) { func TestRequestDeniedToCLAManagersTemplate(t *testing.T) { params := RequestDeniedToCLAManagersTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, RequesterName: "RequesterName", RequesterEmail: "RequesterEmail", @@ -148,10 +148,10 @@ func TestRequestDeniedToCLAManagersTemplate(t *testing.T) { func TestRequestDeniedToRequesterTemplate(t *testing.T) { params := RequestDeniedToRequesterTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, } @@ -167,10 +167,10 @@ func TestRequestDeniedToRequesterTemplate(t *testing.T) { func TestClaManagerAddedEToUserTemplate(t *testing.T) { params := ClaManagerAddedEToUserTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, CorporateURL: "http://CorporateURL.com", } @@ -190,10 +190,10 @@ func TestClaManagerAddedEToUserTemplate(t *testing.T) { func TestClaManagerAddedToCLAManagersTemplate(t *testing.T) { params := ClaManagerAddedToCLAManagersTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, Name: "John", Email: "john@example.com", @@ -214,10 +214,10 @@ func TestClaManagerAddedToCLAManagersTemplate(t *testing.T) { func TestClaManagerDeletedToCLAManagersTemplate(t *testing.T) { params := ClaManagerDeletedToCLAManagersTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsProject", + CompanyName: "JohnsCompany", }, Name: "John", Email: "john@example.com", diff --git a/cla-backend-go/emails/params.go b/cla-backend-go/emails/params.go new file mode 100644 index 000000000..a54884c87 --- /dev/null +++ b/cla-backend-go/emails/params.go @@ -0,0 +1,85 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "fmt" + "html/template" + "net/url" + "path" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) + +// ClaManagerInfoParams represents the CLAManagerInfo used inside of the Email Templates +type ClaManagerInfoParams struct { + LfUsername string + Email string +} + +// CLAProjectParams is useful struct which keeps cla group project info and also +// know how to render it's corporate console url +type CLAProjectParams struct { + ExternalProjectName string + ProjectSFID string + FoundationName string + FoundationSFID string + SignedAtFoundationLevel bool + CorporateConsole string +} + +// GetProjectFullURL has the logic how to return back it's full url in corporate console +// it checks at SignedAtFoundationLevel flag as well for this specific kind of projects +func (p CLAProjectParams) GetProjectFullURL() template.HTML { + if p.CorporateConsole == "" { + return template.HTML(p.ExternalProjectName) + } + + u, err := url.Parse(p.CorporateConsole) + if err != nil { + log.Warnf("couldn't parse the console url, probably wrong configuration used : %s : %v", p.CorporateConsole, err) + // at least return the project name so we don't have broken email + return template.HTML(p.ExternalProjectName) + } + + var projectConsolePathURL string + fullURLHtml := `%s` + if p.SignedAtFoundationLevel { + u.Path = path.Join(u.Path, "foundation", p.FoundationSFID, "cla") + projectConsolePathURL = u.String() + } else { + u.Path = path.Join(u.Path, "foundation", p.FoundationSFID, "project", p.ProjectSFID, "cla") + projectConsolePathURL = u.String() + } + + return template.HTML(fmt.Sprintf(fullURLHtml, projectConsolePathURL, p.ExternalProjectName)) +} + +// CLAManagerTemplateParams includes the params for the CLAManagerTemplateParams +type CLAManagerTemplateParams struct { + RecipientName string + CompanyName string + CLAGroupName string + Project CLAProjectParams + CLAManagers []ClaManagerInfoParams + // ChildProjectCount indicates how many childProjects are under this CLAGroup + // this is important for some of the email rendering knowing if claGroup has + // multiple children + ChildProjectCount int +} + +// GetProjectNameOrFoundation returns if the foundationName is set it gets back +// the foundation Name otherwise the ProjectName is returned +func (claParams CLAManagerTemplateParams) GetProjectNameOrFoundation() string { + if claParams.ChildProjectCount == 0 { + return claParams.Project.ExternalProjectName + } + + // if multiple return the foundation if present + if claParams.Project.FoundationName != "" { + return claParams.Project.FoundationName + } + //default to project name if nothing works + return claParams.Project.ExternalProjectName +} diff --git a/cla-backend-go/emails/params_test.go b/cla-backend-go/emails/params_test.go new file mode 100644 index 000000000..25c9da550 --- /dev/null +++ b/cla-backend-go/emails/params_test.go @@ -0,0 +1,58 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "html/template" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCLAProjectParams_GetProjectFullURL(t *testing.T) { + testCases := []struct { + name string + projectParams CLAProjectParams + result template.HTML + }{ + { + name: "empty url", + projectParams: CLAProjectParams{ + ExternalProjectName: "JohnsProject", + }, + result: template.HTML("JohnsProject"), + }, + { + name: "foundation level project", + projectParams: CLAProjectParams{ + ExternalProjectName: "JohnsProject", + ProjectSFID: "projectSFIDValue", + FoundationName: "CNCF", + FoundationSFID: "FoundationSFIDValue", + SignedAtFoundationLevel: true, + CorporateConsole: "https://corporate.dev.lfcla.com", + }, + result: template.HTML(`JohnsProject`), + }, + { + name: "standalone project", + projectParams: CLAProjectParams{ + ExternalProjectName: "JohnsProject", + ProjectSFID: "projectSFIDValue", + FoundationName: "CNCF", + FoundationSFID: "FoundationSFIDValue", + SignedAtFoundationLevel: false, + CorporateConsole: "https://corporate.dev.lfcla.com", + }, + result: template.HTML(`JohnsProject`), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + resul := tc.projectParams.GetProjectFullURL() + assert.Equal(tt, tc.result, resul) + }) + } +} diff --git a/cla-backend-go/emails/uitls.go b/cla-backend-go/emails/uitls.go index 3c5a3d0b3..b1bf2af89 100644 --- a/cla-backend-go/emails/uitls.go +++ b/cla-backend-go/emails/uitls.go @@ -4,9 +4,12 @@ package emails import ( + "context" "errors" + "fmt" log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/project" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" ) @@ -23,10 +26,14 @@ func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups. } params.CLAGroupName = projectCLAGroup.ClaGroupName - params.ProjectName = projectCLAGroup.ClaGroupName - params.FoundationName = projectCLAGroup.FoundationName - params.ExternalProjectName = projectCLAGroup.ProjectName - + params.Project = CLAProjectParams{ + ExternalProjectName: projectCLAGroup.ProjectName, + ProjectSFID: projectSFID, + FoundationName: projectCLAGroup.FoundationName, + FoundationSFID: projectCLAGroup.FoundationSFID, + SignedAtFoundationLevel: false, + CorporateConsole: "", + } projects, err := repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) if err != nil { return err @@ -35,3 +42,44 @@ func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups. params.ChildProjectCount = len(projects) return nil } + +// PrefillCLAProjectParams for each supplied projectSFIDs gets the claGroup info + checks if the project is signed at +// foundation level which is important for email rendering +func PrefillCLAProjectParams(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, corporateConsole string) ([]CLAProjectParams, error) { + if len(projectSFIDs) == 0 { + return nil, nil + } + + var claProjectParams []CLAProjectParams + // keeping a cache so we can safe some of the remote svc calls + signedAtFoundationLevelCache := map[string]bool{} + for _, pSFID := range projectSFIDs { + projectCLAGroup, err := repository.GetClaGroupIDForProject(pSFID) + if err != nil { + return nil, fmt.Errorf("fetching project : %s failed: %v", pSFID, err) + } + + params := CLAProjectParams{ + ExternalProjectName: projectCLAGroup.ProjectName, + ProjectSFID: pSFID, + FoundationName: projectCLAGroup.FoundationName, + FoundationSFID: projectCLAGroup.FoundationSFID, + CorporateConsole: corporateConsole, + } + signed, found := signedAtFoundationLevelCache[projectCLAGroup.FoundationSFID] + if found { + params.SignedAtFoundationLevel = signed + } + + signedResult, err := projectService.SignedAtFoundationLevel(context.Background(), projectCLAGroup.FoundationSFID) + if err != nil { + return nil, fmt.Errorf("fetching the SignedAtFoundationLevel for foundation : %s failed : %v", projectCLAGroup.FoundationSFID, err) + } + params.SignedAtFoundationLevel = signedResult + signedAtFoundationLevelCache[projectCLAGroup.FoundationSFID] = signedResult + + claProjectParams = append(claProjectParams, params) + } + + return claProjectParams, nil +} diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index bcec889e8..803fef3d5 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -6,6 +6,8 @@ package emails import ( "strings" + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" ) @@ -38,7 +40,6 @@ type V2OrgAdminTemplateParams struct { CLAManagerTemplateParams SenderName string SenderEmail string - ProjectList []string CorporateConsole string } @@ -49,23 +50,37 @@ const ( V2OrgAdminTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the CLA setup and signing process for {{.CompanyName}}.

        -

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following projects:

        +

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following project:

          - {{range .ProjectList}} -
        • {{.}}
        • - {{end}} +
        • {{.Project.ExternalProjectName}}

        Before the contribution can be accepted, your organization must sign a CLA. -Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.ProjectName}}

        +Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.Project.GetProjectFullURL}}

        If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

        Please notify the user once CLA setup is complete.

        ` ) +// RenderV2OrgAdminTemplate renders V2OrgAdminTemplate +func RenderV2OrgAdminTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params V2OrgAdminTemplateParams) (string, error) { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { + return "", err + } + + projectParams, err := PrefillCLAProjectParams(repository, projectService, []string{projectSFID}, params.CorporateConsole) + if err != nil { + return "", err + } + + params.Project = projectParams[0] + + return RenderTemplate(utils.V2, V2OrgAdminTemplateName, V2OrgAdminTemplate, params) +} + // V2ContributorToOrgAdminTemplateParams is email template params for V2ContributorToOrgAdminTemplate type V2ContributorToOrgAdminTemplateParams struct { CLAManagerTemplateParams - ProjectNames []string + Projects []CLAProjectParams UserDetails string CorporateConsole string } @@ -76,21 +91,34 @@ const ( // V2ContributorToOrgAdminTemplate is email template for V2ContributorToOrgAdminTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}

        +

        This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.ExternalProjectName}}{{end}}

        The following contributor is requesting to sign CLA for organization: {{.CompanyName}}

        {{.UserDetails}}

        Before the user contribution can be accepted, your organization must sign a CLA. -

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the projects {{range $index, $projectName := .ProjectNames}}{{if $index}},{{end}}{{$projectName}}{{end}}.

        +

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the projects {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

        ` ) +// RenderV2ContributorToOrgAdminTemplate renders V2ContributorToOrgAdminTemplate +func RenderV2ContributorToOrgAdminTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ContributorToOrgAdminTemplateParams) (string, error) { + // prefill the projects data + projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsole) + if err != nil { + return "", err + } + + params.Projects = projects + + return RenderTemplate(utils.V2, V2ContributorToOrgAdminTemplateName, + V2ContributorToOrgAdminTemplate, params) +} + // V2CLAManagerDesigneeCorporateTemplateParams is email params for V2CLAManagerDesigneeCorporateTemplate type V2CLAManagerDesigneeCorporateTemplateParams struct { CLAManagerTemplateParams SenderName string SenderEmail string - ProjectList []string CorporateConsole string } @@ -103,30 +131,36 @@ const (

        This is a notification email from EasyCLA regarding the CLA setup and signing process for {{.CompanyName}}.

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following projects:

          - {{range .ProjectList}} -
        • {{.}}
        • - {{end}} +
        • {{.Project.ExternalProjectName}}

        Before the contribution can be accepted, your organization must sign a CLA. -Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.ProjectName}}

        +Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.Project.GetProjectFullURL}}

        If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

        Please notify the user once CLA setup is complete.

        ` ) // RenderV2CLAManagerDesigneeCorporateTemplate renders V2CLAManagerDesigneeCorporateTemplate -func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups.Repository, projectSFID string, params V2CLAManagerDesigneeCorporateTemplateParams) (string, error) { +func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params V2CLAManagerDesigneeCorporateTemplateParams) (string, error) { if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { return "", err } + projects, err := PrefillCLAProjectParams(repository, projectService, []string{projectSFID}, params.CorporateConsole) + if err != nil { + return "", err + } + + // assing the prefilled project + params.Project = projects[0] + return RenderTemplate(utils.V2, V2CLAManagerDesigneeCorporateTemplateName, V2CLAManagerDesigneeCorporateTemplate, params) } // V2ToCLAManagerDesigneeTemplateParams is email params for V2ToCLAManagerDesigneeTemplate type V2ToCLAManagerDesigneeTemplateParams struct { RecipientName string - ProjectNames []string + Projects []CLAProjectParams ContributorID string ContributorName string CorporateConsole string @@ -134,11 +168,16 @@ type V2ToCLAManagerDesigneeTemplateParams struct { // GetProjectsOrProject returns the single Project or comma saparated projects if more than one func (p V2ToCLAManagerDesigneeTemplateParams) GetProjectsOrProject() string { - if len(p.ProjectNames) == 1 { - return " " + p.ProjectNames[0] + if len(p.Projects) == 1 { + return " " + p.Projects[0].ExternalProjectName + } + + var projectNames []string + for _, p := range p.Projects { + projectNames = append(projectNames, p.ExternalProjectName) } - return "s " + strings.Join(p.ProjectNames, ", ") + return "s " + strings.Join(projectNames, ", ") } const ( @@ -151,11 +190,25 @@ const (

        The following contributor is requesting to sign CLA for organization:

        {{.ContributorID}} ({{.ContributorName}})

        Before the user contribution can be accepted, your organization must sign a CLA. -

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project{{.GetProjectsOrProject}}.

        +

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s) {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

        ` ) +// RenderV2ToCLAManagerDesigneeTemplate renders V2ToCLAManagerDesigneeTemplate +func RenderV2ToCLAManagerDesigneeTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ToCLAManagerDesigneeTemplateParams) (string, error) { + // prefill the projects data + projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsole) + if err != nil { + return "", err + } + + params.Projects = projects + + return RenderTemplate(utils.V2, V2ToCLAManagerDesigneeTemplateName, + V2ToCLAManagerDesigneeTemplate, params) +} + // V2DesigneeToUserWithNoLFIDTemplateParams is email params for V2DesigneeToUserWithNoLFIDTemplate type V2DesigneeToUserWithNoLFIDTemplateParams struct { CLAManagerTemplateParams @@ -175,7 +228,7 @@ const (

        {{.RequesterUserName}} ({{.RequesterEmail}})

        Before the user's contribution can be accepted, your organization must sign a CLA.

        Please click on Accept Invite to create your LF Login.

        -

        After login, you will be redirected to this portal {{.CorporateConsole}} where you can sign the CLA for the project {{.GetProjectNameOrFoundation}}.

        +

        After login, you will be redirected to this portal {{.CorporateConsole}} where you can sign the CLA for the project {{.Project.GetProjectFullURL}}.

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

        ` ) @@ -186,6 +239,9 @@ func RenderV2DesigneeToUserWithNoLFIDTemplate(repository projects_cla_groups.Rep return "", err } + // assign the corporate console so we can show the link of the project + params.Project.CorporateConsole = params.CorporateConsole + return RenderTemplate(utils.V2, V2DesigneeToUserWithNoLFIDTemplateName, V2DesigneeToUserWithNoLFIDTemplate, params) } @@ -204,8 +260,8 @@ const ( V2CLAManagerToUserWithNoLFIDTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the Project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}} in the EasyCLA system.

        -

        User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for Project {{.ProjectName}} but was unable to identify your account details in -the EasyCLA system. In order to become a CLA Manager for Project {{.ProjectName}}, you will need to accept invite below. +

        User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for Project {{.Project.ExternalProjectName}} but was unable to identify your account details in +the EasyCLA system. In order to become a CLA Manager for Project {{.Project.ExternalProjectName}}, you will need to accept invite below. Once complete, notify the user {{.RequesterUserName}} and they will be able to add you as a CLA Manager.

        Accept Invite

        ` @@ -216,7 +272,6 @@ func RenderV2CLAManagerToUserWithNoLFIDTemplate(repository projects_cla_groups.R params := V2CLAManagerToUserWithNoLFIDTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: recipientName, - ProjectName: projectName, }, RequesterUserName: requesterName, RequesterEmail: requesterEmail, diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/emails/v2_cla_manager_templates_test.go index ee8170b66..fb5f974ac 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates_test.go +++ b/cla-backend-go/emails/v2_cla_manager_templates_test.go @@ -14,7 +14,7 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { params := V2ContributorApprovalRequestTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", + Project: CLAProjectParams{ExternalProjectName: "JohnsProject"}, CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, @@ -47,13 +47,17 @@ func TestV2OrgAdminTemplate(t *testing.T) { params := V2OrgAdminTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + Project: CLAProjectParams{ + ExternalProjectName: "JohnsProject", + ProjectSFID: "ProjectSFIDValue", + FoundationSFID: "FoundationSFIDValue", + CorporateConsole: "http://CorporateConsole.com", + }, + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", }, SenderName: "SenderNameValue", SenderEmail: "SenderEmailValue", - ProjectList: []string{"Project1", "Project2"}, CorporateConsole: "http://CorporateConsole.com", } @@ -64,21 +68,23 @@ func TestV2OrgAdminTemplate(t *testing.T) { assert.Contains(t, result, "signing process for JohnsCompany") assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") assert.Contains(t, result, "Corporate CLA for JohnsCompany") - assert.Contains(t, result, "
      • Project1
      • ") - assert.Contains(t, result, "
      • Project2
      • ") + assert.Contains(t, result, "
      • JohnsProject
      • ") assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") - assert.Contains(t, result, "sign the CLA for this project JohnsProject") + assert.Contains(t, result, `sign the CLA for this project JohnsProject`) } func TestV2ContributorToOrgAdminTemplate(t *testing.T) { params := V2ContributorToOrgAdminTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", + Project: CLAProjectParams{ExternalProjectName: "JohnsProject"}, CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, - ProjectNames: []string{"Project1", "Project2"}, + Projects: []CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, UserDetails: "UserDetailsValue", CorporateConsole: "http://CorporateConsole.com", } @@ -91,20 +97,24 @@ func TestV2ContributorToOrgAdminTemplate(t *testing.T) { assert.Contains(t, result, "sign CLA for organization: JohnsCompany") assert.Contains(t, result, "

        UserDetailsValue

        ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, "CLA for any of the projects Project1,Project2") + assert.Contains(t, result, `CLA for any of the projects Project1,Project2`) } func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { params := V2CLAManagerDesigneeCorporateTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + Project: CLAProjectParams{ + ExternalProjectName: "JohnsProject", + FoundationSFID: "FoundationSFIDValue", + ProjectSFID: "ProjectSFIDValue", + CorporateConsole: "http://CorporateConsole.com", + }, + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", }, SenderName: "SenderNameValue", SenderEmail: "SenderEmailValue", - ProjectList: []string{"Project1", "Project2"}, CorporateConsole: "http://CorporateConsole.com", } @@ -115,16 +125,18 @@ func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { assert.Contains(t, result, "CLA setup and signing process for JohnsCompany") assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") assert.Contains(t, result, "Corporate CLA for JohnsCompany") - assert.Contains(t, result, "
      • Project1
      • ") - assert.Contains(t, result, "
      • Project2
      • ") + assert.Contains(t, result, "
      • JohnsProject
      • ") assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") - assert.Contains(t, result, "sign the CLA for this project JohnsProject") + assert.Contains(t, result, `sign the CLA for this project JohnsProject`) } func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { params := V2ToCLAManagerDesigneeTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectNames: []string{"Project1", "Project2"}, + RecipientName: "JohnsClaManager", + Projects: []CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, ContributorID: "ContributorIDValue", ContributorName: "ContributorNameValue", CorporateConsole: "http://CorporateConsole.com", @@ -137,9 +149,11 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.Contains(t, result, "regarding the projects Project1, Project2") assert.Contains(t, result, "

        ContributorIDValue (ContributorNameValue)

        ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, "CLA for one of the projects Project1, Project2") + assert.Contains(t, result, `CLA for one of the project(s) Project1,Project2`) - params.ProjectNames = []string{"Project1"} + params.Projects = []CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + } result, err = RenderTemplate(utils.V1, V2ToCLAManagerDesigneeTemplateName, V2ToCLAManagerDesigneeTemplate, params) assert.NoError(t, err) @@ -147,18 +161,22 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.Contains(t, result, "regarding the project Project1") assert.Contains(t, result, "

        ContributorIDValue (ContributorNameValue)

        ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, "CLA for one of the project Project1") + assert.Contains(t, result, `CLA for one of the project(s) Project1`) } func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { params := V2DesigneeToUserWithNoLFIDTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ + ExternalProjectName: "JohnsProjectExternal", + CorporateConsole: "https://corporate.dev.lfcla.com", + FoundationSFID: "FoundationSFIDValue", + SignedAtFoundationLevel: true, + }, + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", }, RequesterUserName: "RequesterUserNameValue", RequesterEmail: "RequesterEmailValue", @@ -170,17 +188,17 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager,") assert.Contains(t, result, "The following contributor would like to contribute to JohnsProjectExternal on behalf of your organization: JohnsCompany.") - assert.Contains(t, result, "you will be redirected to this portal https://corporate.dev.lfcla.com where you can sign the CLA for the project JohnsProjectExternal") + assert.Contains(t, result, "you will be redirected to this portal https://corporate.dev.lfcla.com ") + assert.Contains(t, result, `where you can sign the CLA for the project JohnsProjectExternal`) } func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { params := V2CLAManagerToUserWithNoLFIDTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - ProjectName: "JohnsProject", - ExternalProjectName: "JohnsProjectExternal", - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + RecipientName: "JohnsClaManager", + Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", }, RequesterUserName: "RequesterUserNameValue", RequesterEmail: "RequesterEmailValue", diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index cf06296a0..9b6275776 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -328,7 +328,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C } claManagerDesignee, err := service.CreateCLAManagerRequest(ctx, params.Body.ContactAdmin, v1CompanyModel.CompanyID, params.ProjectSFID, params.Body.UserEmail.String(), - *params.Body.FullName, authUser, LfxPortalURL) + *params.Body.FullName, authUser, LfxPortalURL, CorporateConsoleV2URL) if err != nil { statusCode := buildErrorStatusCode(err) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index b33e917a7..68f91c525 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -98,7 +98,7 @@ type Service interface { DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) - CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) + CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL, CorporateConsole string) (*models.ClaManagerDesignee, error) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, CorporateConsoleV2URL string) error CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) @@ -651,7 +651,7 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl } // CreateCLAManagerRequest service method -func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL string) (*models.ClaManagerDesignee, error) { +func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL, corporateConsole string) (*models.ClaManagerDesignee, error) { f := logrus.Fields{ "functionName": "cla_manager.service.CreateCLAManagerRequest", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -724,7 +724,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool for _, admin := range scopes.Userroles { log.WithFields(f).Debugf("sending email to organization admin: %+v", admin) - sendEmailToOrgAdmin(admin.Contact.EmailAddress, admin.Contact.Name, v1CompanyModel.CompanyName, []string{projectSF.Name}, authUser.Email, authUser.UserName, LfxPortalURL) + sendEmailToOrgAdmin(s.projectCGRepo, s.projectService, admin.Contact.EmailAddress, admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) // Make a note in the event log s.eventService.LogEvent(&events.LogEventArgs{ EventType: events.ContributorNotifyCompanyAdminType, @@ -791,7 +791,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debugf("sending Email to CLA Manager Designee email: %s ", userEmail) designeeName := fmt.Sprintf("%s %s", lfxUser.FirstName, lfxUser.LastName) - sendEmailToCLAManagerDesigneeCorporate(ctx, s.projectCGRepo, LfxPortalURL, v1CompanyModel.CompanyName, projectSF.ID, []string{projectSF.Name}, userEmail, designeeName, authUser.Email, authUser.UserName) + sendEmailToCLAManagerDesigneeCorporate(ctx, s.projectCGRepo, s.projectService, corporateConsole, v1CompanyModel.CompanyName, projectSF.ID, projectSF.Name, userEmail, designeeName, authUser.Email, authUser.UserName) log.WithFields(f).Debug("creating a contributor notify CLA designee log event...") // Make a note in the event log @@ -900,6 +900,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com } var projectSFs []string + var projectSFIDs []string for _, pcg := range projectCLAGroups { log.WithFields(f).Debugf("Getting salesforce project by SFID: %s ", pcg.ProjectSFID) projectSF, projectErr := projectService.GetProject(pcg.ProjectSFID) @@ -909,6 +910,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return nil, projectErr } projectSFs = append(projectSFs, projectSF.Name) + projectSFIDs = append(projectSFIDs, projectSF.ID) } var designeeScopes []*models.ClaManagerDesignee @@ -942,7 +944,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return nil, adminErr } - contributorEmailToOrgAdmin(userService.GetPrimaryEmail(adminUser), admin.Contact.Name, organization.Name, projectSFs, userModel, LfxPortalURL) + contributorEmailToOrgAdmin(s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, organization.Name, projectSFIDs, userModel, LfxPortalURL) designeeScope := models.ClaManagerDesignee{ Email: strfmt.Email(admin.Contact.EmailAddress), Name: admin.Contact.Name, @@ -1015,10 +1017,10 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com log.Debugf("Sending Email to CLA Manager Designee email: %s ", userEmail) if contributor.LFUsername != "" && contributor.LFEmail != "" && len(projectSFs) > 0 { - sendEmailToCLAManagerDesignee(ctx, CorporateConsoleV2URL, organization.Name, projectSFs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) + sendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) } else { contributorUserName, contributorEmail := getContributorPublicEmail(contributor) - sendEmailToCLAManagerDesignee(ctx, CorporateConsoleV2URL, organization.Name, projectSFs, userEmail, user.Name, contributorUserName, contributorEmail) + sendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributorUserName, contributorEmail) } log.Debugf("CLA Manager designee created : %+v", designeeScopes) @@ -1244,24 +1246,19 @@ func (s *service) isSigned(ctx context.Context, companyModel *v1Models.Company, return false, nil } -func sendEmailToOrgAdmin(adminEmail string, admin string, company string, projectNames []string, senderEmail string, senderName string, corporateConsole string) { +func sendEmailToOrgAdmin(repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, admin string, company string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) { subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", company) recipients := []string{adminEmail} - body, err := emails.RenderTemplate( - utils.V2, emails.V2OrgAdminTemplateName, - emails.V2OrgAdminTemplate, - emails.V2OrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: admin, - CompanyName: company, - ProjectName: projectNames[0], - }, - SenderName: senderName, - SenderEmail: senderEmail, - ProjectList: projectNames, - CorporateConsole: corporateConsole, + body, err := emails.RenderV2OrgAdminTemplate(repository, projectService, projectSFID, emails.V2OrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: admin, + CompanyName: company, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, }, - ) + SenderName: senderName, + SenderEmail: senderEmail, + CorporateConsole: corporateConsole, + }) if err != nil { log.Warnf("rendering email template : %s failed : %v", emails.V2OrgAdminTemplateName, err) return @@ -1274,22 +1271,17 @@ func sendEmailToOrgAdmin(adminEmail string, admin string, company string, projec } } -func contributorEmailToOrgAdmin(adminEmail string, admin string, company string, projectNames []string, contributor *v1Models.User, corporateConsole string) { +func contributorEmailToOrgAdmin(repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, admin string, company string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) { subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", company, getBestUserName(contributor)) recipients := []string{adminEmail} - body, err := emails.RenderTemplate( - utils.V2, emails.V2ContributorToOrgAdminTemplateName, - emails.V2ContributorToOrgAdminTemplate, - emails.V2ContributorToOrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: admin, - CompanyName: company, - }, - ProjectNames: projectNames, - UserDetails: getFormattedUserDetails(contributor), - CorporateConsole: corporateConsole, + body, err := emails.RenderV2ContributorToOrgAdminTemplate(repository, projectService, projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: admin, + CompanyName: company, }, - ) + UserDetails: getFormattedUserDetails(contributor), + CorporateConsole: corporateConsole, + }) if err != nil { log.Warnf("rendering template : %s failed : %v", emails.V2ContributorToOrgAdminTemplateName, err) return @@ -1302,13 +1294,13 @@ func contributorEmailToOrgAdmin(adminEmail string, admin string, company string, } } -func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, corporateConsole string, companyName string, projectSFID string, projectNames []string, designeeEmail string, designeeName string, senderEmail string, senderName string) { +func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) { f := logrus.Fields{ "functionName": "cla_manager.service.sendEmailToCLAManagerDesigneeCorporate", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "corporateConsole": corporateConsole, "companyName": companyName, - "projectNames": strings.Join(projectNames, ","), + "projectName": projectName, "designeeEmail": designeeEmail, "designeeName": designeeName, "senderEmail": senderEmail, @@ -1317,15 +1309,13 @@ func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository proj subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) recipients := []string{designeeEmail} - body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(repository, projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ + body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(repository, projectService, projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: designeeName, CompanyName: companyName, - ProjectName: projectNames[0], }, SenderName: senderName, SenderEmail: senderEmail, - ProjectList: projectNames, CorporateConsole: corporateConsole, }) if err != nil { @@ -1340,7 +1330,7 @@ func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository proj } } -func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, companyName string, projectNames []string, designeeEmail string, designeeName string, contributorID string, contributorName string) { +func sendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) { f := logrus.Fields{ "functionName": "cla_manager.service.sendEmailToCLAManagerDesignee", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1356,12 +1346,9 @@ func sendEmailToCLAManagerDesignee(ctx context.Context, corporateConsole string, subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", companyName, contributorID) recipients := []string{designeeEmail} - body, err := emails.RenderTemplate(utils.V2, - emails.V2ToCLAManagerDesigneeTemplateName, - emails.V2ToCLAManagerDesigneeTemplate, + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ RecipientName: designeeName, - ProjectNames: projectNames, ContributorID: contributorID, ContributorName: contributorName, CorporateConsole: corporateConsole, @@ -1396,7 +1383,7 @@ func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_ emails.V2DesigneeToUserWithNoLFIDTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: userWithNoLFIDName, - ProjectName: projectName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: organizationName, }, RequesterUserName: requesterUsername, From d156847273fe507ef8d473e50c7183d57d1ed28a Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 19 Feb 2021 13:29:00 -0800 Subject: [PATCH 0106/1276] Updated Golang Lint Version to v1.37.0 (#2686) Signed-off-by: David Deal --- cla-backend-go/Makefile | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 28124b956..862bb8d54 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -19,7 +19,7 @@ LDFLAGS=-ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X m BUILD_TAGS=-tags aws_lambda LINT_TOOL=$(shell go env GOPATH)/bin/golangci-lint -LINT_VERSION=v1.29.0 +LINT_VERSION=v1.37.0 SWAGGER_TOOL_VERSION=v0.24.0 GO_PKGS=$(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') @@ -52,6 +52,9 @@ setup-dev: tool-setup @echo "Installing cover..." gobin golang.org/x/tools/cmd/cover @echo "Installing multi-file-swagger tool..." + @echo "Downloading golangci-lint version $(LINT_VERSION)..." + @# Latest releases: https://github.com/golangci/golangci-lint/releases + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin $(LINT_VERSION) cd $(dir $(realpath $(firstword $(MAKEFILE_LIST))))swagger && pip3 install virtualenv && virtualenv .venv && source .venv/bin/activate && pip3 install -r requirements.txt setup_deploy: setup-deploy @@ -287,12 +290,7 @@ build-functional-tests-mac: deps env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(FUNCTIONAL_TESTS_BIN)-mac cmd/functional_tests/main.go @chmod +x $(FUNCTIONAL_TESTS_BIN)-mac -$(LINT_TOOL): - @echo "Downloading golangci-lint version $(LINT_VERSION)..." - @# Latest releases: https://github.com/golangci/golangci-lint/releases - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin $(LINT_VERSION) - -lint: $(LINT_TOOL) - @cd $(MAKEFILE_DIR) && echo "Running lint..." && $(LINT_TOOL) run --exclude="this method will not auto-escape HTML. Verify data is well formed" --allow-parallel-runners --config=.golangci.yaml ./... && echo "Lint check passed." +lint: + @cd $(MAKEFILE_DIR) && echo "Running lint..." && $(LINT_TOOL) --version && $(LINT_TOOL) run --exclude="this method will not auto-escape HTML. Verify data is well formed" --allow-parallel-runners --config=.golangci.yaml ./... && echo "Lint check passed." @cd $(MAKEFILE_DIR) && ./check-headers.sh From fd60da9b80a8b66cb1853efcf6856b86dab4250f Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Sat, 20 Feb 2021 00:29:08 +0300 Subject: [PATCH 0107/1276] [#LFX3038] Feature/Project Heirachy (#2685) - Updated enroll and unenroll sf projects with cla group - Supported 3 level heirachy Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/v2/cla_groups/helpers.go | 37 +++++++++++++++++-------- cla-backend-go/v2/cla_groups/service.go | 26 +++++++++++++++-- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 50f8cca2c..e45f62fd5 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -71,6 +71,7 @@ github.com/communitybridge/easycla v1.0.107 h1:dktHAji1yJ1nMEu54z4paPWOM4Q7A9rry github.com/communitybridge/easycla v1.0.117 h1:o+rdmcNgZeMQ/N8HV/d5apNIBrkYH7eyM9UUYnEzewo= github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNutidR+yy2HwW0= github.com/communitybridge/easycla v1.0.123 h1:Lh5i/9aajrTYItxNpVCmi9T1yyIfnQIOk0tC2Wtslvk= +github.com/communitybridge/easycla v1.0.133 h1:aJulQGLLRISCMsZcCP4aIE8xGtHoBNm/EmA00n3NYVA= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index dfe13ce9d..7122b6f34 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -205,12 +205,19 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI return err } - // Let's check the foundation provided - does it have a parent? Only allowed parent is TLF - if foundationProjectDetails.Parent != "" && !isLFParent { - log.WithFields(f).Warnf("input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) - return fmt.Errorf("bad request: input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) + for _, projectSFID := range projectSFIDList { + projectDetails, projErr := psc.GetProject(projectSFID) + if projErr != nil { + return err + } + + if foundationProjectDetails.Parent != "" && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { + msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", + foundationProjectDetails.Parent, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + log.WithFields(f).Warnf(msg) + return fmt.Errorf(msg) + } + } // Comment out the below as we want to support stand-alone projects @@ -308,11 +315,19 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } - if foundationProjectDetails.Parent != "" && !isLFParent { - log.WithFields(f).Warnf("input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) - return fmt.Errorf("bad request: input validation failure - foundation_sfid of %s has a parent other than %s or %s which is: %s", - foundationSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC, foundationProjectDetails.Parent) + for _, projectSFID := range projectSFIDList { + projectDetails, projErr := psc.GetProject(projectSFID) + if projErr != nil { + return err + } + + if foundationProjectDetails.Parent != "" && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { + msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", + foundationProjectDetails.Parent, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + log.WithFields(f).Warnf(msg) + return fmt.Errorf(msg) + } + } // Comment out the below as we want to support stand-alone projects diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index d760f1e24..f1515ff3a 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -36,6 +36,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" v1Template "github.com/communitybridge/easycla/cla-backend-go/template" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + v2ProjectServiceModels "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/models" "github.com/sirupsen/logrus" ) @@ -340,7 +341,7 @@ func (s *service) UpdateCLAGroup(ctx context.Context, claGroupModel *v1Models.Cl } // ListClaGroupsForFoundationOrProject returns the CLA Group list for the specified foundation ID -func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, projectOrFoundationSFID string) (*models.ClaGroupListSummary, error) { +func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, projectOrFoundationSFID string) (*models.ClaGroupListSummary, error) { // nolint f := logrus.Fields{ "functionName": "ListClaGroupsForFoundationOrProject", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -365,12 +366,31 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje return nil, &utils.SFProjectNotFound{ProjectSFID: projectOrFoundationSFID} } + // Try and check if parent exists and projectType + var parentDetails *v2ProjectServiceModels.ProjectOutputDetailed + var parentDetailErr error + + if sfProjectModelDetails.Parent != "" { + var parentSFID string + // Use utility function that considers TLF and LF Projects, LLC + parentSFID, parentDetailErr = v2ProjectService.GetClient().GetParentProject(projectOrFoundationSFID) + if parentDetailErr != nil { + return nil, parentDetailErr + } + + // Get Parent + parentDetails, parentDetailErr = v2ProjectService.GetClient().GetProject(parentSFID) + if parentDetailErr != nil { + return nil, parentDetailErr + } + } + // Lookup the foundation name - need this if we were a project - need to lookup parent ID/Name var foundationID = sfProjectModelDetails.ID var foundationName = sfProjectModelDetails.Name // If it's a project... - if sfProjectModelDetails.ProjectType == utils.ProjectTypeProject { + if sfProjectModelDetails.ProjectType == utils.ProjectTypeProject || (parentDetails != nil && (parentDetails.ProjectType == utils.ProjectTypeProjectGroup && sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup)) { // Since this is a project and not a foundation, we'll want to set he parent foundation ID and name (which is // our parent in this case) log.WithFields(f).Debug("found 'project' in platform project service.") @@ -413,7 +433,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1CLAGroupData) } - } else if sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup { + } else if parentDetails != nil && (parentDetails.ProjectType == utils.ProjectTypeProjectGroup && sfProjectModelDetails.ProjectType != utils.ProjectTypeProjectGroup) { log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") projectCLAGroups, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) if lookupErr != nil { From bff3158ef891abe4ac9447f440b3137d2aa099fc Mon Sep 17 00:00:00 2001 From: David Deal Date: Sat, 20 Feb 2021 17:57:58 -0800 Subject: [PATCH 0108/1276] Refactoring of CLA Manager Emails (#2687) - Made functions public - Moved Tests to common test package to access now public functions - Changed SendEmailToCLAManager to accept a model rather than so many parameters - Updated logging, added logrus fields, added with error Signed-off-by: David Deal --- .../v2_cla_manager_templates_test.go | 66 ++-- cla-backend-go/v2/cla_manager/emails.go | 291 ++++++++++++++++++ cla-backend-go/v2/cla_manager/service.go | 291 +++--------------- 3 files changed, 363 insertions(+), 285 deletions(-) rename cla-backend-go/{emails => tests}/v2_cla_manager_templates_test.go (77%) create mode 100644 cla-backend-go/v2/cla_manager/emails.go diff --git a/cla-backend-go/emails/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go similarity index 77% rename from cla-backend-go/emails/v2_cla_manager_templates_test.go rename to cla-backend-go/tests/v2_cla_manager_templates_test.go index fb5f974ac..97e8bd13e 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -1,20 +1,22 @@ // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT -package emails +package tests import ( "testing" + "github.com/communitybridge/easycla/cla-backend-go/emails" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/stretchr/testify/assert" ) func TestV2ContributorApprovalRequestTemplate(t *testing.T) { - params := V2ContributorApprovalRequestTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + params := emails.V2ContributorApprovalRequestTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProject"}, + Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProject"}, CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, @@ -22,7 +24,7 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { CorporateConsoleV2URL: "http://CorporateConsoleV2URL.com", } - result, err := RenderTemplate(utils.V1, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2ContributorApprovalRequestTemplateName, emails.V2ContributorApprovalRequestTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -33,7 +35,7 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { params.SigningEntityName = "SigningEntityNameValue" - result, err = RenderTemplate(utils.V1, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, + result, err = emails.RenderTemplate(utils.V1, emails.V2ContributorApprovalRequestTemplateName, emails.V2ContributorApprovalRequestTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -44,10 +46,10 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { } func TestV2OrgAdminTemplate(t *testing.T) { - params := V2OrgAdminTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + params := emails.V2OrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ + Project: emails.CLAProjectParams{ ExternalProjectName: "JohnsProject", ProjectSFID: "ProjectSFIDValue", FoundationSFID: "FoundationSFIDValue", @@ -61,7 +63,7 @@ func TestV2OrgAdminTemplate(t *testing.T) { CorporateConsole: "http://CorporateConsole.com", } - result, err := RenderTemplate(utils.V1, V2OrgAdminTemplateName, V2OrgAdminTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2OrgAdminTemplateName, emails.V2OrgAdminTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -74,14 +76,14 @@ func TestV2OrgAdminTemplate(t *testing.T) { } func TestV2ContributorToOrgAdminTemplate(t *testing.T) { - params := V2ContributorToOrgAdminTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + params := emails.V2ContributorToOrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProject"}, + Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProject"}, CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, - Projects: []CLAProjectParams{ + Projects: []emails.CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, }, @@ -89,7 +91,7 @@ func TestV2ContributorToOrgAdminTemplate(t *testing.T) { CorporateConsole: "http://CorporateConsole.com", } - result, err := RenderTemplate(utils.V1, V2ContributorToOrgAdminTemplateName, V2ContributorToOrgAdminTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2ContributorToOrgAdminTemplateName, emails.V2ContributorToOrgAdminTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -101,10 +103,10 @@ func TestV2ContributorToOrgAdminTemplate(t *testing.T) { } func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { - params := V2CLAManagerDesigneeCorporateTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + params := emails.V2CLAManagerDesigneeCorporateTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ + Project: emails.CLAProjectParams{ ExternalProjectName: "JohnsProject", FoundationSFID: "FoundationSFIDValue", ProjectSFID: "ProjectSFIDValue", @@ -118,7 +120,7 @@ func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { CorporateConsole: "http://CorporateConsole.com", } - result, err := RenderTemplate(utils.V1, V2CLAManagerDesigneeCorporateTemplateName, V2CLAManagerDesigneeCorporateTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2CLAManagerDesigneeCorporateTemplateName, emails.V2CLAManagerDesigneeCorporateTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -131,9 +133,9 @@ func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { } func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { - params := V2ToCLAManagerDesigneeTemplateParams{ + params := emails.V2ToCLAManagerDesigneeTemplateParams{ RecipientName: "JohnsClaManager", - Projects: []CLAProjectParams{ + Projects: []emails.CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, }, @@ -142,7 +144,7 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { CorporateConsole: "http://CorporateConsole.com", } - result, err := RenderTemplate(utils.V1, V2ToCLAManagerDesigneeTemplateName, V2ToCLAManagerDesigneeTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2ToCLAManagerDesigneeTemplateName, emails.V2ToCLAManagerDesigneeTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -151,10 +153,10 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") assert.Contains(t, result, `CLA for one of the project(s) Project1,Project2`) - params.Projects = []CLAProjectParams{ + params.Projects = []emails.CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, } - result, err = RenderTemplate(utils.V1, V2ToCLAManagerDesigneeTemplateName, V2ToCLAManagerDesigneeTemplate, + result, err = emails.RenderTemplate(utils.V1, emails.V2ToCLAManagerDesigneeTemplateName, emails.V2ToCLAManagerDesigneeTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") @@ -166,10 +168,10 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { } func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { - params := V2DesigneeToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + params := emails.V2DesigneeToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ + Project: emails.CLAProjectParams{ ExternalProjectName: "JohnsProjectExternal", CorporateConsole: "https://corporate.dev.lfcla.com", FoundationSFID: "FoundationSFIDValue", @@ -183,7 +185,7 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { CorporateConsole: "https://corporate.dev.lfcla.com", } - result, err := RenderTemplate(utils.V1, V2DesigneeToUserWithNoLFIDTemplateName, V2DesigneeToUserWithNoLFIDTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2DesigneeToUserWithNoLFIDTemplateName, emails.V2DesigneeToUserWithNoLFIDTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager,") @@ -193,10 +195,10 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { } func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { - params := V2CLAManagerToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + params := emails.V2CLAManagerToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, + Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, @@ -204,7 +206,7 @@ func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { RequesterEmail: "RequesterEmailValue", } - result, err := RenderTemplate(utils.V1, V2CLAManagerToUserWithNoLFIDTemplateName, V2CLAManagerToUserWithNoLFIDTemplate, + result, err := emails.RenderTemplate(utils.V1, emails.V2CLAManagerToUserWithNoLFIDTemplateName, emails.V2CLAManagerToUserWithNoLFIDTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go new file mode 100644 index 000000000..eab1b55ab --- /dev/null +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -0,0 +1,291 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package cla_manager + +import ( + "context" + "fmt" + "strings" + + "github.com/communitybridge/easycla/cla-backend-go/emails" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/utils" + v2AcsService "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" + "github.com/sirupsen/logrus" +) + +// EmailToCLAManagerModel data model for sending emails to CLA Managers +type EmailToCLAManagerModel struct { + Contributor *v1Models.User + CLAManagerName string + CLAManagerEmail string + CompanyName string + CLAGroupName string + CorporateConsoleURL string +} + +// SendEmailToCLAManager handles sending an email to the specified CLA Manager +func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel) { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendEmailToCLAManager", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "contributorUsername": input.Contributor.Username, + "contributorLFUsername": input.Contributor.LfUsername, + "contributorGitHubID": input.Contributor.GithubID, + "contributorGitHubUsername": input.Contributor.GithubUsername, + "contributorLFEmail": input.Contributor.LfEmail, + "contributorEmails": strings.Join(input.Contributor.Emails, ","), + "claManagerName": input.CLAManagerName, + "claManagerEmail": input.CLAManagerEmail, + "companyName": input.CompanyName, + "claGroupName": input.CLAGroupName, + } + + subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(input.Contributor)) + recipients := []string{input.CLAManagerEmail} + body, err := emails.RenderTemplate( + utils.V2, emails.V2ContributorApprovalRequestTemplateName, + emails.V2ContributorApprovalRequestTemplate, + emails.V2ContributorApprovalRequestTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: input.CLAGroupName, + CompanyName: input.CompanyName, + CLAGroupName: input.CLAGroupName, + }, + SigningEntityName: input.CompanyName, + UserDetails: getFormattedUserDetails(input.Contributor), + CorporateConsoleV2URL: input.CorporateConsoleURL, + }, + ) + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering email template: %s", emails.V2ContributorApprovalRequestTemplateName) + return + } + + log.WithFields(f).Debugf("sending email with subject: %s to recipients: %+v...", subject, recipients) + err = utils.SendEmail(subject, body, recipients) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + } else { + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + } +} + +// SendEmailToOrgAdmin sends an email to the organization admin +func (s *service) SendEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendEmailToOrgAdmin", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "adminEmail": adminEmail, + "adminName": adminName, + "companyName": companyName, + "projectName": projectName, + "projectSFID": projectSFID, + "senderName": senderName, + "senderEmail": senderEmail, + "corporateConsoleURL": corporateConsole, + } + + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) + recipients := []string{adminEmail} + body, err := emails.RenderV2OrgAdminTemplate(repository, projectService, projectSFID, emails.V2OrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: adminName, + CompanyName: companyName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, + }, + SenderName: senderName, + SenderEmail: senderEmail, + CorporateConsole: corporateConsole, + }) + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering email template : %s failed : %v", emails.V2OrgAdminTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + } else { + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + } +} + +func (s *service) ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendEmailToOrgAdmin", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "adminEmail": adminEmail, + "adminName": adminName, + "companyName": companyName, + "contributorName": contributor.Username, + "contributorGitHubID": contributor.GithubID, + "contributorGitHubUsername": contributor.GithubUsername, + "contributorLFUsername": contributor.LfUsername, + "contributorLFEmail": contributor.LfEmail, + "contributorEmails": strings.Join(contributor.Emails, ","), + "corporateConsoleURL": corporateConsole, + } + + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", companyName, getBestUserName(contributor)) + recipients := []string{adminEmail} + body, err := emails.RenderV2ContributorToOrgAdminTemplate(repository, projectService, projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: adminName, + CompanyName: companyName, + }, + UserDetails: getFormattedUserDetails(contributor), + CorporateConsole: corporateConsole, + }) + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering template : %s failed : %v", emails.V2ContributorToOrgAdminTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + } else { + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + } +} + +func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendEmailToCLAManagerDesigneeCorporate", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "corporateConsole": corporateConsole, + "companyName": companyName, + "projectName": projectName, + "designeeEmail": designeeEmail, + "designeeName": designeeName, + "senderEmail": senderEmail, + "senderName": senderName, + } + + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) + recipients := []string{designeeEmail} + body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(repository, projectService, projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: designeeName, + CompanyName: companyName, + }, + SenderName: senderName, + SenderEmail: senderEmail, + CorporateConsole: corporateConsole, + }) + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering template : %s : failed: %v", emails.V2CLAManagerDesigneeCorporateTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + } else { + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + } +} + +func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendEmailToCLAManagerDesignee", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "corporateConsole": corporateConsole, + "companyName": companyName, + "projectNames": strings.Join(projectNames, ","), + "designeeEmail": designeeEmail, + "designeeName": designeeName, + "contributorID": contributorID, + "contributorName": contributorName, + } + + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", + companyName, contributorID) + recipients := []string{designeeEmail} + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, + emails.V2ToCLAManagerDesigneeTemplateParams{ + RecipientName: designeeName, + ContributorID: contributorID, + ContributorName: contributorName, + CorporateConsole: corporateConsole, + }) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering template : %s failed : %v", emails.V2ToCLAManagerDesigneeTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + } else { + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + } +} + +func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string, corporateConsoleV2URL string) error { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendDesigneeEmailToUserWithNoLFID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "userWithNoLFIDName": userWithNoLFIDName, + "userWithNoLFIDEmail": userWithNoLFIDEmail, + "organizationID": organizationID, + "projectID": utils.StringValue(projectID), + "role": role, + "corporateConsoleV2URL": corporateConsoleV2URL, + } + + subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager for project: %s ", projectName) + body, err := emails.RenderV2DesigneeToUserWithNoLFIDTemplate(repository, *projectID, + emails.V2DesigneeToUserWithNoLFIDTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: userWithNoLFIDName, + Project: emails.CLAProjectParams{ExternalProjectName: projectName}, + CompanyName: organizationName, + }, + RequesterUserName: requesterUsername, + RequesterEmail: requesterEmail, + CorporateConsole: corporateConsoleV2URL, + }) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering template : %s failed : %v", emails.V2DesigneeToUserWithNoLFIDTemplateName, err) + return err + } + + acsClient := v2AcsService.GetClient() + automate := false + log.WithFields(f).Debug("sending user invite request...") + return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, projectID, organizationID, "userinvite", &subject, &body, automate) +} + +// sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID +func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error { + f := logrus.Fields{ + "functionName": "cla_manager.service.SendEmailToUserWithNoLFID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectName": projectName, + "requesterUsername": requesterUsername, + "requesterEmail": requesterEmail, + "userWithNoLFIDName": userWithNoLFIDName, + "userWithNoLFIDEmail": userWithNoLFIDEmail, + "organizationID": organizationID, + "projectID": utils.StringValue(projectID), + "role": role, + } + + // subject string, body string, recipients []string + subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", role) + body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(repository, userWithNoLFIDName, projectName, *projectID, requesterUsername, requesterEmail) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("rendering email : %s failed : %v", emails.V2CLAManagerToUserWithNoLFIDTemplateName, err) + return err + } + acsClient := v2AcsService.GetClient() + automate := false + + log.WithFields(f).Debug("sending user invite request...") + return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, projectID, organizationID, "userinvite", &subject, &body, automate) +} diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 68f91c525..8b4591ce8 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -11,8 +11,6 @@ import ( "sync" "time" - "github.com/communitybridge/easycla/cla-backend-go/emails" - "github.com/go-openapi/strfmt" "github.com/sirupsen/logrus" @@ -42,30 +40,20 @@ import ( ) var ( - //ErrSalesForceProjectNotFound returned error if salesForce Project not found - ErrSalesForceProjectNotFound = errors.New("salesforce project not found") //ErrCLACompanyNotFound returned if EasyCLA company not found ErrCLACompanyNotFound = errors.New("company not found") - //ErrGitHubRepoNotFound returned if GH Repos is not found - ErrGitHubRepoNotFound = errors.New("github repo not found") //ErrCLAUserNotFound returned if EasyCLA User is not found ErrCLAUserNotFound = errors.New("cla user not found") - //ErrCLAManagersNotFound when cla managers arent found for given project and company - ErrCLAManagersNotFound = errors.New("cla managers not found") //ErrLFXUserNotFound when user-service fails to find user ErrLFXUserNotFound = errors.New("lfx user not found") //ErrNoLFID thrown when users dont have an LFID ErrNoLFID = errors.New("user has no LF Login") - //ErrNotInOrg when user is not in organization - ErrNotInOrg = errors.New("user not in organization") //ErrNoOrgAdmins when No admins found for organization ErrNoOrgAdmins = errors.New("no admins in company") //ErrRoleScopeConflict thrown if user already has role scope ErrRoleScopeConflict = errors.New("user is already cla-manager") //ErrCLAManagerDesigneeConflict when user is already assigned cla-manager-designee role ErrCLAManagerDesigneeConflict = errors.New("user already assigned cla-manager") - //ErrScopeNotFound returns error when getting scopeID - ErrScopeNotFound = errors.New("scope not found") //ErrProjectSigned returns error if project already signed ErrProjectSigned = errors.New("project already signed") //ErrClaGroupNotFound returns error if cla group not found @@ -75,8 +63,6 @@ var ( ) const ( - // NoAccount represents user with no company - NoAccount = "Individual - No Account" // used for filtering when fetching contributor email excludedNoReplyEmails = "noreply.github.com" ) @@ -101,7 +87,17 @@ type Service interface { CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL, CorporateConsole string) (*models.ClaManagerDesignee, error) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, CorporateConsoleV2URL string) error CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) + ProjectCompanySignedOrNot(ctx context.Context, signedAtFoundation bool, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, companyModel *v1Models.Company) error IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) + + // Email Functions + SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel) + SendEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) + ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) + SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) + SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) + SendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string, corporateConsoleV2URL string) error + SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error } // NewService returns instance of CLA Manager service @@ -724,7 +720,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool for _, admin := range scopes.Userroles { log.WithFields(f).Debugf("sending email to organization admin: %+v", admin) - sendEmailToOrgAdmin(s.projectCGRepo, s.projectService, admin.Contact.EmailAddress, admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) + s.SendEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, admin.Contact.EmailAddress, admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) // Make a note in the event log s.eventService.LogEvent(&events.LogEventArgs{ EventType: events.ContributorNotifyCompanyAdminType, @@ -752,7 +748,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool msg := fmt.Sprintf("User: %s does not have an LF Login", userEmail) log.WithFields(f).Warn(msg) // Send email - sendEmailErr := sendEmailToUserWithNoLFID(ctx, s.projectCGRepo, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole) + sendEmailErr := s.SendEmailToUserWithNoLFID(ctx, s.projectCGRepo, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole) if sendEmailErr != nil { log.WithFields(f).Warnf("Error sending email: %+v", sendEmailErr) return nil, sendEmailErr @@ -791,7 +787,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debugf("sending Email to CLA Manager Designee email: %s ", userEmail) designeeName := fmt.Sprintf("%s %s", lfxUser.FirstName, lfxUser.LastName) - sendEmailToCLAManagerDesigneeCorporate(ctx, s.projectCGRepo, s.projectService, corporateConsole, v1CompanyModel.CompanyName, projectSF.ID, projectSF.Name, userEmail, designeeName, authUser.Email, authUser.UserName) + s.SendEmailToCLAManagerDesigneeCorporate(ctx, s.projectCGRepo, s.projectService, corporateConsole, v1CompanyModel.CompanyName, projectSF.ID, projectSF.Name, userEmail, designeeName, authUser.Email, authUser.UserName) log.WithFields(f).Debug("creating a contributor notify CLA designee log event...") // Make a note in the event log @@ -944,7 +940,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return nil, adminErr } - contributorEmailToOrgAdmin(s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, organization.Name, projectSFIDs, userModel, LfxPortalURL) + s.ContributorEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, organization.Name, projectSFIDs, userModel, LfxPortalURL) designeeScope := models.ClaManagerDesignee{ Email: strfmt.Email(admin.Contact.EmailAddress), Name: admin.Contact.Name, @@ -954,7 +950,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return designeeScopes, nil } - signedError := s.ProjectComapnySignnedOrNot(ctx, f, signedAtFoundation, projectCLAGroups, companyModel) + signedError := s.ProjectCompanySignedOrNot(ctx, signedAtFoundation, projectCLAGroups, companyModel) if signedError != nil { return nil, signedError } @@ -981,7 +977,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com } contibutorEmail := GetNonNoReplyUserEmail(contributor.UserEmails) - sendErr := sendDesigneeEmailToUserWithNoLFID(ctx, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee", LfxPortalURL) + sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee", LfxPortalURL) if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) @@ -1017,10 +1013,10 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com log.Debugf("Sending Email to CLA Manager Designee email: %s ", userEmail) if contributor.LFUsername != "" && contributor.LFEmail != "" && len(projectSFs) > 0 { - sendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) + s.SendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) } else { contributorUserName, contributorEmail := getContributorPublicEmail(contributor) - sendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributorUserName, contributorEmail) + s.SendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributorUserName, contributorEmail) } log.Debugf("CLA Manager designee created : %+v", designeeScopes) @@ -1028,7 +1024,17 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return designeeScopes, nil } -func (s *service) ProjectComapnySignnedOrNot(ctx context.Context, f logrus.Fields, signedAtFoundation bool, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, companyModel *v1Models.Company) error { + +func (s *service) ProjectCompanySignedOrNot(ctx context.Context, signedAtFoundation bool, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, companyModel *v1Models.Company) error { + f := logrus.Fields{ + "functionName": "cla_manager.service.ProjectCompanySignedOrNot", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "signedAtFoundation": signedAtFoundation, + "companyID": companyModel.CompanyID, + "companySFID": companyModel.CompanyExternalID, + "companyName": companyModel.CompanyName, + } + if signedAtFoundation { foundationSFID := projectCLAGroups[0].FoundationSFID @@ -1098,53 +1104,19 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode log.Debugf("Sending notification emails to CLA Managers: %+v", notifyCLAManagers.List) for _, claManager := range notifyCLAManagers.List { - sendEmailToCLAManager(ctx, claManager.Name, claManager.Email.String(), userModel, notifyCLAManagers.CompanyName, notifyCLAManagers.SigningEntityName, notifyCLAManagers.ClaGroupName, CorporateConsoleV2URL) + s.SendEmailToCLAManager(ctx, &EmailToCLAManagerModel{ + Contributor: userModel, + CLAManagerName: claManager.Name, + CLAManagerEmail: claManager.Email.String(), + CompanyName: notifyCLAManagers.CompanyName, + CLAGroupName: notifyCLAManagers.ClaGroupName, + CorporateConsoleURL: CorporateConsoleV2URL, + }) } return nil } -func sendEmailToCLAManager(ctx context.Context, manager string, managerEmail string, userModel *v1Models.User, company, signingEntityName, claGroupName, corporateConsoleV2URL string) { - f := logrus.Fields{ - "functionName": "cla_manager.service.sendEmailToCLAManager", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "manager": manager, - "managerEmail": managerEmail, - "user": userModel.Username, - "companyName": company, - "signingEntityName": signingEntityName, - "claGroupName": claGroupName, - } - subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(userModel)) - recipients := []string{managerEmail} - body, err := emails.RenderTemplate( - utils.V2, emails.V2ContributorApprovalRequestTemplateName, - emails.V2ContributorApprovalRequestTemplate, - emails.V2ContributorApprovalRequestTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: manager, - CompanyName: company, - CLAGroupName: claGroupName, - }, - SigningEntityName: signingEntityName, - UserDetails: getFormattedUserDetails(userModel), - CorporateConsoleV2URL: corporateConsoleV2URL, - }, - ) - if err != nil { - log.Warnf("rendering email template : %s failed : %v", emails.V2ContributorApprovalRequestTemplateName, err) - return - } - - log.WithFields(f).Debugf("sending email with subject: %s to recipients: %+v...", subject, recipients) - err = utils.SendEmail(subject, body, recipients) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) - } else { - log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) - } -} - // getBestUserName is a helper function to extract what information we can from the user record for purposes of displaying the user's name func getBestUserName(model *v1Models.User) string { if model.Username != "" { @@ -1246,193 +1218,6 @@ func (s *service) isSigned(ctx context.Context, companyModel *v1Models.Company, return false, nil } -func sendEmailToOrgAdmin(repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, admin string, company string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) { - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", company) - recipients := []string{adminEmail} - body, err := emails.RenderV2OrgAdminTemplate(repository, projectService, projectSFID, emails.V2OrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: admin, - CompanyName: company, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - }, - SenderName: senderName, - SenderEmail: senderEmail, - CorporateConsole: corporateConsole, - }) - if err != nil { - log.Warnf("rendering email template : %s failed : %v", emails.V2OrgAdminTemplateName, err) - return - } - err = utils.SendEmail(subject, body, recipients) - if err != nil { - log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) - } else { - log.Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) - } -} - -func contributorEmailToOrgAdmin(repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, admin string, company string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) { - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", company, getBestUserName(contributor)) - recipients := []string{adminEmail} - body, err := emails.RenderV2ContributorToOrgAdminTemplate(repository, projectService, projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: admin, - CompanyName: company, - }, - UserDetails: getFormattedUserDetails(contributor), - CorporateConsole: corporateConsole, - }) - if err != nil { - log.Warnf("rendering template : %s failed : %v", emails.V2ContributorToOrgAdminTemplateName, err) - return - } - err = utils.SendEmail(subject, body, recipients) - if err != nil { - log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) - } else { - log.Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) - } -} - -func sendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) { - f := logrus.Fields{ - "functionName": "cla_manager.service.sendEmailToCLAManagerDesigneeCorporate", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "corporateConsole": corporateConsole, - "companyName": companyName, - "projectName": projectName, - "designeeEmail": designeeEmail, - "designeeName": designeeName, - "senderEmail": senderEmail, - "senderName": senderName, - } - - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) - recipients := []string{designeeEmail} - body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(repository, projectService, projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: designeeName, - CompanyName: companyName, - }, - SenderName: senderName, - SenderEmail: senderEmail, - CorporateConsole: corporateConsole, - }) - if err != nil { - log.Warnf("rendering template : %s : failed: %v", emails.V2CLAManagerDesigneeCorporateTemplateName, err) - return - } - err = utils.SendEmail(subject, body, recipients) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) - } else { - log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) - } -} - -func sendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) { - f := logrus.Fields{ - "functionName": "cla_manager.service.sendEmailToCLAManagerDesignee", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "corporateConsole": corporateConsole, - "companyName": companyName, - "projectNames": strings.Join(projectNames, ","), - "designeeEmail": designeeEmail, - "designeeName": designeeName, - "contributorID": contributorID, - "contributorName": contributorName, - } - - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", - companyName, contributorID) - recipients := []string{designeeEmail} - body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, - emails.V2ToCLAManagerDesigneeTemplateParams{ - RecipientName: designeeName, - ContributorID: contributorID, - ContributorName: contributorName, - CorporateConsole: corporateConsole, - }) - - if err != nil { - log.Warnf("rendering template : %s failed : %v", emails.V2ToCLAManagerDesigneeTemplateName, err) - return - } - err = utils.SendEmail(subject, body, recipients) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) - } else { - log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) - } -} - -func sendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string, corporateConsoleV2URL string) error { - f := logrus.Fields{ - "functionName": "cla_manager.service.sendDesigneeEmailToUserWithNoLFID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "userWithNoLFIDName": userWithNoLFIDName, - "userWithNoLFIDEmail": userWithNoLFIDEmail, - "organizationID": organizationID, - "projectID": utils.StringValue(projectID), - "role": role, - "corporateConsoleV2URL": corporateConsoleV2URL, - } - - subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager for project: %s ", projectName) - body, err := emails.RenderV2DesigneeToUserWithNoLFIDTemplate(repository, *projectID, - emails.V2DesigneeToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: userWithNoLFIDName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: organizationName, - }, - RequesterUserName: requesterUsername, - RequesterEmail: requesterEmail, - CorporateConsole: corporateConsoleV2URL, - }) - - if err != nil { - log.Warnf("rendering template : %s failed : %v", emails.V2DesigneeToUserWithNoLFIDTemplateName, err) - return err - } - - acsClient := v2AcsService.GetClient() - automate := false - log.WithFields(f).Debug("sending user invite request...") - return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, "project|organization", projectID, organizationID, "userinvite", &subject, &body, automate) - -} - -// sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID -func sendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error { - f := logrus.Fields{ - "functionName": "cla_manager.service.sendEmailToUserWithNoLFID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectName": projectName, - "requesterUsername": requesterUsername, - "requesterEmail": requesterEmail, - "userWithNoLFIDName": userWithNoLFIDName, - "userWithNoLFIDEmail": userWithNoLFIDEmail, - "organizationID": organizationID, - "projectID": utils.StringValue(projectID), - "role": role, - } - - // subject string, body string, recipients []string - subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", role) - body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(repository, userWithNoLFIDName, projectName, *projectID, requesterUsername, requesterEmail) - - if err != nil { - log.Warnf("rendering email : %s failed : %v", emails.V2CLAManagerToUserWithNoLFIDTemplateName, err) - return err - } - acsClient := v2AcsService.GetClient() - automate := false - - log.WithFields(f).Debug("sending user invite request...") - return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, "project|organization", projectID, organizationID, "userinvite", &subject, &body, automate) -} - // buildErrorMessage helper function to build an error message func buildErrorMessage(errPrefix string, claGroupID string, params cla_manager.CreateCLAManagerParams, err error) string { return fmt.Sprintf("%s - problem creating new CLA Manager Request using company ID: %s, project ID: %s, first name: %s, last name: %s, user email: %s, error: %+v", @@ -1461,7 +1246,7 @@ func GetNonNoReplyUserEmail(userEmails []string) string { return "" } - excludedEmails := []string{} + var excludedEmails []string for _, email := range userEmails { if strings.HasSuffix(email, excludedNoReplyEmails) { From 001c1b7a35b4f2911ab3f9bb6782b440afec66f8 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 22 Feb 2021 14:37:33 +0300 Subject: [PATCH 0109/1276] Feature/Email Content - Updated text Signed-off-by: wanyaland --- cla-backend-go/cla_manager/handlers.go | 2 +- cla-backend-go/cla_manager/service.go | 16 +++++-- .../emails/cla_manager_templates.go | 16 +++++-- .../emails/v2_cla_manager_templates.go | 46 +++++++++---------- .../tests/v2_cla_manager_templates_test.go | 32 ++++++------- cla-backend-go/v2/cla_manager/service.go | 2 +- 6 files changed, 65 insertions(+), 49 deletions(-) diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 0af1dcb55..47e687b2e 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -683,7 +683,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Audit Event sent from service upon success - signature, addErr := service.AddClaManager(ctx, params.CompanyID, params.ProjectID, params.Body.UserLFID) + signature, addErr := service.AddClaManager(ctx, params.CompanyID, params.ProjectID, params.Body.UserLFID, "") if addErr != nil { msg := buildErrorMessageAddManager("Add CLA Manager - Service Error", params, addErr) log.Warn(msg) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 7fc330117..b6f5fb9c1 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -35,7 +35,7 @@ type IService interface { PendingRequest(companyID, claGroupID, requestID string) (*models.ClaManagerRequest, error) DeleteRequest(requestID string) error - AddClaManager(ctx context.Context, companyID string, claGroupID string, LFID string) (*models.Signature, error) + AddClaManager(ctx context.Context, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) RemoveClaManager(ctx context.Context, companyID string, claGroupID string, LFID string) (*models.Signature, error) } @@ -187,7 +187,7 @@ func (s service) DeleteRequest(requestID string) error { } // AddClaManager Adds LFID to Signature Access Control List list -func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID string, LFID string) (*models.Signature, error) { +func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) { userModel, userErr := s.usersService.GetUserByLFUserName(LFID) if userErr != nil || userModel == nil { @@ -236,7 +236,7 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID manager.Username, manager.LfEmail) } // Notify the added user - sendClaManagerAddedEmailToUser(companyModel, claGroupModel, userModel.Username, userModel.LfEmail) + sendClaManagerAddedEmailToUser(companyModel, claGroupModel, userModel.Username, userModel.LfEmail, projectSFName) // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ @@ -354,9 +354,14 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou return updatedSignature, nil } -func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail string) { +func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail, projectSFName string) { companyName := companyModel.CompanyName projectName := claGroupModel.ProjectName + templateName := emails.ClaManagerAddedEToUserTemplate + if claGroupModel.Version == "v2" { + templateName = emails.V2ClaManagerAddedEToUserTemplate + projectName = projectSFName + } // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Added as CLA Manager for Project :%s", projectName) @@ -364,12 +369,13 @@ func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel body, err := emails.RenderTemplate( claGroupModel.Version, emails.ClaManagerAddedEToUserTemplateName, - emails.ClaManagerAddedEToUserTemplate, + templateName, emails.ClaManagerAddedEToUserTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: requesterName, Project: emails.CLAProjectParams{ExternalProjectName: projectName}, CompanyName: companyName, + CLAGroupName: claGroupModel.ProjectName, }, CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), }, diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go index ad357ef34..b4d8f318c 100644 --- a/cla-backend-go/emails/cla_manager_templates.go +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -165,10 +165,20 @@ const ( // ClaManagerAddedEToUserTemplate is email template for ClaManagerAddedEToUserTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

        -

        You have been added as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that you can now maintain the +

        This is a notification email from EasyCLA regarding the project {{.CLAGroupName}}.

        +

        You have been added as a CLA Manager from {{.CompanyName}} for the project {{.CLAGroupName}}. This means that you can now maintain the list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your -company’s CLA Managers for {{.Project.ExternalProjectName}}.

        +company’s CLA Managers for {{.CLAGroupName}}.

        +

        To get started, please log into the EasyCLA Corporate Console, and select your +company and then the project {{.CLAGroupName}}. From here you will be able to edit the list of approved employees and CLA Managers.

        +` + //V2ClaManagerAddedEToUserTemplate email template for cla manager v2 + V2ClaManagerAddedEToUserTemplate = ` +

        Hello {{.RecipientName}},

        +

        This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}} and CLA Group {{.CLAGroupName}}.

        +

        You have been added as a CLA Manager for the organization {{.CompanyName}} and the project {{.Project.ExternalProjectName}}. This means that you can now maintain the +list of employees allowed to contribute to the project {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your +company’s CLA Managers for the CLA Group {{.CLAGroupName}}.

        To get started, please log into the EasyCLA Corporate Console, and select your company and then the project {{.Project.ExternalProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

        ` diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 803fef3d5..46c4a97db 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -27,11 +27,10 @@ const ( V2ContributorApprovalRequestTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

        -

        The following contributor would like to submit a contribution to the {{if .SigningEntityName}}{{.SigningEntityName}}{{else}}{{.CompanyName}}{{end}} CLA Group {{.CLAGroupName}} -and is requesting to be approved as a contributor for your organization:

        -

        {{.CLAGroupName}} - {{.UserDetails}}

        +

        The following contributor would like to submit a contribution to the CLA Group {{.CLAGroupName}} and is requesting to be approved as a contributor for your organization:

        +

        {{.UserDetails}}

        Approval can be done at {{.CorporateConsoleV2URL}}

        -

        Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

        +

        Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

        ` ) @@ -49,8 +48,8 @@ const ( // V2OrgAdminTemplate is email template for V2OrgAdminTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the CLA setup and signing process for {{.CompanyName}}.

        -

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following project:

        +

        This is a notification email from EasyCLA regarding the CLA setup and signing process for the organization {{.CompanyName}}.

        +

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA in support of the following project(s):

        • {{.Project.ExternalProjectName}}
        @@ -92,11 +91,11 @@ const ( V2ContributorToOrgAdminTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.ExternalProjectName}}{{end}}

        -

        The following contributor is requesting to sign CLA for organization: {{.CompanyName}}

        +

        The following contributor is requesting to sign the CLA for the organization: {{.CompanyName}}

        {{.UserDetails}}

        Before the user contribution can be accepted, your organization must sign a CLA. -

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the projects {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        -

        Please notify the contributor once they are added to the approved list of contributors so that they can complete their code contribution.

        +

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        +

        Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

        ` ) @@ -128,8 +127,8 @@ const ( // V2CLAManagerDesigneeCorporateTemplate is email template for V2CLAManagerDesigneeCorporateTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the CLA setup and signing process for {{.CompanyName}}.

        -

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for {{.CompanyName}} in support of the following projects:

        +

        This is a notification email from EasyCLA regarding the CLA setup and signing process for the organization {{.CompanyName}}.

        +

        {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for the organization {{.CompanyName}} in support of the following project(s):

        • {{.Project.ExternalProjectName}}
        @@ -164,12 +163,13 @@ type V2ToCLAManagerDesigneeTemplateParams struct { ContributorID string ContributorName string CorporateConsole string + CompanyName string } -// GetProjectsOrProject returns the single Project or comma saparated projects if more than one +// GetProjectsOrProject returns the single Project or comma separated projects if more than one func (p V2ToCLAManagerDesigneeTemplateParams) GetProjectsOrProject() string { if len(p.Projects) == 1 { - return " " + p.Projects[0].ExternalProjectName + return p.Projects[0].ExternalProjectName } var projectNames []string @@ -177,7 +177,7 @@ func (p V2ToCLAManagerDesigneeTemplateParams) GetProjectsOrProject() string { projectNames = append(projectNames, p.ExternalProjectName) } - return "s " + strings.Join(projectNames, ", ") + return strings.Join(projectNames, ", ") } const ( @@ -186,12 +186,12 @@ const ( // V2ToCLAManagerDesigneeTemplate is email template for V2ToCLAManagerDesigneeTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project{{.GetProjectsOrProject}}.

        -

        The following contributor is requesting to sign CLA for organization:

        +

        This is a notification email from EasyCLA regarding the project(s): {{.GetProjectsOrProject}}.

        +

        The following contributor is requesting to sign the CLA for the organization {{.CompanyName}}:

        {{.ContributorID}} ({{.ContributorName}})

        Before the user contribution can be accepted, your organization must sign a CLA. -

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s) {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        -

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

        +

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        +

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they can complete the contribution process.

        ` ) @@ -226,10 +226,10 @@ const (

        This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}}.

        The following contributor would like to contribute to {{.GetProjectNameOrFoundation}} on behalf of your organization: {{.CompanyName}}.

        {{.RequesterUserName}} ({{.RequesterEmail}})

        -

        Before the user's contribution can be accepted, your organization must sign a CLA.

        +

        Before the user contribution can be accepted, your organization must sign a CLA.

        Please click on Accept Invite to create your LF Login.

        After login, you will be redirected to this portal {{.CorporateConsole}} where you can sign the CLA for the project {{.Project.GetProjectFullURL}}.

        -

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they may complete the contribution process.

        +

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they can complete the contribution process.

        ` ) @@ -259,9 +259,9 @@ const ( // V2CLAManagerToUserWithNoLFIDTemplate is email template V2CLAManagerToUserWithNoLFIDTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the Project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}} in the EasyCLA system.

        -

        User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for Project {{.Project.ExternalProjectName}} but was unable to identify your account details in -the EasyCLA system. In order to become a CLA Manager for Project {{.Project.ExternalProjectName}}, you will need to accept invite below. +

        This is a notification email from EasyCLA regarding the Project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}}.

        +

        User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for the Project {{.Project.ExternalProjectName}} but was unable to identify your account details in +the EasyCLA system. In order to become a CLA Manager for the Project {{.Project.ExternalProjectName}}, you will need to accept the invite below. Once complete, notify the user {{.RequesterUserName}} and they will be able to add you as a CLA Manager.

        Accept Invite

        ` diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 97e8bd13e..7018ea7b5 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -29,8 +29,8 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the organization JohnsCompany") - assert.Contains(t, result, "contribution to the JohnsCompany CLA Group JohnsCLAGroupName") - assert.Contains(t, result, "JohnsCLAGroupName - UserDetailsValue") + assert.Contains(t, result, "contribution to the CLA Group JohnsCLAGroupName") + assert.Contains(t, result, "UserDetailsValue") assert.Contains(t, result, "Approval can be done at http://CorporateConsoleV2URL.com") params.SigningEntityName = "SigningEntityNameValue" @@ -40,8 +40,8 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the organization JohnsCompany") - assert.Contains(t, result, "contribution to the SigningEntityNameValue CLA Group JohnsCLAGroupName") - assert.Contains(t, result, "JohnsCLAGroupName - UserDetailsValue") + assert.Contains(t, result, "contribution to the CLA Group JohnsCLAGroupName") + assert.Contains(t, result, "UserDetailsValue") assert.Contains(t, result, "Approval can be done at http://CorporateConsoleV2URL.com") } @@ -67,9 +67,9 @@ func TestV2OrgAdminTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "signing process for JohnsCompany") + assert.Contains(t, result, "signing process for the organization JohnsCompany") assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") - assert.Contains(t, result, "Corporate CLA for JohnsCompany") + assert.Contains(t, result, "Corporate CLA in support of the following project(s):") assert.Contains(t, result, "
      • JohnsProject
      • ") assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") assert.Contains(t, result, `sign the CLA for this project JohnsProject`) @@ -96,10 +96,10 @@ func TestV2ContributorToOrgAdminTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s) Project1,Project2") - assert.Contains(t, result, "sign CLA for organization: JohnsCompany") + assert.Contains(t, result, "sign the CLA for the organization: JohnsCompany") assert.Contains(t, result, "

        UserDetailsValue

        ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, `CLA for any of the projects Project1,Project2`) + assert.Contains(t, result, `CLA for any of the project(s): Project1,Project2`) } func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { @@ -124,9 +124,9 @@ func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "CLA setup and signing process for JohnsCompany") + assert.Contains(t, result, "CLA setup and signing process for the organization JohnsCompany") assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") - assert.Contains(t, result, "Corporate CLA for JohnsCompany") + assert.Contains(t, result, "Corporate CLA for the organization JohnsCompany") assert.Contains(t, result, "
      • JohnsProject
      • ") assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") assert.Contains(t, result, `sign the CLA for this project JohnsProject`) @@ -148,10 +148,10 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the projects Project1, Project2") + assert.Contains(t, result, "regarding the project(s): Project1, Project2") assert.Contains(t, result, "

        ContributorIDValue (ContributorNameValue)

        ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, `CLA for one of the project(s) Project1,Project2`) + assert.Contains(t, result, `CLA for one of the project(s): Project1,Project2`) params.Projects = []emails.CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, @@ -160,10 +160,10 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the project Project1") + assert.Contains(t, result, "regarding the project(s): Project1") assert.Contains(t, result, "

        ContributorIDValue (ContributorNameValue)

        ") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, `CLA for one of the project(s) Project1`) + assert.Contains(t, result, `CLA for one of the project(s): Project1`) } @@ -210,8 +210,8 @@ func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the Project JohnsProjectExternal and CLA Group JohnsCLAGroupName in the") + assert.Contains(t, result, "regarding the Project JohnsProjectExternal and CLA Group JohnsCLAGroupName") assert.Contains(t, result, "User RequesterUserNameValue (RequesterEmailValue) was trying") - assert.Contains(t, result, "CLA Manager for Project JohnsProject") + assert.Contains(t, result, "CLA Manager for the Project JohnsProject") assert.Contains(t, result, "notify the user RequesterUserNameValue") } diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 8b4591ce8..781a4d3cb 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -234,7 +234,7 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param } // Add CLA Manager to Database - signature, addErr := s.managerService.AddClaManager(ctx, v1CompanyModel.CompanyID, claGroupID, user.Username) + signature, addErr := s.managerService.AddClaManager(ctx, v1CompanyModel.CompanyID, claGroupID, user.Username, projectSF.Name) if addErr != nil { msg := buildErrorMessageCreate(params, addErr) log.WithFields(f).Warn(msg) From 17a4fc7efcce49e3414c742cc5e7a585b1ab8e22 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Mon, 22 Feb 2021 17:29:00 +0300 Subject: [PATCH 0110/1276] Bug/ Get Foundation CLA Groups - Resolved foundation cla-groups GET endpoint Signed-off-by: wanyaland --- cla-backend-go/v2/cla_groups/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index f1515ff3a..35c7ba620 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -433,7 +433,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1CLAGroupData) } - } else if parentDetails != nil && (parentDetails.ProjectType == utils.ProjectTypeProjectGroup && sfProjectModelDetails.ProjectType != utils.ProjectTypeProjectGroup) { + } else if (parentDetails != nil && (parentDetails.ProjectType == utils.ProjectTypeProjectGroup && sfProjectModelDetails.ProjectType != utils.ProjectTypeProjectGroup)) || (sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup) { log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") projectCLAGroups, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) if lookupErr != nil { From abbbaa551ca3b5ca2a3d4115d54f08025dc5c668 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 22 Feb 2021 18:08:48 +0200 Subject: [PATCH 0111/1276] [#2632]handling cla group name change and reflecting that to cla-%s-projects-cla-groups table (#2690) cla-%s-projects-cla-groups table Signed-off-by: makkalot --- .../projects_cla_groups/repository.go | 55 +++++++++++++++++++ .../v2/dynamo_events/cla_groups_db_handler.go | 31 +++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/projects_cla_groups/repository.go b/cla-backend-go/projects_cla_groups/repository.go index a6ff2d22a..0126440c5 100644 --- a/cla-backend-go/projects_cla_groups/repository.go +++ b/cla-backend-go/projects_cla_groups/repository.go @@ -54,6 +54,7 @@ type Repository interface { IsExistingFoundationLevelCLAGroup(foundationSFID string) (bool, error) IsAssociated(projectSFID string, claGroupID string) (bool, error) UpdateRepositoriesCount(projectSFID string, diff int64, reset bool) error + UpdateClaGroupName(projectSFID string, claGroupName string) error } type repo struct { @@ -456,6 +457,60 @@ func (repo *repo) UpdateRepositoriesCount(projectSFID string, diff int64, reset return updateErr } +// UpdateClaGroupName updates cla group name for given projectSFID +func (repo *repo) UpdateClaGroupName(projectSFID string, claGroupName string) error { + f := logrus.Fields{ + "functionName": "project_cla_groups.repository.UpdateClaGroupName", + "projectSFID": projectSFID, + "claGroupName": claGroupName, + } + + // Check to see if we have an existing record + existingProjectCLAGroupMapping, err := repo.GetClaGroupIDForProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to lookup existing project cla group mapping") + return err + } + if existingProjectCLAGroupMapping == nil { + log.WithFields(f).Warn("unable to lookup existing project cla group mapping - response is empty") + return &utils.ProjectCLAGroupMappingNotFound{ + ProjectSFID: projectSFID, + CLAGroupID: "", + Err: nil, + } + } + + expressionAttributeNames := map[string]*string{} + expressionAttributeValues := map[string]*dynamodb.AttributeValue{} + var updateExpression string + + // update repositories_count based on reset flag + expressionAttributeNames["#N"] = aws.String("cla_group_name") + expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: &claGroupName} + updateExpression = "SET #N = :n" + + _, now := utils.CurrentTime() + expressionAttributeNames["#M"] = aws.String("date_modified") + expressionAttributeValues[":m"] = &dynamodb.AttributeValue{S: aws.String(now)} + updateExpression = updateExpression + ", #M = :m" + + _, updateErr := repo.dynamoDBClient.UpdateItem(&dynamodb.UpdateItemInput{ + UpdateExpression: aws.String(updateExpression), + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + Key: map[string]*dynamodb.AttributeValue{ + "project_sfid": {S: aws.String(projectSFID)}, + }, + TableName: aws.String(repo.tableName), + }) + + if updateErr != nil { + log.WithFields(f).WithError(updateErr).Warn("update cla group name failed") + } + + return updateErr +} + // IsExistingFoundationLevelCLAGroup is a query helper function to determine if the // specified foundation SFID has an entry in the mapping table to signify that // it's a foundation level CLA Group (foundationSFID == projectSFID) diff --git a/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go b/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go index 265c82e32..f83ef6d99 100644 --- a/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go +++ b/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go @@ -23,12 +23,19 @@ func (s *service) ProcessCLAGroupUpdateEvents(event events.DynamoDBEventRecord) log.WithFields(f).Debug("processing event") - var updatedProject project.DBProjectModel + var oldProject, updatedProject project.DBProjectModel err := unmarshalStreamImage(event.Change.NewImage, &updatedProject) if err != nil { - log.WithFields(f).Warnf("unable to unmarshal project model, error: %+v", err) + log.WithFields(f).Warnf("unable to unmarshal new project model, error: %+v", err) return err } + + err = unmarshalStreamImage(event.Change.OldImage, &oldProject) + if err != nil { + log.WithFields(f).Warnf("unable to unmarshal old project model, error: %+v", err) + return err + } + log.WithFields(f).Debugf("decoded project record from stream: %+v", updatedProject) // Update any DB records that have CLA Approval Requests from Contributors - need to update Name, etc. if that has changed @@ -44,13 +51,25 @@ func (s *service) ProcessCLAGroupUpdateEvents(event events.DynamoDBEventRecord) log.WithFields(f).Warnf("unable to update cla manager request with updated CLA Group information, error: %+v", approvalListRequestErr) } + if oldProject.ProjectName != updatedProject.ProjectName { + claProjects, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(updatedProject.ProjectID) + if err != nil { + log.WithFields(f).Warnf("unabled to update cla group name : %v", err) + return nil + } + + for _, claProject := range claProjects { + if err := s.projectsClaGroupRepo.UpdateClaGroupName(claProject.ProjectSFID, updatedProject.ProjectName); err != nil { + log.WithFields(f).Warnf("updating cla project : %s with name : %s failed : %v", claProject.ProjectSFID, updatedProject.ProjectName, err) + return nil + } + } + log.WithFields(f).Infof("updating related cla projects with name : %s", updatedProject.ProjectName) + } + // TODO - update other tables: // cla-%s-metrics, - // cla-%s-projects-cla-groups, // cla-%s-gerrit-instances, - // possibly add/update cla_group_name/project_name to other tables: - // cla-%-repositories - // cla-%-signatures return nil } From 7f99586a90353431ac6d6c35e0163e3909e70356 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 22 Feb 2021 23:57:16 +0300 Subject: [PATCH 0112/1276] Bug/Get Foundation|Project CLA groups (#2695) - Resolved GET cla-groups endpoint for SF Project and Company endpoints Signed-off-by: wanyaland --- cla-backend-go/project/repository.go | 2 ++ cla-backend-go/utils/project_helpers.go | 5 +++++ cla-backend-go/v2/cla_groups/service.go | 4 ++-- cla-backend-go/v2/company/service.go | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 796d5482b..2dfa9863f 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -354,6 +354,8 @@ func (repo *repo) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSF } } + log.WithFields(f).Debugf("foundation projects!: %#v ", projects) + return &models.ClaGroups{ ResultCount: int64(len(projects)), Projects: projects, diff --git a/cla-backend-go/utils/project_helpers.go b/cla-backend-go/utils/project_helpers.go index e90f3b3a1..3457fc55d 100644 --- a/cla-backend-go/utils/project_helpers.go +++ b/cla-backend-go/utils/project_helpers.go @@ -23,3 +23,8 @@ func IsProjectHaveChildren(project *models.ProjectOutputDetailed) bool { // a project model with a project list means it has children return len(project.Projects) > 0 } + +// IsProjectCategory determines if a given project is categorised as cla project sfid +func IsProjectCategory(project *models.ProjectOutputDetailed, parent *models.ProjectOutputDetailed) bool { + return project.ProjectType == ProjectTypeProject || (!IsProjectHasRootParent(project) && parent.ProjectType == ProjectTypeProjectGroup && project.ProjectType == ProjectTypeProjectGroup) +} diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 35c7ba620..c21486fd6 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -390,7 +390,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje var foundationName = sfProjectModelDetails.Name // If it's a project... - if sfProjectModelDetails.ProjectType == utils.ProjectTypeProject || (parentDetails != nil && (parentDetails.ProjectType == utils.ProjectTypeProjectGroup && sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup)) { + if utils.IsProjectCategory(sfProjectModelDetails, parentDetails) { // Since this is a project and not a foundation, we'll want to set he parent foundation ID and name (which is // our parent in this case) log.WithFields(f).Debug("found 'project' in platform project service.") @@ -433,7 +433,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1CLAGroupData) } - } else if (parentDetails != nil && (parentDetails.ProjectType == utils.ProjectTypeProjectGroup && sfProjectModelDetails.ProjectType != utils.ProjectTypeProjectGroup)) || (sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup) { + } else if sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup { log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") projectCLAGroups, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) if lookupErr != nil { diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 706e09135..6d77153ea 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1165,7 +1165,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, proj var allProjectMapping []*projects_cla_groups.ProjectClaGroup // Determine query index (foundation or project) - if parentProjectDetails != nil && (parentProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup) { + if !utils.IsProjectCategory(projectDetails, parentProjectDetails) { // get all projects for all cla group under foundation allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForFoundation(projectSFID) if err != nil { From d0ef9b383115392f868b1fc3ff33820876cf3aec Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 23 Feb 2021 14:27:33 +0300 Subject: [PATCH 0113/1276] [#2634] Feature/Contributor Contact Admin email - Updated content sent to company admin in regards the CLA signing Signed-off-by: wanyaland --- cla-backend-go/emails/v2_cla_manager_templates.go | 9 ++++----- cla-backend-go/tests/v2_cla_manager_templates_test.go | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 46c4a97db..cfdfe0e02 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -90,12 +90,11 @@ const ( // V2ContributorToOrgAdminTemplate is email template for V2ContributorToOrgAdminTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project(s) {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.ExternalProjectName}}{{end}}

        -

        The following contributor is requesting to sign the CLA for the organization: {{.CompanyName}}

        +

        The following contributor would like to submit a contribution to {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.ExternalProjectName}}{{end}} and is requesting to be added to the approval list as a contributor for your organization:

        {{.UserDetails}}

        -

        Before the user contribution can be accepted, your organization must sign a CLA. -

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for any of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        -

        Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

        +

        Before the contribution can be accepted, your organization must sign a CLA. Either you or someone whom you designate from your company can login to this portal and sign the CLA for any of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        +

        Please notify the contributor once they are added so that they may complete the contribution process.

        + ` ) diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 7018ea7b5..25927155c 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -95,10 +95,10 @@ func TestV2ContributorToOrgAdminTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the project(s) Project1,Project2") - assert.Contains(t, result, "sign the CLA for the organization: JohnsCompany") + assert.Contains(t, result, "would like to submit a contribution to Project1,Project2") + assert.Contains(t, result, "your organization must sign a CLA.") assert.Contains(t, result, "

        UserDetailsValue

        ") - assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") + assert.Contains(t, result, "Please notify the contributor once they are added so that they may complete the contribution process") assert.Contains(t, result, `CLA for any of the project(s): Project1,Project2`) } From 5d2a5914dbbc79795c2a54b5ad0132ea12344940 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 23 Feb 2021 16:59:12 +0300 Subject: [PATCH 0114/1276] Bug/Enroll Middle Level Project - Added support for enrolling project in a 3 tier level Signed-off-by: wanyaland --- cla-backend-go/v2/cla_groups/handlers.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index f19ce4284..40cd7dab5 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -28,6 +28,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" v2ProjectServiceClient "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/client/project" + v2ProjectServiceModels "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/models" "github.com/go-openapi/runtime/middleware" ) @@ -281,7 +282,20 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, projectErr)) } - if project.ProjectType == utils.ProjectTypeProjectGroup { + var parentProject *v2ProjectServiceModels.ProjectOutputDetailed + // Handle the ONAP edge case + if project.Parent != "" { + parentProject, projectErr = psc.GetProject(project.Parent) + if parentProject == nil || projectErr != nil { + msg := fmt.Sprintf("Failed to get parent: %s", project.Parent) + log.WithFields(f).Warnf(msg) + return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload((&models.ErrorResponse{ + Code: "400", + Message: msg, + })) + } + } + if (project.Parent != "" && !utils.IsProjectCategory(project, parentProject)) || (!utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { msg := fmt.Sprintf("Unable to enroll salesforce foundation project: %s in project level cla-group.", projectSFID) return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } From d4639d7c2eda3e5ce08046c65abd1d8fc6c22088 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Tue, 23 Feb 2021 17:56:39 +0300 Subject: [PATCH 0115/1276] [#2697] Bug/Enroll Project - Resolved issue raised when enrolling a project in 3 tier levels Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/v2/cla_groups/handlers.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index e45f62fd5..2c59d99bc 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -72,6 +72,7 @@ github.com/communitybridge/easycla v1.0.117 h1:o+rdmcNgZeMQ/N8HV/d5apNIBrkYH7eyM github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNutidR+yy2HwW0= github.com/communitybridge/easycla v1.0.123 h1:Lh5i/9aajrTYItxNpVCmi9T1yyIfnQIOk0tC2Wtslvk= github.com/communitybridge/easycla v1.0.133 h1:aJulQGLLRISCMsZcCP4aIE8xGtHoBNm/EmA00n3NYVA= +github.com/communitybridge/easycla v1.0.135 h1:Dvn8jX+7BAnpmA+jvdK0n5ajWP8SoH5vvopt7whZDEU= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 40cd7dab5..7de4461b8 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -295,7 +295,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P })) } } - if (project.Parent != "" && !utils.IsProjectCategory(project, parentProject)) || (!utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { + if (project.Parent != "" && !utils.IsProjectCategory(project, parentProject)) || (utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { msg := fmt.Sprintf("Unable to enroll salesforce foundation project: %s in project level cla-group.", projectSFID) return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } From 565d602236cfbe1f55c873f121d8dac850c15db9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 24 Feb 2021 21:59:40 -0800 Subject: [PATCH 0116/1276] Added Defensive Code for Avoid Creating Duplicate Company Records (#2709) Signed-off-by: David Deal --- cla-backend-go/company/repository.go | 22 +++- cla-backend-go/go.sum | 160 ++++++++++++++++++++++++++ cla-backend-go/v2/company/handlers.go | 26 +++-- cla-backend-go/v2/company/service.go | 3 +- 4 files changed, 199 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/company/repository.go b/cla-backend-go/company/repository.go index 3eee0852d..d5d8ca32c 100644 --- a/cla-backend-go/company/repository.go +++ b/cla-backend-go/company/repository.go @@ -1217,14 +1217,31 @@ func (repo repository) UpdateCompanyAccessList(ctx context.Context, companyID st // CreateCompany creates a new company record func (repo repository) CreateCompany(ctx context.Context, in *models.Company) (*models.Company, error) { f := logrus.Fields{ - "functionName": "company.repository.CreateCompany", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "functionName": "company.repository.CreateCompany", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyName": in.CompanyName, + "signingEntityName": in.SigningEntityName, + "companySFID": in.CompanyExternalID, } + + // Don't create duplicates - check to see if any exist + existingModel, queryErr := repo.GetCompanyByName(ctx, in.CompanyName) + if queryErr != nil { + log.WithFields(f).WithError(queryErr).Warn("problem querying for existing company record by name") + return nil, queryErr + } + // Already exists - don't re-create + if existingModel != nil { + return existingModel, nil + } + companyID, err := uuid.NewV4() if err != nil { log.WithFields(f).Warnf("Unable to generate a UUID for a pending invite, error: %v", err) return nil, err } + f["companyID"] = companyID + _, now := utils.CurrentTime() comp := &DBModel{ CompanyID: companyID.String(), @@ -1260,6 +1277,7 @@ func (repo repository) CreateCompany(ctx context.Context, in *models.Company) (* TableName: aws.String(repo.companyTableName), }) if err != nil { + log.WithFields(f).WithError(err).Warn("problem creating new company") return nil, err } diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 2c59d99bc..02cb0d7bd 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -4,16 +4,25 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -21,8 +30,11 @@ github.com/LF-Engineering/lfx-kit v0.1.22 h1:4tE1xTvu5CRWIokOo1waOfuB6vgaCpov5gl github.com/LF-Engineering/lfx-kit v0.1.22/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.34 h1:K8al2aTq8nDm3qNmsTNAhZ1uDzfew/UymwbcW9gbDDs= github.com/LF-Engineering/lfx-models v0.6.34/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -33,9 +45,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5Vpd github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -51,19 +66,26 @@ github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c h1:+0HFd5KSZ/mm3JmhmrDukiId5iR6w4+BdFtfSy4yWIc= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/communitybridge/easycla v1.0.99 h1:PkmkMV7cLH2Q2YNSFiGGmlyrHBXVYdsWMwbXNuMAyqw= github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9udsPZk8oLoQ= @@ -73,26 +95,38 @@ github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNuti github.com/communitybridge/easycla v1.0.123 h1:Lh5i/9aajrTYItxNpVCmi9T1yyIfnQIOk0tC2Wtslvk= github.com/communitybridge/easycla v1.0.133 h1:aJulQGLLRISCMsZcCP4aIE8xGtHoBNm/EmA00n3NYVA= github.com/communitybridge/easycla v1.0.135 h1:Dvn8jX+7BAnpmA+jvdK0n5ajWP8SoH5vvopt7whZDEU= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= @@ -100,15 +134,23 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e h1:5CNDPg63TSvbNi3viqFnIywPu2TqLMlWAPuFuuTrFe4= github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -181,36 +223,52 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -230,8 +288,10 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -244,10 +304,14 @@ github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/J github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -256,39 +320,63 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -303,6 +391,7 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -310,10 +399,14 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -321,27 +414,37 @@ github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2h github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0 h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= @@ -355,7 +458,9 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= @@ -365,34 +470,49 @@ github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= @@ -405,37 +525,50 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= +github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -447,7 +580,9 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.3.0 h1:Ysnmjh1Di8EaWaBv40CYR4IdaIsBc5996Gh1oZzCBKk= @@ -470,6 +605,7 @@ github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -482,9 +618,13 @@ github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 h1:xp/21gmSP github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8/go.mod h1:K3DbqPpP2WE/9MWokWWzgFZcbgtMb9Wd5CYk9AAbEN8= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4 h1:euf5tLM++W5h5uyfs6NSMoCGGhw+hRXMLE/DU6hireM= github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -492,10 +632,15 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -503,12 +648,15 @@ go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -528,6 +676,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -540,11 +689,14 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -657,6 +809,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -673,11 +826,13 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -695,11 +850,14 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -717,5 +875,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 3d519fd72..76b151cf6 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -372,12 +372,20 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": params.CompanyName, } - // Anyone can query for a company by name - log.WithFields(f).Debug("loading company by name") + // Anyone can query for a company by name - no permissions checks + + // Weird - sometimes the UI calls us with the company name of "null" + if params.CompanyName == "" || params.CompanyName == "null" { + return company.NewGetCompanyByNameBadRequest(). + WithXRequestID(reqID). + WithPayload(utils.ErrorResponseBadRequest(reqID, "company name input parameter missing or valid")) + } + + log.WithFields(f).Debugf("loading company by name: '%s'", params.CompanyName) companyModel, err := service.GetCompanyByName(ctx, params.CompanyName) if err != nil || companyModel == nil { - log.WithFields(f).Warn("unable to lookup company by name in local database. trying organization service...") + log.WithFields(f).Warnf("unable to lookup company by name '%s' in local database. trying organization service...", params.CompanyName) osClient := organization_service.GetClient() orgModels, orgLookupErr := osClient.SearchOrganization(ctx, params.CompanyName, "", "") if orgLookupErr != nil || len(orgModels) == 0 { @@ -387,17 +395,19 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debugf("found company: '%s' in the organization service - creating local record...", params.CompanyName) - companyModels, companyCreateErr := service.CreateCompanyFromSFModel(ctx, orgModels[0]) - if companyCreateErr != nil || companyModels == nil { + companyModelOutput, companyCreateErr := service.CreateCompanyFromSFModel(ctx, orgModels[0]) + if companyCreateErr != nil || companyModelOutput == nil { msg := fmt.Sprintf("unable to create company '%s' from salesforce record", params.CompanyName) log.WithFields(f).WithError(err).Warn(msg) return company.NewGetCompanyByNameInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, companyCreateErr)) } - log.WithFields(f).Debugf("loading company: %s by name after creation...", params.CompanyName) - companyModel, err = service.GetCompanyByName(ctx, params.CompanyName) + // Note: company name may have been swapped with actual value from SF or Clearbit authority - so use it below... + + log.WithFields(f).Debugf("loading company: %s by name after creation...", companyModelOutput.CompanyName) + companyModel, err = service.GetCompanyByName(ctx, companyModelOutput.CompanyName) if err != nil { - msg := fmt.Sprintf("unable to locate company '%s' after creating...", params.CompanyName) + msg := fmt.Sprintf("unable to locate company '%s' after creating...", companyModelOutput.CompanyName) log.WithFields(f).WithError(err).Warn(msg) return company.NewGetCompanyByNameNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) } diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 6d77153ea..68cd5e6ff 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -465,7 +465,6 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN } _, createErr := s.companyRepo.CreateCompany(ctx, createCompanyModel) - //easyCLAErr := s.repo.CreateCompany(companyName, org.ID, userID) if createErr != nil { log.WithFields(f).Warnf("Failed to create EasyCLA company for company: %s, error: %+v", companyName, createErr) @@ -492,7 +491,7 @@ func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgMod "organizationStatus": orgModel.Status, } - log.WithFields(f).Debug("Creating company...") + log.WithFields(f).Debugf("Creating company: %s...", orgModel.Name) return s.CreateCompany(ctx, orgModel.Name, orgModel.Name, orgModel.Link, "", "", fmt.Sprintf("created from platform organization service model: %s", orgModel.ID)) } From ef5769f825c1ba2d7df69115270625ef13f22dec Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 25 Feb 2021 21:04:26 +0300 Subject: [PATCH 0117/1276] [#2703,#2704]Feature/Email Refactor (#2710) - Updated email sent to designee with NO LFID - Updated designee email sent to one with LF Login Signed-off-by: wanyaland --- .../emails/v2_cla_manager_templates.go | 51 +++++++++---------- .../tests/v2_cla_manager_templates_test.go | 35 ++++++------- cla-backend-go/v2/cla_manager/emails.go | 37 +++++++------- cla-backend-go/v2/cla_manager/service.go | 50 +++++++++--------- 4 files changed, 82 insertions(+), 91 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index cfdfe0e02..8d20d06ed 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -159,7 +159,7 @@ func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups. type V2ToCLAManagerDesigneeTemplateParams struct { RecipientName string Projects []CLAProjectParams - ContributorID string + ContributorEmail string ContributorName string CorporateConsole string CompanyName string @@ -186,16 +186,21 @@ const ( V2ToCLAManagerDesigneeTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the project(s): {{.GetProjectsOrProject}}.

        -

        The following contributor is requesting to sign the CLA for the organization {{.CompanyName}}:

        -

        {{.ContributorID}} ({{.ContributorName}})

        -

        Before the user contribution can be accepted, your organization must sign a CLA. +

        We received a request from {{.ContributorName}} ({{.ContributorEmail}}): to contribute to the above projects on behalf of your organization

        +

        Before the user contribution can be accepted, your organization must sign a Corporate CLA (CCLA).The requester has stated that you would be the initial CLA Manager for this CCLA, to coordinate the signing of the CCLA and then manage the list of employees who are authorized to contribute

        +

        Please complete the following steps:

        +
          +
        1. After login, you will be redirected to the portal {{.CorporateConsole}} where you can either sign the CLA for any of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}, or send it to an authorized signatory for your company.
        2. +
        3. After signing the CLA, you will need to add this contributor to the approved list in the CLA Manager console.
        4. +
        5. After adding the contributor, please notify them so that they can complete the contribution process.
        6. +

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they can complete the contribution process.

        ` ) // RenderV2ToCLAManagerDesigneeTemplate renders V2ToCLAManagerDesigneeTemplate -func RenderV2ToCLAManagerDesigneeTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ToCLAManagerDesigneeTemplateParams) (string, error) { +func RenderV2ToCLAManagerDesigneeTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ToCLAManagerDesigneeTemplateParams, template string, templateName string) (string, error) { // prefill the projects data projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsole) if err != nil { @@ -204,8 +209,8 @@ func RenderV2ToCLAManagerDesigneeTemplate(repository projects_cla_groups.Reposit params.Projects = projects - return RenderTemplate(utils.V2, V2ToCLAManagerDesigneeTemplateName, - V2ToCLAManagerDesigneeTemplate, params) + return RenderTemplate(utils.V2, templateName, + template, params) } // V2DesigneeToUserWithNoLFIDTemplateParams is email params for V2DesigneeToUserWithNoLFIDTemplate @@ -222,29 +227,21 @@ const ( // V2DesigneeToUserWithNoLFIDTemplate is email template for V2DesigneeToUserWithNoLFIDTemplate = `

        Hello {{.RecipientName}},

        -

        This is a notification email from EasyCLA regarding the project {{.GetProjectNameOrFoundation}}.

        -

        The following contributor would like to contribute to {{.GetProjectNameOrFoundation}} on behalf of your organization: {{.CompanyName}}.

        -

        {{.RequesterUserName}} ({{.RequesterEmail}})

        -

        Before the user contribution can be accepted, your organization must sign a CLA.

        -

        Please click on Accept Invite to create your LF Login.

        -

        After login, you will be redirected to this portal {{.CorporateConsole}} where you can sign the CLA for the project {{.Project.GetProjectFullURL}}.

        -

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they can complete the contribution process.

        +

        This is a notification email from EasyCLA regarding the project(s): {{.GetProjectsOrProject}}.

        +

        We received a request from {{.ContributorName}} ({{.ContributorEmail}}) to contribute to any of the above projects on behalf of your +organization {{.CompanyName}}.

        +

        Before the user contribution can be accepted, your organization must sign a Corporate CLA(CCLA). +The requester has stated that you would be the initial CLA Manager for this CCLA, to coordinate the signing of the CCLA and then manage the list of employees who are authorized to contribute.

        +

        Please complete the following steps:

        +
          +
        1. Please click on Accept Invite to create your LF Login.This is used to access the EasyCLA CLA Manager console.
        2. +
        3. After login, you will be redirected to the portal {{.CorporateConsole}} where you can either sign the CLA for any of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}, or send it to an authorized signatory for your company.
        4. +
        5. After signing the CLA, you will need to add this contributor to the approved list in the CLA Manager console.
        6. +
        7. After adding the contributor, please notify them so that they can complete the contribution process.
        8. +
        ` ) -// RenderV2DesigneeToUserWithNoLFIDTemplate renders V2DesigneeToUserWithNoLFIDTemplate -func RenderV2DesigneeToUserWithNoLFIDTemplate(repository projects_cla_groups.Repository, projectSFID string, params V2DesigneeToUserWithNoLFIDTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { - return "", err - } - - // assign the corporate console so we can show the link of the project - params.Project.CorporateConsole = params.CorporateConsole - - return RenderTemplate(utils.V2, V2DesigneeToUserWithNoLFIDTemplateName, - V2DesigneeToUserWithNoLFIDTemplate, params) -} - // V2CLAManagerToUserWithNoLFIDTemplateParams is email params for V2CLAManagerToUserWithNoLFIDTemplate type V2CLAManagerToUserWithNoLFIDTemplateParams struct { CLAManagerTemplateParams diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 25927155c..decf5ec26 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -139,7 +139,7 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, }, - ContributorID: "ContributorIDValue", + ContributorEmail: "ContributorIDValue", ContributorName: "ContributorNameValue", CorporateConsole: "http://CorporateConsole.com", } @@ -149,7 +149,7 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s): Project1, Project2") - assert.Contains(t, result, "

        ContributorIDValue (ContributorNameValue)

        ") + assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue):") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") assert.Contains(t, result, `CLA for one of the project(s): Project1,Project2`) @@ -161,37 +161,32 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s): Project1") - assert.Contains(t, result, "

        ContributorIDValue (ContributorNameValue)

        ") + assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue):") assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") assert.Contains(t, result, `CLA for one of the project(s): Project1`) } func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { - params := emails.V2DesigneeToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ - ExternalProjectName: "JohnsProjectExternal", - CorporateConsole: "https://corporate.dev.lfcla.com", - FoundationSFID: "FoundationSFIDValue", - SignedAtFoundationLevel: true, - }, - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + params := emails.V2ToCLAManagerDesigneeTemplateParams{ + RecipientName: "JohnsClaManager", + Projects: []emails.CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "https://corporate.dev.lfcla.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "https://corporate.dev.lfcla.com"}, }, - RequesterUserName: "RequesterUserNameValue", - RequesterEmail: "RequesterEmailValue", - CorporateConsole: "https://corporate.dev.lfcla.com", + ContributorEmail: "ContributorIDValue", + ContributorName: "ContributorNameValue", + CorporateConsole: "https://corporate.dev.lfcla.com", } result, err := emails.RenderTemplate(utils.V1, emails.V2DesigneeToUserWithNoLFIDTemplateName, emails.V2DesigneeToUserWithNoLFIDTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager,") - assert.Contains(t, result, "The following contributor would like to contribute to JohnsProjectExternal on behalf of your organization: JohnsCompany.") - assert.Contains(t, result, "you will be redirected to this portal https://corporate.dev.lfcla.com ") - assert.Contains(t, result, `where you can sign the CLA for the project JohnsProjectExternal`) + assert.Contains(t, result, "We received a request from ContributorNameValue (ContributorIDValue)") + assert.Contains(t, result, "After login, you will be redirected to the portal https://corporate.dev.lfcla.com ") + assert.Contains(t, result, `where you can either sign the CLA for any of the project(s): Project1`) + assert.Contains(t, result, "or send it to an authorized signatory for your company.") } func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index eab1b55ab..ad3542fc4 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -188,7 +188,7 @@ func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, re } } -func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) { +func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorEmail string, contributorName string) { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToCLAManagerDesignee", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -197,20 +197,20 @@ func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository "projectNames": strings.Join(projectNames, ","), "designeeEmail": designeeEmail, "designeeName": designeeName, - "contributorID": contributorID, + "contributorEmail": contributorEmail, "contributorName": contributorName, } subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", - companyName, contributorID) + companyName, contributorEmail) recipients := []string{designeeEmail} body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ RecipientName: designeeName, - ContributorID: contributorID, + ContributorEmail: contributorEmail, ContributorName: contributorName, CorporateConsole: corporateConsole, - }) + }, emails.V2ToCLAManagerDesigneeTemplate, emails.V2ToCLAManagerDesigneeTemplateName) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering template : %s failed : %v", emails.V2ToCLAManagerDesigneeTemplateName, err) @@ -224,30 +224,27 @@ func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository } } -func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string, corporateConsoleV2URL string) error { +func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectSFIDs []string, foundationSFID, role string, corporateConsoleV2URL string) error { f := logrus.Fields{ "functionName": "cla_manager.service.SendDesigneeEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "userWithNoLFIDName": userWithNoLFIDName, "userWithNoLFIDEmail": userWithNoLFIDEmail, "organizationID": organizationID, - "projectID": utils.StringValue(projectID), + "projectNames": strings.Join(projectNames, ","), "role": role, "corporateConsoleV2URL": corporateConsoleV2URL, } - subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager for project: %s ", projectName) - body, err := emails.RenderV2DesigneeToUserWithNoLFIDTemplate(repository, *projectID, - emails.V2DesigneeToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: userWithNoLFIDName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: organizationName, - }, - RequesterUserName: requesterUsername, - RequesterEmail: requesterEmail, - CorporateConsole: corporateConsoleV2URL, - }) + subject := "EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager" + + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, + emails.V2ToCLAManagerDesigneeTemplateParams{ + RecipientName: userWithNoLFIDName, + ContributorEmail: requesterEmail, + ContributorName: requesterUsername, + CorporateConsole: corporateConsoleV2URL, + }, emails.V2DesigneeToUserWithNoLFIDTemplate, emails.V2DesigneeToUserWithNoLFIDTemplateName) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering template : %s failed : %v", emails.V2DesigneeToUserWithNoLFIDTemplateName, err) @@ -257,7 +254,7 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, reposit acsClient := v2AcsService.GetClient() automate := false log.WithFields(f).Debug("sending user invite request...") - return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, projectID, organizationID, "userinvite", &subject, &body, automate) + return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, &foundationSFID, organizationID, "userinvite", &subject, &body, automate) } // sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 781a4d3cb..fb9706796 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -96,7 +96,7 @@ type Service interface { ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) - SendDesigneeEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID, projectName string, projectID *string, role string, corporateConsoleV2URL string) error + SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectIDs []string, foundationSFID, role string, corporateConsoleV2URL string) error SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error } @@ -897,16 +897,34 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com var projectSFs []string var projectSFIDs []string - for _, pcg := range projectCLAGroups { - log.WithFields(f).Debugf("Getting salesforce project by SFID: %s ", pcg.ProjectSFID) - projectSF, projectErr := projectService.GetProject(pcg.ProjectSFID) + foundationSFID := projectCLAGroups[0].FoundationSFID + + if signedAtFoundation { + + // Get salesforce project by FoundationID + log.WithFields(f).Debugf("querying project service for project details...") + // GetSFProject + foundationSF, projectErr := projectService.GetProject(foundationSFID) if projectErr != nil { - msg := fmt.Sprintf("Problem getting salesforce Project ID: %s", pcg.ProjectSFID) + msg := fmt.Sprintf("EasyCLA - 400 Bad Request - Project service lookup error for SFID: %s, error : %+v", + projectID, projectErr) log.WithFields(f).Warn(msg) return nil, projectErr } - projectSFs = append(projectSFs, projectSF.Name) - projectSFIDs = append(projectSFIDs, projectSF.ID) + projectSFs = append(projectSFs, foundationSF.Name) + projectSFIDs = append(projectSFIDs, foundationSFID) + } else { + for _, pcg := range projectCLAGroups { + log.WithFields(f).Debugf("Getting salesforce project by SFID: %s ", pcg.ProjectSFID) + projectSF, projectErr := projectService.GetProject(pcg.ProjectSFID) + if projectErr != nil { + msg := fmt.Sprintf("Problem getting salesforce Project ID: %s", pcg.ProjectSFID) + log.WithFields(f).Warn(msg) + return nil, projectErr + } + projectSFs = append(projectSFs, projectSF.Name) + projectSFIDs = append(projectSFIDs, projectSF.ID) + } } var designeeScopes []*models.ClaManagerDesignee @@ -960,24 +978,8 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com if userErr != nil || (user != nil && user.Username == "") { msg := fmt.Sprintf("UserEmail: %s has no LF Login and has been sent an invite email to create an account , error: %+v", userEmail, userErr) log.Warn(msg) - - // Use FoundationSFID - foundationSFID := projectCLAGroups[0].FoundationSFID - - // Get salesforce project by FoundationID - log.WithFields(f).Debugf("querying project service for project details...") - // GetSFProject - ps := v2ProjectService.GetClient() - sfProject, projectErr := ps.GetProject(foundationSFID) - if projectErr != nil { - msg := fmt.Sprintf("EasyCLA - 400 Bad Request - Project service lookup error for SFID: %s, error : %+v", - projectID, projectErr) - log.WithFields(f).Warn(msg) - return nil, projectErr - } - contibutorEmail := GetNonNoReplyUserEmail(contributor.UserEmails) - sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, sfProject.Name, &foundationSFID, "cla-manager-designee", LfxPortalURL) + sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectService, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, projectSFs, projectSFIDs, foundationSFID, "cla-manager-designee", LfxPortalURL) if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) From 041766d699433026306602dae1383bc89375876f Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Feb 2021 15:08:59 -0800 Subject: [PATCH 0118/1276] Resolved [#2702] Return ICLA Signatures (#2712) - Added logic to ensure the correct filter/query was executing - Augmented the results to lookup missing github username, name, email, if available - response entries were previously missing some data Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 101 +++++++++++++++++++---- cla-backend-go/signatures/service.go | 13 +-- cla-backend-go/v2/signatures/handlers.go | 29 ++++--- cla-backend-go/v2/signatures/service.go | 11 +++ 4 files changed, 117 insertions(+), 37 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 457488782..db0ae7a95 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -431,7 +431,7 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(userID))) filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value("user"))). + And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) @@ -2295,15 +2295,27 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID "functionName": "GetClaGroupICLASignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, + "searchTerm": utils.StringValue(searchTerm), } - sortKeyPrefix := fmt.Sprintf("%s#%v#%v", utils.ClaTypeICLA, true, true) + //sortKeyPrefix := fmt.Sprintf("%s#%v#%v", utils.ClaTypeICLA, true, true) // This is the key we want to match - condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). - And(expression.Key("sigtype_signed_approved_id").BeginsWith(sortKeyPrefix)) + //condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). + // And(expression.Key("sigtype_signed_approved_id").BeginsWith(sortKeyPrefix)) + condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)) + + filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). + And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). + And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). + And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) // Use the builder to create the expression - expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildProjection()).Build() + expr, err := expression.NewBuilder(). + WithKeyCondition(condition). + WithFilter(filter). + WithProjection(buildProjection()). + Build() if err != nil { log.WithFields(f).Warnf("error building expression for get cla group icla signatures, claGroupID: %s, error: %v", claGroupID, err) @@ -2315,15 +2327,23 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID ExpressionAttributeNames: expr.Names(), ExpressionAttributeValues: expr.Values(), KeyConditionExpression: expr.KeyCondition(), + FilterExpression: expr.Filter(), ProjectionExpression: expr.Projection(), TableName: aws.String(repo.signatureTableName), - IndexName: aws.String(SignatureProjectIDSigTypeSignedApprovedIDIndex), - Limit: aws.Int64(HugePageSize), + //IndexName: aws.String(SignatureProjectIDSigTypeSignedApprovedIDIndex), + IndexName: aws.String(SignatureProjectIDIndex), + Limit: aws.Int64(HugePageSize), } - out := &models.IclaSignatures{List: make([]*models.IclaSignature, 0)} if searchTerm != nil { searchTerm = aws.String(strings.ToLower(*searchTerm)) } + + type IclaSignatureWithDetails struct { + IclaSignature *models.IclaSignature + SignatureReferenceID string + } + var intermediateResponse []*IclaSignatureWithDetails + for { // Make the DynamoDB Query API call results, queryErr := repo.dynamoDBClient.Query(queryInput) @@ -2347,20 +2367,24 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID continue } } + signedOn := sig.DateCreated if sig.SignedOn != "" { signedOn = sig.SignedOn } - out.List = append(out.List, &models.IclaSignature{ - GithubUsername: sig.UserGithubUsername, - LfUsername: sig.UserLFUsername, - SignatureID: sig.SignatureID, - UserEmail: sig.UserEmail, - UserName: sig.UserName, - SignedOn: signedOn, - UserDocusignName: sig.UserDocusignName, - UserDocusignDateSigned: sig.UserDocusignDateSigned, - SignatureModified: sig.DateModified, + intermediateResponse = append(intermediateResponse, &IclaSignatureWithDetails{ + IclaSignature: &models.IclaSignature{ + GithubUsername: sig.UserGithubUsername, + LfUsername: sig.UserLFUsername, + SignatureID: sig.SignatureID, + UserEmail: sig.UserEmail, + UserName: sig.UserName, + SignedOn: signedOn, + UserDocusignName: sig.UserDocusignName, + UserDocusignDateSigned: sig.UserDocusignDateSigned, + SignatureModified: sig.DateModified, + }, + SignatureReferenceID: sig.SignatureReferenceID, }) } @@ -2370,6 +2394,47 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID queryInput.ExclusiveStartKey = results.LastEvaluatedKey log.WithFields(f).Debug("querying next page") } + + log.WithFields(f).Debugf("Adding additional meta-data for %d records...", len(intermediateResponse)) + // For some older ICLA signatures, we are missing the user's info, but we have their internal ID - let's look up those values before returning + responseChannel := make(chan *models.IclaSignature) + for _, iclaSignatureWithDetails := range intermediateResponse { + go func(iclaSignatureWithDetails *IclaSignatureWithDetails) { + userModel, userLookupErr := repo.usersRepo.GetUser(iclaSignatureWithDetails.SignatureReferenceID) + if userLookupErr != nil || userModel == nil { + log.WithFields(f).WithError(userLookupErr).Warnf("unable to lookup user with id: %s", iclaSignatureWithDetails.SignatureReferenceID) + } else { + // If the github username is empty, see if it was set in the user model + if iclaSignatureWithDetails.IclaSignature.GithubUsername == "" { + // Grab and set the github username + iclaSignatureWithDetails.IclaSignature.GithubUsername = userModel.GithubUsername + } + // If the github username is empty, see if it was set in the user model + if iclaSignatureWithDetails.IclaSignature.UserName == "" { + if userModel.Username != "" { + // Grab and set the github username + iclaSignatureWithDetails.IclaSignature.UserName = userModel.Username + } else if userModel.LfUsername != "" { + iclaSignatureWithDetails.IclaSignature.UserName = userModel.LfUsername + } + } + // If the github username is empty, see if it was set in the user model + if iclaSignatureWithDetails.IclaSignature.UserEmail == "" { + // Grab and set the github username + iclaSignatureWithDetails.IclaSignature.UserEmail = getBestEmail(userModel) + } + } + + responseChannel <- iclaSignatureWithDetails.IclaSignature + }(iclaSignatureWithDetails) + } + + // Append all the responses to our list + out := &models.IclaSignatures{List: make([]*models.IclaSignature, 0)} + for i := 0; i < len(intermediateResponse); i++ { + out.List = append(out.List, <-responseChannel) + } + return out, nil } diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 8d2de5219..b418da046 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "strings" "sync" "github.com/aws/aws-sdk-go/aws" @@ -433,7 +434,7 @@ func (s service) UpdateApprovalList(ctx context.Context, authUser *auth.User, cl // Send an email to the CLA Managers for _, claManager := range claManagers { - claManagerEmail := getBestEmail(claManager) + claManagerEmail := getBestEmail(&claManager) // nolint s.sendApprovalListUpdateEmailToCLAManagers(companyModel, claGroupModel, claManager.Username, claManagerEmail, params) } @@ -846,13 +847,13 @@ you can now go back to it and follow the link to verify with your organization.< } // getBestEmail is a helper function to return the best email address for the user model -func getBestEmail(claManager models.User) string { - if claManager.LfEmail != "" { - return claManager.LfEmail +func getBestEmail(userModel *models.User) string { + if userModel.LfEmail != "" { + return userModel.LfEmail } - for _, email := range claManager.Emails { - if email != "" { + for _, email := range userModel.Emails { + if email != "" && !strings.Contains(email, "noreply.github.com") { return email } } diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index a4a86c281..10bb85a56 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -40,7 +40,7 @@ import ( ) // Configure setups handlers on api with service -func Configure(api *operations.EasyclaAPI, projectService project.Service, projectRepo project.ProjectRepository, companyService company.IService, v1SignatureService signatureService.SignatureService, sessionStore *dynastore.Store, eventsService events.Service, v2service Service, projectClaGroupsRepo projects_cla_groups.Repository) { //nolint +func Configure(api *operations.EasyclaAPI, claGroupService project.Service, projectRepo project.ProjectRepository, companyService company.IService, v1SignatureService signatureService.SignatureService, sessionStore *dynastore.Store, eventsService events.Service, v2service Service, projectClaGroupsRepo projects_cla_groups.Repository) { //nolint const problemLoadingCLAGroupByID = "problem loading cla group by ID" const iclaNotSupportedForCLAGroup = "individual contribution is not supported for this project" @@ -135,7 +135,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } log.WithFields(f).Debug("loading CLA groups by projectSFID") - projectModels, projsErr := projectService.GetCLAGroupsByExternalSFID(ctx, params.ProjectSFID) + projectModels, projsErr := claGroupService.GetCLAGroupsByExternalSFID(ctx, params.ProjectSFID) if projsErr != nil || projectModels == nil { msg := fmt.Sprintf("unable to locate projects by Project SFID: %s", params.ProjectSFID) log.WithFields(f).Warn(msg) @@ -143,7 +143,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } // Lookup the internal project ID when provided the external ID via the v1SignatureService call - claGroupModel, projErr := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, projErr := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if projErr != nil || claGroupModel == nil { msg := fmt.Sprintf("unable to locate project by CLA Group ID: %s", params.ClaGroupID) log.WithFields(f).Warn(msg) @@ -367,7 +367,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } log.WithFields(f).Debug("looking up CLA Group by ID...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { @@ -711,7 +711,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje log.WithFields(f).Debug("processing request...") log.WithFields(f).Debug("looking up CLA Group by ID...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { @@ -795,6 +795,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje }) }) + // GET https://api-gw.platform.linuxfoundation.org/v4/cla-group/{claGroupID}/icla/signatures api.SignaturesListClaGroupIclaSignatureHandler = signatures.ListClaGroupIclaSignatureHandlerFunc(func(params signatures.ListClaGroupIclaSignatureParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint @@ -802,10 +803,12 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje "functionName": "SignaturesListClaGroupIclaSignatureHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, + "searchTerm": utils.StringValue(params.SearchTerm), + "sortOrder": utils.StringValue(params.SortOrder), } log.WithFields(f).Debug("looking up CLA Group by ID...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { @@ -837,7 +840,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("searching for ICLA signatures...") - result, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm) + results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm) if err != nil { msg := fmt.Sprintf("problem loading ICLA signatures by CLA Group ID search term: %s", aws.StringValue(params.SearchTerm)) log.WithFields(f).WithError(err).Warn(msg) @@ -845,8 +848,8 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - log.WithFields(f).Debugf("returning %d ICLA signatures to caller...", len(result.List)) - return signatures.NewListClaGroupIclaSignatureOK().WithXRequestID(reqID).WithPayload(result) + log.WithFields(f).Debugf("returning %d ICLA signatures to caller...", len(results.List)) + return signatures.NewListClaGroupIclaSignatureOK().WithXRequestID(reqID).WithPayload(results) }) api.SignaturesListClaGroupCorporateContributorsHandler = signatures.ListClaGroupCorporateContributorsHandlerFunc(func(params signatures.ListClaGroupCorporateContributorsParams, authUser *auth.User) middleware.Responder { @@ -990,7 +993,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } log.WithFields(f).Debug("loading cla group by id...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { @@ -1044,7 +1047,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } log.WithFields(f).Debug("looking up CLA Group by ID...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { @@ -1111,7 +1114,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } log.WithFields(f).Debug("looking up CLA Group by ID...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { @@ -1164,7 +1167,7 @@ func Configure(api *operations.EasyclaAPI, projectService project.Service, proje } log.WithFields(f).Debug("looking up CLA Group by ID...") - claGroupModel, err := projectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := claGroupService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) if err == project.ErrProjectDoesNotExist { diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 2c0d33b8f..3d04f4075 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -168,15 +168,26 @@ func (s service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID str } func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) { + f := logrus.Fields{ + "functionName": "v2.signatures.service.GetProjectIclaSignatures", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "searchTerm": utils.StringValue(searchTerm), + } + var out models.IclaSignatures result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load ICLA signatures using the specified search parameters") return nil, err } + err = copier.Copy(&out, result) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to convert signature results from v1 to v2") return nil, err } + return &out, nil } From eb1bc2364a453677db5c19d2d4ce12d495a2e59a Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Feb 2021 15:22:19 -0800 Subject: [PATCH 0119/1276] Resolved [LFX-3130] Prevent Delete of Last CLA Manager (#2713) - Added logic to prevent the last CLA manager from deleting themself Signed-off-by: David Deal --- cla-backend-go/cla_manager/service.go | 7 +++++++ cla-backend-go/utils/errors.go | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index b6f5fb9c1..cb6e8b4fd 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -308,6 +308,13 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou return nil, sigErr } + if len(sigModel.SignatureACL) <= 1 { + // Can't delete the only remaining CLA Manager.... + return nil, &utils.CLAManagerError{ + Message: "unable to remove the only remaining CLA Manager - signed CLAs must have at least one CLA Manager", + } + } + // Update the signature ACL updatedSignature, aclErr := s.sigService.RemoveCLAManager(ctx, sigModel.SignatureID, LFID) if aclErr != nil || updatedSignature == nil { diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index bbf3b3dc3..ca6b930e1 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -323,3 +323,27 @@ func (e *GitHubRepositoryExists) Error() string { func (e *GitHubRepositoryExists) Unwrap() error { return e.Err } + +// CLAManagerError is an error model for when a CLA Manager error occurs +type CLAManagerError struct { + Message string + Err error +} + +// Error is an error string function for the CLAManagerError model +func (e *CLAManagerError) Error() string { + msg := "CLA Manager Error" + if e.Message != "" { + msg = e.Message + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *CLAManagerError) Unwrap() error { + return e.Err +} From 2e2b1dce90c023c6eed471d6e2f05064224cf9c2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Feb 2021 16:11:36 -0800 Subject: [PATCH 0120/1276] Resolved User Invite with Missing First and Last Name (#2714) - Added first and last name to user invite request - Added utlity method with tests to parse single first/last name string into separate pieces Signed-off-by: David Deal --- .../tests/utils_string_utils_test.go | 28 ++++++++ cla-backend-go/utils/string_utils.go | 16 +++++ cla-backend-go/v2/acs-service/client.go | 66 ++++++++++++------- cla-backend-go/v2/cla_manager/emails.go | 38 +++++++++-- 4 files changed, 120 insertions(+), 28 deletions(-) diff --git a/cla-backend-go/tests/utils_string_utils_test.go b/cla-backend-go/tests/utils_string_utils_test.go index f612c0427..c55173964 100644 --- a/cla-backend-go/tests/utils_string_utils_test.go +++ b/cla-backend-go/tests/utils_string_utils_test.go @@ -38,3 +38,31 @@ func TestTrimSpaceFromItems(t *testing.T) { assert.ObjectsAreEqualValues(expectedResults[i], utils.TrimSpaceFromItems(testInputs[i])) } } + +func TestGetFirstAndLastName(t *testing.T) { + + testInputs := []string{ + "", + "John", + "John Smith", + "John Smith", + "John Harold Smith", + "John Harold Smith", + "John Harold Zeek Smith", + } + expectedResults := [][]string{ + {"", ""}, + {"John", ""}, + {"John", "Smith"}, + {"John", "Smith"}, + {"John", "Smith"}, + {"John", "Smith"}, + {"John", "Smith"}, + } + + for i := range testInputs { + firstName, lastName := utils.GetFirstAndLastName(testInputs[i]) + assert.Equal(t, expectedResults[i][0], firstName) + assert.Equal(t, expectedResults[i][1], lastName) + } +} diff --git a/cla-backend-go/utils/string_utils.go b/cla-backend-go/utils/string_utils.go index 855c43785..934550ba2 100644 --- a/cla-backend-go/utils/string_utils.go +++ b/cla-backend-go/utils/string_utils.go @@ -24,3 +24,19 @@ func TrimSpaceFromItems(arr []string) []string { return newArr } + +// GetFirstAndLastName parses the user's name into first and last strings +func GetFirstAndLastName(firstAndLastName string) (string, string) { + // Parse the provided user's name + userNames := strings.Split(firstAndLastName, " ") + var userFirstName string + var userLastName string + if len(userNames) >= 2 { + userFirstName = userNames[0] + userLastName = userNames[len(userNames)-1] + } else if len(userNames) == 1 { + userFirstName = userNames[0] + } + + return strings.TrimSpace(userFirstName), strings.TrimSpace(userLastName) +} diff --git a/cla-backend-go/v2/acs-service/client.go b/cla-backend-go/v2/acs-service/client.go index 337efc62c..bdb930504 100644 --- a/cla-backend-go/v2/acs-service/client.go +++ b/cla-backend-go/v2/acs-service/client.go @@ -67,19 +67,35 @@ func GetClient() *Client { return acsServiceClient } +// SendUserInviteInput input model for sending user invites +type SendUserInviteInput struct { + InviteUserFirstName string + InviteUserLastName string + InviteUserEmail string + RoleName string + Scope string + ProjectSFID string + OrganizationSFID string + InviteType string + Subject string + EmailContent string + Automate bool +} + // SendUserInvite invites users to the LFX platform -func (ac *Client) SendUserInvite(ctx context.Context, email *string, - roleName string, scope string, projectID *string, organizationID string, inviteType string, subject *string, emailContent *string, automate bool) error { +//func (ac *Client) SendUserInvite(ctx context.Context, email *string, +// roleName string, scope string, projectID *string, organizationID string, inviteType string, subject *string, emailContent *string, automate bool) error { +func (ac *Client) SendUserInvite(ctx context.Context, input *SendUserInviteInput) error { f := logrus.Fields{ - "functionName": "SendUserInvite", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "roleName": roleName, - "scope": scope, - "projectID": utils.StringValue(projectID), - "organizationID": organizationID, - "inviteType": inviteType, - "subject": utils.StringValue(subject), - "automate": automate, + "functionName": "SendUserInvite", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "roleName": input.RoleName, + "scope": input.Scope, + "projectSFID": input.ProjectSFID, + "organizationSFID": input.OrganizationSFID, + "inviteType": input.InviteType, + "subject": input.Subject, + "automate": input.Automate, } tok, err := token.GetToken() @@ -91,30 +107,32 @@ func (ac *Client) SendUserInvite(ctx context.Context, email *string, clientAuth := runtimeClient.BearerToken(tok) params := &invite.CreateUserInviteParams{ SendInvite: &models.CreateInvite{ - Automate: automate, - Email: email, - Scope: scope, - RoleName: roleName, - Type: inviteType, + Automate: input.Automate, + Email: &input.InviteUserEmail, + FirstName: input.InviteUserFirstName, + LastName: input.InviteUserLastName, + RoleName: input.RoleName, + Scope: input.Scope, + Type: input.InviteType, }, Context: ctx, } - if scope == utils.ProjectOrgScope && projectID == nil { + if input.Scope == utils.ProjectOrgScope && input.ProjectSFID == "" { log.WithFields(f).Warnf("Project ID required for project|organization scope, error: %+v", ErrProjectIDMissing) return ErrProjectIDMissing } - if scope == utils.ProjectOrgScope { + if input.Scope == utils.ProjectOrgScope { // Set project|organization scope - params.SendInvite.ScopeID = fmt.Sprintf("%s|%s", *projectID, organizationID) + params.SendInvite.ScopeID = fmt.Sprintf("%s|%s", input.ProjectSFID, input.OrganizationSFID) } else { - params.SendInvite.ScopeID = organizationID + params.SendInvite.ScopeID = input.OrganizationSFID } - if subject != nil { - params.SendInvite.Subject = *subject + if input.Subject != "" { + params.SendInvite.Subject = input.Subject } // Pass emailContent if passed in the args - if emailContent != nil { - params.SendInvite.Body = *emailContent + if input.EmailContent != "" { + params.SendInvite.Body = input.EmailContent } log.WithFields(f).Debugf("Submitting ACS Service CreateUserInvite with payload: %+v", params) diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index ad3542fc4..e400f01d3 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -252,9 +252,24 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project } acsClient := v2AcsService.GetClient() - automate := false log.WithFields(f).Debug("sending user invite request...") - return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, &foundationSFID, organizationID, "userinvite", &subject, &body, automate) + + // Parse the provided user's name + userFirstName, userLastName := utils.GetFirstAndLastName(userWithNoLFIDName) + + return acsClient.SendUserInvite(ctx, &v2AcsService.SendUserInviteInput{ + InviteUserFirstName: userFirstName, + InviteUserLastName: userLastName, + InviteUserEmail: userWithNoLFIDEmail, + RoleName: role, + Scope: utils.ProjectOrgScope, + ProjectSFID: foundationSFID, + OrganizationSFID: organizationID, + InviteType: "userinvite", + Subject: subject, + EmailContent: body, + Automate: false, + }) } // sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID @@ -281,8 +296,23 @@ func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository proj return err } acsClient := v2AcsService.GetClient() - automate := false + + // Parse the provided user's name + userFirstName, userLastName := utils.GetFirstAndLastName(userWithNoLFIDName) log.WithFields(f).Debug("sending user invite request...") - return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, projectID, organizationID, "userinvite", &subject, &body, automate) + //return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, projectID, organizationID, "userinvite", &subject, &body, automate) + return acsClient.SendUserInvite(ctx, &v2AcsService.SendUserInviteInput{ + InviteUserFirstName: userFirstName, + InviteUserLastName: userLastName, + InviteUserEmail: userWithNoLFIDEmail, + RoleName: role, + Scope: utils.ProjectOrgScope, + ProjectSFID: *projectID, + OrganizationSFID: organizationID, + InviteType: "userinvite", + Subject: subject, + EmailContent: body, + Automate: false, + }) } From 34a93068e46c43964201d0eda46b0dd91fef709b Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Feb 2021 16:53:22 -0800 Subject: [PATCH 0121/1276] Resolved [#2706] Added User Name to Event Log - Added cla signatory name to docusing event log entries - Updated unit test for event time - ensure it uses UTC time Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 20 +++++++--- cla-backend/cla/models/dynamo_models.py | 48 +++++++++++++++++------ cla-backend/cla/tests/unit/test_event.py | 33 +++++++++++----- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 5ddaf461b..8a0f9c0c2 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -600,6 +600,7 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic event_project_id=project_id, event_project_name=project.get_project_name(), event_user_id=user.get_user_id(), + event_user_name=user.get_user_name() if user else None, event_data=event_data, event_summary=event_summary, contains_pii=True, @@ -707,6 +708,7 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url event_company_id=company_id, event_project_id=project_id, event_user_id=user_id, + event_user_name=user.get_user_name() if user else None, event_data=event_data, event_summary=event_summary, contains_pii=True, @@ -808,6 +810,7 @@ def request_employee_signature_gerrit(self, project_id, company_id, user_id, ret event_company_id=company_id, event_project_id=project_id, event_user_id=user_id, + event_user_name=user.get_user_name() if user else None, event_data=event_data, event_summary=event_summary, contains_pii=True, @@ -1459,6 +1462,7 @@ def signed_individual_callback(self, content, installation_id, github_repository event_project_id=signature.get_signature_project_id(), event_company_id=None, event_user_id=signature.get_signature_reference_id(), + event_user_name=user.get_user_name() if user else None, event_data=event_data, event_summary=event_summary, contains_pii=False, @@ -1512,6 +1516,7 @@ def signed_individual_callback_gerrit(self, content, user_id): event_project_id=signature.get_signature_project_id(), event_company_id=None, event_user_id=user.get_user_id(), + event_user_name=user.get_user_name(), event_data=event_data, event_summary=event_summary, contains_pii=False, @@ -1654,33 +1659,36 @@ def signed_corporate_callback(self, content, project_id, company_id): # Update our event/activity log if signature.get_signature_reference_type() == 'user': event_data = (f'The user {user.get_user_name()} signed an individual CLA for ' - f'project {project.get_project_name()}.') + f'the project {project.get_project_name()}.') event_summary = (f'The user {user.get_user_name()} signed an individual CLA for ' - f'project {project.get_project_name()} with project ID: {project.get_project_id()}.') + f'the project {project.get_project_name()} with ' + f'the project ID: {project.get_project_id()}.') Event.create_event( event_type=EventType.IndividualSignatureSigned, event_project_id=project_id, event_company_id=None, event_user_id=user.get_user_id(), + event_user_name=user.get_user_name(), event_data=event_data, event_summary=event_summary, contains_pii=False, ) elif signature.get_signature_reference_type() == 'company': - event_data = (f'Corporate signature ' - f'signed for project {project.get_project_name()} ' + event_data = (f'A corporate signature ' + f'was signed for project {project.get_project_name()} ' f'and company {company.get_company_name()} ' f'by {signature.get_signatory_name()}, ' f'params: {param_str}') event_summary = (f'A corporate signature ' - f'was signed for project {project.get_project_name()} ' - f'and company {company.get_company_name()} ' + f'was signed for the project {project.get_project_name()} ' + f'and the company {company.get_company_name()} ' f'by {signature.get_signatory_name()}.') Event.create_event( event_type=EventType.CompanySignatureSigned, event_project_id=project_id, event_company_id=company.get_company_id(), event_user_id=user.get_user_id(), + event_user_name=signature.get_signatory_name(), event_data=event_data, event_summary=event_summary, contains_pii=False, diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 771ce9aea..123ec1b79 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -30,6 +30,7 @@ import cla from cla.models import model_interfaces, key_value_store_interface, DoesNotExist +from cla.models.event_types import EventType from cla.models.model_interfaces import User, Signature, ProjectCLAGroup, Repository, Gerrit stage = os.environ.get("STAGE", "") @@ -4394,27 +4395,43 @@ def search_events(self, **kwargs): @classmethod def create_event( cls, - event_type=None, - event_project_id=None, - event_company_id=None, - event_project_name=None, - event_company_name=None, - event_data=None, - event_summary=None, - event_user_id=None, - contains_pii=False, - dry_run=False + event_type: Optional[EventType] = None, + event_project_id: Optional[str] = None, + event_company_id: Optional[str] = None, + event_project_name: Optional[str] = None, + event_company_name: Optional[str] = None, + event_data: Optional[str] = None, + event_summary: Optional[str] = None, + event_user_id: Optional[str] = None, + event_user_name: Optional[str] = None, + contains_pii: bool = False, + dry_run: bool = False ): """ Creates an event returns the newly created event in dict format. :param event_type: The type of event :type event_type: EventType - :param event_user_id: The user that is assocaited with the event - :type event_user_id: string :param event_project_id: The project associated with event :type event_project_id: string - + :param event_project_name: The project name associated with event + :type event_project_name: string + :param event_company_id: The company associated with event + :type event_company_id: string + :param event_company_name: The company name associated with event + :type event_company_name: string + :param event_data: The event message/data + :type event_data: string + :param event_summary: The event summary message/data + :type event_summary: string + :param event_user_id: The user that is associated with the event + :type event_user_id: string + :param event_user_name: The user's name that is associated with the event + :type event_user_name: string + :param contains_pii: flag to indicate if the message contains personal information (deprecated) + :type contains_pii: bool + :param dry_run: flag to indicate this is for testing and the record should not be stored/created + :type dry_run: bool """ try: event = cls() @@ -4440,6 +4457,7 @@ def create_event( event.set_event_company_id(event_company_id) except DoesNotExist as err: return {"errors": {"event_company_id": str(err)}} + if event_user_id: try: user = User() @@ -4450,6 +4468,10 @@ def create_event( event.set_event_user_name(user_name) except DoesNotExist as err: return {"errors": {"event_": str(err)}} + + if event_user_name: + event.set_event_user_name(event_user_name) + event.set_event_id(str(uuid.uuid4())) if event_type: event.set_event_type(event_type.name) diff --git a/cla-backend/cla/tests/unit/test_event.py b/cla-backend/cla/tests/unit/test_event.py index 9769b05c6..efc16696b 100644 --- a/cla-backend/cla/tests/unit/test_event.py +++ b/cla-backend/cla/tests/unit/test_event.py @@ -1,13 +1,14 @@ # Copyright The Linux Foundation and each contributor to CommunityBridge. # SPDX-License-Identifier: MIT -from cla.models.dynamo_models import Event, User, Project, Company -from cla.models import event_types -from unittest.mock import patch, Mock -import pytest import datetime -import cla import time +from unittest.mock import Mock + +import pytest + +from cla.models import event_types +from cla.models.dynamo_models import Event, User, Project, Company @pytest.fixture() @@ -16,6 +17,7 @@ def mock_event(): event.model.save = Mock() yield event + def test_event_user_id(user_instance): """ Test event_user_id """ Event.save = Mock() @@ -29,9 +31,10 @@ def test_event_user_id(user_instance): ) assert 'data' in response + def test_event_company_id(company): """ Test creation of event instance """ - #Case for creating Company + # Case for creating Company Event.save = Mock() Company.load = Mock() event_data = 'test company created' @@ -43,6 +46,7 @@ def test_event_company_id(company): ) assert 'data' in response + def test_event_project_id(project): """ Test event with event_project_id """ Event.save = Mock() @@ -56,45 +60,53 @@ def test_event_project_id(project): ) assert 'data' in response + def test_event_user_id_attribute(user_instance, mock_event): """ Test event_user_id attribute """ mock_event.set_event_user_id(user_instance.get_user_id()) mock_event.save() assert mock_event.get_event_user_id() == user_instance.get_user_id() + def test_event_company_name_lower_attribute(mock_event): """ Test company_name_lower attribute """ mock_event.set_event_company_name("Company_lower") mock_event.save() assert mock_event.get_event_company_name_lower() == "company_lower" + def test_event_username_attribute(mock_event): """ Test event_username attribute """ mock_event.set_event_user_name("foo_username") mock_event.save() assert mock_event.get_event_user_name() == "foo_username" + def test_event_user_name_lower_attribute(mock_event): """ Test event_user_name_lower attribute """ mock_event.set_event_user_name("Username") mock_event.save() assert mock_event.get_event_user_name_lower() == "username" + def test_event_project_name_lower_attribute(mock_event): - """ Test gettting project """ + """ Test getting project """ mock_event.set_event_project_name("Project") mock_event.save() assert mock_event.get_event_project_name_lower() == "project" + def test_event_time(mock_event): """ Test event time """ mock_event.save() - assert mock_event.get_event_time() <= datetime.datetime.now() + assert mock_event.get_event_time() <= datetime.datetime.utcnow() + def test_event_time_epoch(mock_event): """ Test event time epoch """ mock_event.save() - assert mock_event.get_event_time_epoch() <= time.time() + assert mock_event.get_event_time_epoch() <= datetime.datetime.utcnow().timestamp() + def test_company_id_external_project_id(mock_event): mock_event.set_event_project_external_id("external_id") @@ -102,16 +114,19 @@ def test_company_id_external_project_id(mock_event): mock_event.set_company_id_external_project_id() assert mock_event.get_company_id_external_project_id() == "company_id#external_id" + def test_company_id_external_project_id_empty_test1(mock_event): mock_event.set_event_project_external_id("external_id") mock_event.set_company_id_external_project_id() assert mock_event.get_company_id_external_project_id() == None + def test_company_id_external_project_id_empty_test2(mock_event): mock_event.set_event_company_id("company_id") mock_event.set_company_id_external_project_id() assert mock_event.get_company_id_external_project_id() == None + def test_company_id_external_project_id_empty_test3(mock_event): mock_event.set_company_id_external_project_id() assert mock_event.get_company_id_external_project_id() == None From 326a159c4c7a871a9c003739496f86f6611e8187 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 26 Feb 2021 21:15:15 +0300 Subject: [PATCH 0122/1276] [#2703,#2704,#2705] Feature/Emails (#2717) - Updated Email subject for Signed CLAs - Updated content for email sent to designees - Updated approvals accepted email content Signed-off-by: wanyaland --- cla-backend-go/approval_list/handlers.go | 2 +- cla-backend-go/approval_list/service.go | 124 +++++++++++++----- cla-backend-go/approval_list/service_test.go | 26 ---- cla-backend-go/cmd/server.go | 2 +- .../emails/approval_list_templates.go | 54 +++++++- .../emails/approval_list_templates_test.go | 23 ++++ cla-backend-go/emails/params.go | 9 ++ .../emails/v2_cla_manager_templates.go | 6 +- .../tests/v2_cla_manager_templates_test.go | 10 +- cla-backend-go/v2/cla_manager/emails.go | 2 + cla-backend-go/v2/cla_manager/service.go | 15 ++- cla-backend/cla/models/docusign_models.py | 2 +- .../cla/tests/unit/test_docusign_models.py | 2 +- 13 files changed, 200 insertions(+), 77 deletions(-) delete mode 100644 cla-backend-go/approval_list/service_test.go diff --git a/cla-backend-go/approval_list/handlers.go b/cla-backend-go/approval_list/handlers.go index 94193a314..c362d5407 100644 --- a/cla-backend-go/approval_list/handlers.go +++ b/cla-backend-go/approval_list/handlers.go @@ -47,7 +47,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore func(params company.ApproveCclaWhitelistRequestParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - err := service.ApproveCclaWhitelistRequest(ctx, params.CompanyID, params.ProjectID, params.RequestID) + err := service.ApproveCclaWhitelistRequest(ctx, claUser, params.CompanyID, params.ProjectID, params.RequestID) if err != nil { return company.NewApproveCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index d9d5b34b7..cf8ad6731 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -9,6 +9,8 @@ import ( "fmt" "net/http" + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/emails" @@ -20,6 +22,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/gen/models" @@ -38,7 +41,7 @@ const ( // IService interface defines the service methods/functions type IService interface { AddCclaWhitelistRequest(ctx context.Context, companyID string, claGroupID string, args models.CclaWhitelistRequestInput) (string, error) - ApproveCclaWhitelistRequest(ctx context.Context, companyID, claGroupID, requestID string) error + ApproveCclaWhitelistRequest(ctx context.Context, claUser *user.CLAUser, ClacompanyID, claGroupID, requestID string) error RejectCclaWhitelistRequest(ctx context.Context, companyID, claGroupID, requestID string) error ListCclaWhitelistRequest(companyID string, claGroupID, status *string) (*models.CclaWhitelistRequestList, error) ListCclaWhitelistRequestByCompanyProjectUser(companyID string, claGroupID, status, userID *string) (*models.CclaWhitelistRequestList, error) @@ -46,6 +49,7 @@ type IService interface { type service struct { repo IRepository + projectService project.Service userRepo users.UserRepository companyRepo company.IRepository projectRepo project.ProjectRepository @@ -56,10 +60,11 @@ type service struct { } // NewService creates a new whitelist service -func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups.Repository, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, corpConsoleURL string, httpClient *http.Client) IService { +func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups.Repository, projService project.Service, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, corpConsoleURL string, httpClient *http.Client) IService { return service{ repo: repo, projectsCLAGroupRepository: projectsCLAGroupRepository, + projectService: projService, userRepo: userRepo, companyRepo: companyRepo, projectRepo: projectRepo, @@ -128,10 +133,18 @@ func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, } // ApproveCclaWhitelistRequest is the handler for the approve CLA request -func (s service) ApproveCclaWhitelistRequest(ctx context.Context, companyID, claGroupID, requestID string) error { +func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user.CLAUser, companyID, claGroupID, requestID string) error { + + f := logrus.Fields{ + "functionName": "ApproveCclaWhitelistRequest", + "companyID": companyID, + "claGroupID": claGroupID, + "requestID": requestID, + "Approver": claUser.Name, + } err := s.repo.ApproveCclaWhitelistRequest(requestID) if err != nil { - log.Warnf("ApproveCclaWhitelistRequest - problem updating approved list with 'approved' status for request: %s, error: %+v", + log.WithFields(f).Warnf("ApproveCclaWhitelistRequest - problem updating approved list with 'approved' status for request: %s, error: %+v", requestID, err) return err } @@ -160,8 +173,43 @@ func (s service) ApproveCclaWhitelistRequest(ctx context.Context, companyID, cla return errors.New(msg) } + // Get project cla Group records + log.WithFields(f).Debugf("Getting SalesForce Projects for claGroup: %s ", claGroupID) + projectCLAGroups, getErr := s.projectsCLAGroupRepository.GetProjectsIdsForClaGroup(claGroupID) + if getErr != nil { + msg := fmt.Sprintf("Error getting SF projects for claGroup: %s ", claGroupID) + log.Debug(msg) + } + + if len(projectCLAGroups) == 0 { + msg := fmt.Sprintf("Error getting SF projects for claGroup: %s ", claGroupID) + return errors.New(msg) + } + + signedAtFoundation, signedErr := s.projectService.SignedAtFoundationLevel(ctx, projectCLAGroups[0].FoundationSFID) + if signedErr != nil { + msg := fmt.Sprintf("Problem checking project: %s , error: %+v", claGroupID, signedErr) + log.WithFields(f).Warn(msg) + return signedErr + } + + var projectSFIDs []string + foundationSFID := projectCLAGroups[0].FoundationSFID + + if signedAtFoundation { + // Get salesforce project by FoundationID + log.WithFields(f).Debugf("querying project service for project details...") + projectSFIDs = append(projectSFIDs, foundationSFID) + } else { + for _, pcg := range projectCLAGroups { + log.WithFields(f).Debugf("Getting salesforce project by SFID: %s ", pcg.ProjectSFID) + projectSFIDs = append(projectSFIDs, pcg.ProjectSFID) + } + } + // Send the email - sendRequestApprovedEmailToRecipient(companyModel, claGroupModel, requestModel.UserName, requestModel.UserEmails[0]) + s.sendRequestApprovedEmailToRecipient(ctx, s.projectService, s.projectsCLAGroupRepository, *claUser, companyModel, claGroupModel, + requestModel.UserName, requestModel.UserEmails[0], projectSFIDs) return nil } @@ -345,40 +393,50 @@ func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Compan } } -func requestApprovedEmailToRecipientContent(companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string) (string, string, []string) { - companyName := companyModel.CompanyName +func (s service) sendRequestApprovedEmailToRecipient(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, claUser user.CLAUser, companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, projectSFIDs []string) { + + f := logrus.Fields{ + "functionName": "sendRequestApprovedEmailToRecipient", + utils.XREQUESTID: ctx.Value((utils.XREQUESTID)), + "claGroupName": claGroupModel.ProjectName, + "claGroupID": claGroupModel.ProjectID, + "companyName": companyModel.CompanyName, + "recipientName": recipientName, + "recipientAddress": recipientAddress, + } + companyName := companyModel.CompanyName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Approved List Request Accepted for %s", companyName) recipients := []string{recipientAddress} - body := fmt.Sprintf(` -

        Hello %s,

        -

        This is a notification email from EasyCLA regarding the company %s.

        -

        You have now been added to the approval list for %s.

        -

        To get started, please navigate back to GitHub or Gerrit and start the authorization process. Once you select the -authorization link, you will be directed to the EasyCLA Contributor Console. GitHub users will need to authorize the -tool to see your GitHub user name and email. Gerrit users will first need to log in with their LF Account. On the -console landing page, select the corporate agreement option. To finish, search and select your company to acknowledge -your association with your company. This will complete the authorization process. For GitHub users, your pull request -will refresh and confirm that you are authorized. For Gerrit users, please log out of the UI and back in to complete the -authorization.

        -%s -%s`, - recipientName, companyName, - companyName, - utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), - utils.GetEmailSignOffContent()) - - return subject, body, recipients -} -// sendRequestApprovedEmailToRecipient generates and sends an email to the specified recipient -func sendRequestApprovedEmailToRecipient(companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string) { - subject, body, recipients := requestApprovedEmailToRecipientContent(companyModel, claGroupModel, recipientName, recipientAddress) - err := utils.SendEmail(subject, body, recipients) + approver := "" + if claUser.LFUsername != "" { + approver = claUser.LFUsername + } else if claUser.LFEmail != "" { + approver = claUser.LFEmail + } else if claUser.Emails != nil { + approver = claUser.Emails[0] + } + + body, err := emails.RenderApprovalListTemplate( + repository, projectService, projectSFIDs, emails.ApprovalListApprovedTemplateParams{ + ApprovalTemplateParams: emails.ApprovalTemplateParams{ + RecipientName: recipientName, + CompanyName: companyName, + CLAGroupName: claGroupModel.ProjectName, + Approver: approver, + }, + }, + ) if err != nil { - log.Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) + log.WithFields(f).Warnf("rendering email failed for : %s : %v", emails.ApprovalListApprovedTemplateName, err) + return + } + err = utils.SendEmail(subject, body, recipients) + if err != nil { + log.WithFields(f).Warnf("problem sending email with subject: %s to recipients: %+v, error: %+v", subject, recipients, err) } else { - log.Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) + log.WithFields(f).Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) } } diff --git a/cla-backend-go/approval_list/service_test.go b/cla-backend-go/approval_list/service_test.go deleted file mode 100644 index ebc053a70..000000000 --- a/cla-backend-go/approval_list/service_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package approval_list - -import ( - "testing" - - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/stretchr/testify/assert" -) - -func TestRequestApprovedEmailToRecipientContent(t *testing.T) { - subject, body, recipients := requestApprovedEmailToRecipientContent( - &models.Company{ - CompanyName: "gardenerLtd"}, - &models.ClaGroup{Version: "v2"}, - "john", - "john@john.com") - - assert.Equal(t, "EasyCLA: Approved List Request Accepted for gardenerLtd", subject) - assert.Equal(t, []string{"john@john.com"}, recipients) - assert.Contains(t, body, "Hello john,") - assert.Contains(t, body, "This is a notification email from EasyCLA regarding the company gardenerLtd") - assert.Contains(t, body, "You have now been added to the approval list for gardenerLtd") -} diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 82cea5da4..0f2d13ce6 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -270,7 +270,7 @@ func server(localMode bool) http.Handler { v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) - v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) + v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) diff --git a/cla-backend-go/emails/approval_list_templates.go b/cla-backend-go/emails/approval_list_templates.go index 478cf4cac..4b0dc1613 100644 --- a/cla-backend-go/emails/approval_list_templates.go +++ b/cla-backend-go/emails/approval_list_templates.go @@ -3,7 +3,13 @@ package emails -import "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" +import ( + "strings" + + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/utils" +) // ApprovalListRejectedTemplateParams is email params for ApprovalListRejectedTemplate type ApprovalListRejectedTemplateParams struct { @@ -28,6 +34,24 @@ If you have further questions about this denial, please contact one of the exist ` ) +// ApprovalListApprovedTemplateParams is email params for Approval +type ApprovalListApprovedTemplateParams struct { + ApprovalTemplateParams +} + +const ( + // ApprovalListApprovedTemplateName is email template name for ApprovalListRejectedTemplate + ApprovalListApprovedTemplateName = "ApprovalListApprovedTemplate" + // ApprovalListApprovedTemplate is email template for + ApprovalListApprovedTemplate = ` +

        Hello {{.RecipientName}},

        +

        This is a notification email from EasyCLA regarding the CLA Group {{.CLAGroupName}}.

        +

        You have been added to the Approval list of {{.CompanyName}} for {{.CLAGroupName}} by CLA Manager {{.Approver}}. +

        This means that you are authorized to contribute to the any of the following project(s) associated with the CLA Group {{.CLAGroupName}}: {{.GetProjects}}

        +

        If you had previously submitted a pull request to any any the above project(s) that had failed, you can now go back to it and follow the link to verify with your organization.

        + ` +) + // RequestToAuthorizeTemplateParams is email params for RequestToAuthorizeTemplate type RequestToAuthorizeTemplateParams struct { CLAManagerTemplateParams @@ -72,3 +96,31 @@ func RenderRequestToAuthorizeTemplate(repository projects_cla_groups.Repository, ) } + +// GetProjects returns the single Project or comma separated projects if more than one +func (p ApprovalListApprovedTemplateParams) GetProjects() string { + if len(p.Projects) == 1 { + return p.Projects[0].ExternalProjectName + } + + var projectNames []string + for _, p := range p.Projects { + projectNames = append(projectNames, p.ExternalProjectName) + } + + return strings.Join(projectNames, ", ") +} + +// RenderApprovalListTemplate renders RenderApprovalListTemplate +func RenderApprovalListTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params ApprovalListApprovedTemplateParams) (string, error) { + // prefill the projects data + projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, "") + if err != nil { + return "", err + } + + params.Projects = projects + + return RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, + ApprovalListApprovedTemplate, params) +} diff --git a/cla-backend-go/emails/approval_list_templates_test.go b/cla-backend-go/emails/approval_list_templates_test.go index f2096da8f..f7c2524a7 100644 --- a/cla-backend-go/emails/approval_list_templates_test.go +++ b/cla-backend-go/emails/approval_list_templates_test.go @@ -31,6 +31,29 @@ func TestApprovalListRejectedTemplate(t *testing.T) { assert.Contains(t, result, "
      • LFUserName LFEmail
      • ") } +func TestApprovalListApprovedTemplate(t *testing.T) { + params := ApprovalListApprovedTemplateParams{ + ApprovalTemplateParams: ApprovalTemplateParams{ + RecipientName: "Recipient", + CLAGroupName: "CLAGroupFoo", + CompanyName: "CompanyFoo", + Approver: "LFUsername", + Projects: []CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, + }, + } + + result, err := RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, ApprovalListApprovedTemplate, params) + + assert.NoError(t, err) + assert.Contains(t, result, "Hello Recipient") + assert.Contains(t, result, "regarding the CLA Group CLAGroupFoo") + assert.Contains(t, result, "You have been added to the Approval list of CompanyFoo for CLAGroupFoo by CLA Manager LFUsername.") + assert.Contains(t, result, "This means that you are authorized to contribute to the any of the following project(s) associated with the CLA Group CLAGroupFoo: Project1, Project2") +} + func TestRequestToAuthorizeTemplate(t *testing.T) { params := RequestToAuthorizeTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ diff --git a/cla-backend-go/emails/params.go b/cla-backend-go/emails/params.go index a54884c87..fca576309 100644 --- a/cla-backend-go/emails/params.go +++ b/cla-backend-go/emails/params.go @@ -69,6 +69,15 @@ type CLAManagerTemplateParams struct { ChildProjectCount int } +// ApprovalTemplateParams details approval fields for contributor +type ApprovalTemplateParams struct { + RecipientName string + CompanyName string + CLAGroupName string + Approver string + Projects []CLAProjectParams +} + // GetProjectNameOrFoundation returns if the foundationName is set it gets back // the foundation Name otherwise the ProjectName is returned func (claParams CLAManagerTemplateParams) GetProjectNameOrFoundation() string { diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 8d20d06ed..3457c3d1f 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -186,16 +186,14 @@ const ( V2ToCLAManagerDesigneeTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the project(s): {{.GetProjectsOrProject}}.

        -

        We received a request from {{.ContributorName}} ({{.ContributorEmail}}): to contribute to the above projects on behalf of your organization

        -

        Before the user contribution can be accepted, your organization must sign a Corporate CLA (CCLA).The requester has stated that you would be the initial CLA Manager for this CCLA, to coordinate the signing of the CCLA and then manage the list of employees who are authorized to contribute

        +

        We received a request from {{.ContributorName}} ({{.ContributorEmail}}) to contribute to the above projects on behalf of your organization.

        +

        Before the user contribution can be accepted, your organization must sign a Corporate CLA (CCLA).The requester has stated that you would be the initial CLA Manager for this CCLA, to coordinate the signing of the CCLA and then manage the list of employees who are authorized to contribute.

        Please complete the following steps:

        1. After login, you will be redirected to the portal {{.CorporateConsole}} where you can either sign the CLA for any of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}, or send it to an authorized signatory for your company.
        2. After signing the CLA, you will need to add this contributor to the approved list in the CLA Manager console.
        3. After adding the contributor, please notify them so that they can complete the contribution process.
        -

        Kindly login to this portal {{.CorporateConsole}} and sign the CLA for one of the project(s): {{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}}.

        -

        After signing the CLA, you will need to add this contributor to the approved list. Please notify the contributor once they are added, so that they can complete the contribution process.

        ` ) diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index decf5ec26..33999a9bf 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -149,9 +149,8 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s): Project1, Project2") - assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue):") - assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, `CLA for one of the project(s): Project1,Project2`) + assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue)") + assert.Contains(t, result, `CLA for any of the project(s): Project1,Project2`) params.Projects = []emails.CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, @@ -161,9 +160,8 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s): Project1") - assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue):") - assert.Contains(t, result, "Kindly login to this portal http://CorporateConsole.com") - assert.Contains(t, result, `CLA for one of the project(s): Project1`) + assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue)") + assert.Contains(t, result, `CLA for any of the project(s): Project1`) } diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index e400f01d3..18dce0f9e 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -234,6 +234,8 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project "projectNames": strings.Join(projectNames, ","), "role": role, "corporateConsoleV2URL": corporateConsoleV2URL, + "requesterUsername": requesterUsername, + "requesterEmail": requesterEmail, } subject := "EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager" diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index fb9706796..2b1ec453f 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -832,7 +832,7 @@ func (s *service) ValidateInviteCompanyAdminCheck(ctx context.Context, f logrus. return nil } -func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, LfxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) { +func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, LfxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) { //nolint f := logrus.Fields{ "functionName": "cla_manager.service.InviteCompanyAdmin", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -976,10 +976,19 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com // Get suggested CLA Manager user details user, userErr := userService.SearchUserByEmail(userEmail) if userErr != nil || (user != nil && user.Username == "") { + contributorEmail, contributorUsername := "", "" msg := fmt.Sprintf("UserEmail: %s has no LF Login and has been sent an invite email to create an account , error: %+v", userEmail, userErr) log.Warn(msg) - contibutorEmail := GetNonNoReplyUserEmail(contributor.UserEmails) - sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectService, s.projectCGRepo, contributor.UserName, contibutorEmail, name, userEmail, organization.Name, organization.ID, projectSFs, projectSFIDs, foundationSFID, "cla-manager-designee", LfxPortalURL) + + // Get username and useremail details for contributor + if contributor.LFEmail != "" && contributor.UserName != "" { + contributorEmail = contributor.LFEmail + contributorUsername = contributor.UserName + } else { + contributorUsername, contributorEmail = getContributorPublicEmail(contributor) + } + + sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectService, s.projectCGRepo, contributorUsername, contributorEmail, name, userEmail, organization.Name, organization.ID, projectSFs, projectSFIDs, foundationSFID, "cla-manager-designee", LfxPortalURL) if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 8a0f9c0c2..8c9aca376 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -2205,7 +2205,7 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa else: recipient_name = "CLA Manager" - subject = f'EasyCLA: CLA Signature Signed for {project.get_project_name()}' + subject = f'EasyCLA: CLA Signed for {project.get_project_name()}' body = f'''

        Hello {recipient_name},

        This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

        diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index 1c0f6437c..f07b856c6 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -822,7 +822,7 @@ def test_document_signed_email_content(): assert subject is not None assert body is not None - assert "Signature Signed for JohnsProject" in subject + assert "Signed for JohnsProject" in subject assert "Hello john" in body assert "EasyCLA regarding the project JohnsProject" in body assert "The CLA has now been signed." in body From 53b136ab39e6359b113cbd7a7e204ef285d0f7f8 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 2 Mar 2021 02:28:12 +0300 Subject: [PATCH 0123/1276] [#3136] Bug/Corporate Console Admin Email (#2724) - Resolved email sending to company admin on corporate console Signed-off-by: wanyaland --- cla-backend-go/v2/cla_manager/service.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 2b1ec453f..f6ce5a327 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -661,6 +661,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool } orgService := v2OrgService.GetClient() + userService := v2UserService.GetClient() log.WithFields(f).Debugf("loading company by external ID...") // Search for salesForce Company aka external Company @@ -720,7 +721,14 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool for _, admin := range scopes.Userroles { log.WithFields(f).Debugf("sending email to organization admin: %+v", admin) - s.SendEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, admin.Contact.EmailAddress, admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) + + adminUser, adminErr := userService.GetUser(admin.Contact.ID) + if adminErr != nil { + msg := fmt.Sprintf("Failed to get user for ID: %s ", admin.Contact.ID) + log.Warn(msg) + return nil, adminErr + } + s.SendEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) // Make a note in the event log s.eventService.LogEvent(&events.LogEventArgs{ EventType: events.ContributorNotifyCompanyAdminType, @@ -738,7 +746,6 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool } log.WithFields(f).Debug("not sending admin email...") - userService := v2UserService.GetClient() log.WithFields(f).Debug("searching user in user service...") // This routine is taking 24-29 seconds when running locally -> User service in DEV //lfxUser, userErr := userService.SearchUserByEmail(userEmail) From 69c0b2a0eaaca663d34d361b68e2f7ef948e51c1 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 2 Mar 2021 02:32:27 +0300 Subject: [PATCH 0124/1276] Feature/Python hug logging (#2722) - Added elapsed time metrics to Python APIs Signed-off-by: wanyaland --- cla-backend/cla/middleware.py | 29 +++++++++++++++++++++++++++++ cla-backend/cla/routes.py | 3 ++- cla-backend/cla/utils.py | 5 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 cla-backend/cla/middleware.py diff --git a/cla-backend/cla/middleware.py b/cla-backend/cla/middleware.py new file mode 100644 index 000000000..7c5b072e9 --- /dev/null +++ b/cla-backend/cla/middleware.py @@ -0,0 +1,29 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +from hug.middleware import LogMiddleware +from datetime import datetime +from timeit import default_timer + +class CLALogMiddleware(LogMiddleware): + """CLA log middleware""" + + def __init__(self, logger=None): + super().__init__(logger=logger) + self.elapsed_time = 0 + self.start_time = None + self.end_time = None + + def process_request(self, request, response): + """Logs CLA request """ + self.logger.info(f'BEGIN {request.method} {request.path}') + self.start_time = datetime.utcnow() + super().process_request(request, response) + + def process_response(self, request, response, resource, req_succeeded): + """Logs data returned by CLA API """ + if self.start_time: + self.elapsed_time = datetime.utcnow() - self.start_time + super().process_response(request, response, resource, req_succeeded) + self.logger.info(f'END {request.method} {request.path} - elapsed_time : {self.elapsed_time.seconds} secs') + diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 1b4eb787d..5c17879f2 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -34,6 +34,7 @@ get_supported_repository_providers, get_supported_document_content_types, get_session_middleware, + get_log_middleware ) @@ -1786,4 +1787,4 @@ def create_event( # Session Middleware __hug__.http.add_middleware(get_session_middleware()) -__hug__.http.add_middleware(LogMiddleware(logger=cla.log)) +__hug__.http.add_middleware(get_log_middleware()) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 2c8d0a5c7..9a4826325 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -17,6 +17,8 @@ import falcon import requests from hug.middleware import SessionMiddleware +from hug.middleware import LogMiddleware +from cla.middleware import CLALogMiddleware from requests_oauthlib import OAuth2Session import cla @@ -38,6 +40,9 @@ def get_cla_path(): cla_root_dir = os.path.dirname(cla_folder_dir) return cla_root_dir +def get_log_middleware(): + """Prepare the hug middleware to manage logging. """ + return CLALogMiddleware(logger=cla.log) def get_session_middleware(): """Prepares the hug middleware to manage key-value session data.""" From c5da947061f44b37d3a044b8ee1b9154898925b4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 1 Mar 2021 15:46:08 -0800 Subject: [PATCH 0125/1276] Resolved 2716 Activity Log for Enroll/Unenroll (#2725) - Added new events for enroll/unenroll - Refactored event log to ensure CLA Group and Project ID were distinct. Updated load info logic. - Added utility method to help differentiate between internal ID's and SFID's Signed-off-by: David Deal --- cla-backend-go/approval_list/handlers.go | 6 +- cla-backend-go/cla_manager/handlers.go | 8 +- cla-backend-go/cla_manager/service.go | 10 +- cla-backend-go/company/handlers.go | 8 +- cla-backend-go/events/event_data.go | 250 ++++++++++------ cla-backend-go/events/event_types.go | 8 +- cla-backend-go/events/repository.go | 19 +- cla-backend-go/events/service.go | 175 ++++++++--- cla-backend-go/gerrits/handlers.go | 4 +- .../github_organizations/handlers.go | 6 +- cla-backend-go/project/handlers.go | 18 +- cla-backend-go/repositories/handlers.go | 4 +- cla-backend-go/signatures/handlers.go | 4 +- cla-backend-go/signatures/service.go | 37 +-- cla-backend-go/swagger/common/event.yaml | 21 +- cla-backend-go/tests/utils_test.go | 24 ++ cla-backend-go/utils/utils.go | 6 + cla-backend-go/v2/cla_groups/handlers.go | 157 ++++------ cla-backend-go/v2/cla_groups/helpers.go | 84 ++++-- cla-backend-go/v2/cla_groups/models.go | 40 +++ cla-backend-go/v2/cla_groups/service.go | 110 ++++--- .../v2/dynamo_events/projects_cla_groups.go | 279 +++++++++--------- cla-backend-go/v2/dynamo_events/service.go | 5 +- 23 files changed, 781 insertions(+), 502 deletions(-) create mode 100644 cla-backend-go/v2/cla_groups/models.go diff --git a/cla-backend-go/approval_list/handlers.go b/cla-backend-go/approval_list/handlers.go index c362d5407..0a6b59bec 100644 --- a/cla-backend-go/approval_list/handlers.go +++ b/cla-backend-go/approval_list/handlers.go @@ -32,7 +32,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore return company.NewAddCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CCLAApprovalListRequestCreated, ProjectID: params.ProjectID, CompanyID: params.CompanyID, @@ -52,7 +52,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore return company.NewApproveCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CCLAApprovalListRequestApproved, ProjectID: params.ProjectID, CompanyID: params.CompanyID, @@ -72,7 +72,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore return company.NewRejectCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CCLAApprovalListRequestRejected, ProjectID: params.ProjectID, CompanyID: params.CompanyID, diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 47e687b2e..a4f9752a9 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -165,7 +165,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Send an event - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerAccessRequestCreated, ProjectID: params.ProjectID, ClaGroupModel: claGroupModel, @@ -339,7 +339,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Send an event - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerAccessRequestApproved, ProjectID: params.ProjectID, CompanyID: params.CompanyID, @@ -441,7 +441,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Send an event - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerAccessRequestDenied, ProjectID: params.ProjectID, CompanyID: params.CompanyID, @@ -569,7 +569,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Send an event - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerAccessRequestDeleted, ProjectID: params.ProjectID, CompanyID: params.CompanyID, diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index cb6e8b4fd..7ed134f6e 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -239,9 +239,11 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID sendClaManagerAddedEmailToUser(companyModel, claGroupModel, userModel.Username, userModel.LfEmail, projectSFName) // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerCreated, - ProjectID: claGroupID, + ProjectID: claGroupModel.ProjectExternalID, + CLAGroupID: claGroupID, + CLAGroupName: claGroupModel.ProjectName, ClaGroupModel: claGroupModel, CompanyID: companyID, CompanyModel: companyModel, @@ -341,7 +343,9 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ EventType: events.ClaManagerDeleted, - ProjectID: claGroupID, + ProjectID: claGroupModel.ProjectExternalID, + CLAGroupID: claGroupID, + CLAGroupName: claGroupModel.ProjectName, ClaGroupModel: claGroupModel, CompanyID: companyID, CompanyModel: companyModel, diff --git a/cla-backend-go/company/handlers.go b/cla-backend-go/company/handlers.go index 0c7cebbf5..f4fc7243a 100644 --- a/cla-backend-go/company/handlers.go +++ b/cla-backend-go/company/handlers.go @@ -257,7 +257,7 @@ func Configure(api *operations.ClaAPI, service IService, usersService users.Serv return company.NewAddUsertoCompanyAccessListBadRequest().WithXRequestID(reqID) } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CompanyACLUserAdded, CompanyID: params.CompanyID, UserID: claUser.UserID, @@ -280,7 +280,7 @@ func Configure(api *operations.ClaAPI, service IService, usersService users.Serv } // Add an event to the log - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CompanyACLRequestAdded, CompanyID: params.CompanyID, UserID: claUser.UserID, @@ -305,7 +305,7 @@ func Configure(api *operations.ClaAPI, service IService, usersService users.Serv } // Add an event to the log - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CompanyACLRequestApproved, CompanyID: params.CompanyID, UserID: claUser.UserID, @@ -330,7 +330,7 @@ func Configure(api *operations.ClaAPI, service IService, usersService users.Serv } // Add an event to the log - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CompanyACLRequestDenied, CompanyID: params.CompanyID, UserID: claUser.UserID, diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 097f70186..0e48bbb00 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -13,6 +13,18 @@ type EventData interface { GetEventSummaryString(args *LogEventArgs) (eventData string, containsPII bool) } +type CLAGroupEnrolledProjectData struct { +} + +type CLAGroupUnenrolledProjectData struct { +} + +type ProjectServiceCLAEnabledData struct { +} + +type ProjectServiceCLADisabledData struct { +} + // RepositoryAddedEventData . . . type RepositoryAddedEventData struct { RepositoryName string @@ -344,90 +356,114 @@ type ClaManagerRoleDeletedData struct { UserEmail string } +// GetEventDetailsString . . . +func (ed *CLAGroupEnrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s (%s/%s) enabled the the project %s (%s) from the CLA Group %s (%s).", + args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.ClaGroupModel.ProjectName, args.ClaGroupModel.ProjectID), false +} + +// GetEventDetailsString . . . +func (ed *CLAGroupUnenrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s (%s/%s) unenrolled the the project %s (%s) from the CLA Group %s (%s).", + args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.ClaGroupModel.ProjectName, args.ClaGroupModel.ProjectID), false +} + +// GetEventDetailsString . . . +func (ed *ProjectServiceCLAEnabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s (%s/%s) enabled the CLA Service for the project %s (%s)", + args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false +} + +// GetEventDetailsString . . . +func (ed *ProjectServiceCLADisabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s (%s/%s) disabled the CLA Service for the project %s (%s)", + args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false +} + // GetEventDetailsString . . . func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository: %s was added for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository: %s was added for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was deleted for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository %s was deleted for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was updated for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository %s was updated for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *UserCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s added. User Details: %+v.", args.userName, args.UserModel) + data := fmt.Sprintf("User: %s added. User Details: %+v.", args.UserName, args.UserModel) return data, true } // GetEventDetailsString . . . func (ed *UserUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("User: %s updated. User Details: %+v.", args.userName, *args.UserModel), true + return fmt.Sprintf("User: %s updated. User Details: %+v.", args.UserName, *args.UserModel), true } // GetEventDetailsString . . . func (ed *UserDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s deleted. User ID: %s.", args.userName, ed.DeletedUserID) + data := fmt.Sprintf("User: %s deleted. User ID: %s.", args.UserName, ed.DeletedUserID) return data, true } // GetEventDetailsString . . . func (ed *CompanyACLRequestAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s added pending invite with ID: %s and Email: %s for Company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.companyName) + ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) return data, true } // GetEventDetailsString . . . func (ed *CompanyACLRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Access Aproved for User: %s, ID: %s, Email: %s Company Group: %s.", - ed.UserName, args.companyName, ed.UserID, ed.UserEmail) + ed.UserName, args.CompanyName, ed.UserID, ed.UserEmail) return data, true } // GetEventDetailsString . . . func (ed *CompanyACLRequestDeniedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Access Denied for User: %s, ID: %s, Email: %s Company Group: %s.", - ed.UserName, args.companyName, ed.UserID, ed.UserEmail) + ed.UserName, args.CompanyName, ed.UserID, ed.UserEmail) return data, true } // GetEventDetailsString . . . func (ed *CompanyACLUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User with LF Username: %s added to the ACL for Company: %s by: %s.", - ed.UserLFID, args.companyName, args.userName) + ed.UserLFID, args.CompanyName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *CLATemplateCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("PDF Templates created for Project: %s by: %s.", args.userName, args.projectName) + data := fmt.Sprintf("PDF Templates created for Project: %s by: %s.", args.UserName, args.ProjectName) return data, true } @@ -438,14 +474,14 @@ func (ed *GitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEvent if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf(" by: %s.", args.userName) + data = data + fmt.Sprintf(" by: %s.", args.UserName) return data, true } // GetEventDetailsString . . . func (ed *GitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("GitHub Organization: %s was deleted by: %s.", - ed.GitHubOrganizationName, args.userName) + ed.GitHubOrganizationName, args.UserName) return data, true } @@ -456,21 +492,21 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEve if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf("by: %s.", args.userName) + data = data + fmt.Sprintf("by: %s.", args.UserName) return data, true } // GetEventDetailsString . . . func (ed *CCLAApprovalListRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s approved a CCLA Approval Request for Project: %s and Company: %s with Request ID: %s.", - args.userName, args.projectName, args.companyName, ed.RequestID) + args.UserName, args.ProjectName, args.CompanyName, ed.RequestID) return data, true } // GetEventDetailsString . . . func (ed *CCLAApprovalListRequestRejectedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s rejected a CCLA Approval Request for Project: %s, Company: %s with Request ID: %s.", - args.userName, args.projectName, args.companyName, ed.RequestID) + args.UserName, args.ProjectName, args.CompanyName, ed.RequestID) return data, true } @@ -519,161 +555,161 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventDetailsString(args *LogEven // GetEventDetailsString . . . func (ed *CLAApprovalListAddEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Email: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Email: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Domain: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Domain %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Username: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Username: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Organization: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Organization: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.companyName, args.projectName) + ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *CCLAApprovalListRequestCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s created a CCLA Approval Request for Project: %s, Company: %s with Request ID: %s.", - args.userName, args.projectName, args.companyName, ed.RequestID) + args.UserName, args.ProjectName, args.CompanyName, ed.RequestID) return data, true } // GetEventDetailsString . . . func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s added GitHub Organization: %s to the whitelist for Company %s, Project: %s.", - args.userName, ed.GitHubOrganizationName, args.companyName, args.projectName) + args.UserName, ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s removed GitHub Organization: %s from the whitelist for Company: %s, Project: %s.", - args.userName, ed.GitHubOrganizationName, args.companyName, args.projectName) + args.UserName, ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *ClaManagerAccessRequestAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s has requested to be CLA Manager for Company %s, Project: %s.", - args.userName, ed.CompanyName, ed.ProjectName) + args.UserName, ed.CompanyName, ed.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *ClaManagerAccessRequestDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s has deleted CLA Manager Request with ID: %s.", - args.userName, ed.RequestID) + args.UserName, ed.RequestID) return data, true } // GetEventDetailsString . . . func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Group ID: %s, Name: %s was created by: %s.", - args.ProjectID, args.projectName, args.userName) + args.ProjectID, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Group ID: %s was updated by: %s with Name: %s, Description: %s.", - args.ProjectID, args.userName, ed.ClaGroupName, ed.ClaGroupDescription) + args.ProjectID, args.UserName, ed.ClaGroupName, ed.ClaGroupDescription) return data, true } // GetEventDetailsString . . . func (ed *CLAGroupDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Group ID: %s was deleted by: %s.", - args.ProjectID, args.userName) + args.ProjectID, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *GerritProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d Gerrit Repositories were deleted due to CLA Group/Project: %s deletion.", - ed.DeletedCount, args.projectName) + ed.DeletedCount, args.ProjectName) return data, false } // GetEventDetailsString . . . func (ed *GerritAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Gerrit Repository: %s was added by: %s.", ed.GerritRepositoryName, args.userName) + data := fmt.Sprintf("Gerrit Repository: %s was added by: %s.", ed.GerritRepositoryName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *GerritDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Gerrit Repository: %s was deleted by: %s.", ed.GerritRepositoryName, args.userName) + data := fmt.Sprintf("Gerrit Repository: %s was deleted by: %s.", ed.GerritRepositoryName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *GitHubProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d GitHub Repositories were deleted due to CLA Group/Project: [%s] deletion.", - ed.DeletedCount, args.projectName) + ed.DeletedCount, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *SignatureProjectInvalidatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d Signatures were invalidated (approved set to false) due to CLA Group/Project: %s deletion.", - ed.InvalidatedCount, args.projectName) + ed.InvalidatedCount, args.ProjectName) return data, true } // GetEventDetailsString . . . func (ed *ContributorNotifyCompanyAdminData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s notified Company Admin: %s by Email: %s for Company ID: %s, Name: %s.", - args.userName, ed.AdminName, ed.AdminEmail, args.companyName, args.CompanyID) + args.UserName, ed.AdminName, ed.AdminEmail, args.CompanyName, args.CompanyID) return data, true } // GetEventDetailsString . . . func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s notified CLA Designee: %s by Email: %s for Project Name : %s, ID: %s and Company Name: %s, ID: %s.", - args.userName, ed.DesigneeName, ed.DesigneeEmail, - args.projectName, args.ExternalProjectID, - args.companyName, args.CompanyID) + args.UserName, ed.DesigneeName, ed.DesigneeEmail, + args.ProjectName, args.ExternalProjectID, + args.CompanyName, args.CompanyID) return data, true } @@ -681,8 +717,8 @@ func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs func (ed *ContributorAssignCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User Name: %s, Email: %s was assigned as CLA Manager Designee for project Name: %s, ID: %s and Company Name: %s, ID: %s by: %s.", ed.DesigneeName, ed.DesigneeEmail, - args.projectName, args.ExternalProjectID, - args.companyName, args.CompanyID, args.userName) + args.ProjectName, args.ExternalProjectID, + args.CompanyName, args.CompanyID, args.UserName) return data, true } @@ -703,102 +739,124 @@ func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string // GetEventDetailsString . . . func (ed *ClaManagerRoleCreatedData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s, Email: %s was added to Role: %s with Scope: %s by: %s.", ed.UserName, ed.UserEmail, ed.Role, ed.Scope, args.userName) + data := fmt.Sprintf("User: %s, Email: %s was added to Role: %s with Scope: %s by: %s.", ed.UserName, ed.UserEmail, ed.Role, ed.Scope, args.UserName) return data, false } // GetEventDetailsString . . . func (ed *ClaManagerRoleDeletedData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s, Email: %s was removed from Role: %s with Scope: %s by: %s.", ed.UserName, ed.UserEmail, ed.Role, ed.Scope, args.userName) + data := fmt.Sprintf("User: %s, Email: %s was removed from Role: %s with Scope: %s by: %s.", ed.UserName, ed.UserEmail, ed.Role, ed.Scope, args.UserName) return data, false } // Event Summary started +// GetEventDetailsString . . . +func (ed *CLAGroupEnrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s enabled the the project %s from the CLA Group %s.", + args.UserName, args.ProjectName, args.ClaGroupModel.ProjectName), false +} + +// GetEventDetailsString . . . +func (ed *CLAGroupUnenrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s unenrolled the the project %s from the CLA Group %s.", + args.UserName, args.ProjectName, args.ClaGroupModel.ProjectName), false +} + +// GetEventDetailsString . . . +func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s enabled the CLA Service for the project %s.", args.UserName, args.ProjectName), false +} + +// GetEventDetailsString . . . +func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + return fmt.Sprintf("%s disabled the CLA Service for the project %s.", args.UserName, args.ProjectName), false +} + // GetEventSummaryString . . . func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Repository: %s was added to Project: %s by: %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("GitHub Repository: %s was added to Project: %s by: %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Repository: %s was deleted from Project: %s by: %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("GitHub Repository: %s was deleted from Project: %s by: %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *RepositoryUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Repository: %s was updated for the project project: %s by: %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("GitHub Repository: %s was updated for the project project: %s by: %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s by the user %s.", ed.RepositoryName, args.projectName, args.userName) + data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *UserCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was added, User Details: %+v.", args.userName, args.UserModel) + data := fmt.Sprintf("User: %s was added, User Details: %+v.", args.UserName, args.UserModel) return data, true } // GetEventSummaryString . . . func (ed *UserUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("User: %s was updated, User Details: %+v.", args.userName, *args.UserModel), true + return fmt.Sprintf("User: %s was updated, User Details: %+v.", args.UserName, *args.UserModel), true } // GetEventSummaryString . . . func (ed *UserDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User ID : %s was deleted by: %s.", ed.DeletedUserID, args.userName) + data := fmt.Sprintf("User ID : %s was deleted by: %s.", ed.DeletedUserID, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CompanyACLRequestAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s with ID: %s, Email: %s requested Company Invite for Company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.companyName) + ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *CompanyACLRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Company Invite was approved access for User: %s with ID: %s, Email: %s for company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.companyName) + ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *CompanyACLRequestDeniedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Company Invite was denied access for User: %s with ID: %s, Email %s for Company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.companyName) + ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *CompanyACLUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User with LF Username %s was added to the ACL for Company: %s by: %s.", - ed.UserLFID, args.companyName, args.userName) + ed.UserLFID, args.CompanyName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CLATemplateCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("PDF templates were created for Project %s by: %s.", args.projectName, args.userName) + data := fmt.Sprintf("PDF templates were created for Project %s by: %s.", args.ProjectName, args.UserName) return data, true } @@ -809,14 +867,14 @@ func (ed *GitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEvent if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf(" by: %s.", args.userName) + data = data + fmt.Sprintf(" by: %s.", args.UserName) return data, true } // GetEventSummaryString . . . func (ed *GitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("GitHub Organization: %s was deleted by: %s.", - ed.GitHubOrganizationName, args.userName) + ed.GitHubOrganizationName, args.UserName) return data, true } @@ -827,21 +885,21 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEve if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf(" by: %s.", args.userName) + data = data + fmt.Sprintf(" by: %s.", args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CCLAApprovalListRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s approved a CCLA Approval Request for Project: %s, Company: %s.", - args.userName, args.projectName, args.companyName) + args.UserName, args.ProjectName, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *CCLAApprovalListRequestRejectedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s rejected a CCLA Approval Request for Project: %s, Company: %s.", - args.userName, args.projectName, args.companyName) + args.UserName, args.ProjectName, args.CompanyName) return data, true } @@ -890,159 +948,159 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventSummaryString(args *LogEven // GetEventSummaryString . . . func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s added Email: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListEmail, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s removed Email: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListEmail, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s added Domain: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListDomain, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s removed Domain: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListDomain, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s added GitHub Username: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubUsername, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s removed GitHub Username: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubUsername, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s added GitHub Organization: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubOrg, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s removed GitHub Organization: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubOrg, args.companyName, args.projectName) + ed.UserName, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *CCLAApprovalListRequestCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s created a CCLA Approval Request for Project: %s, Company: %s.", - args.userName, args.projectName, args.companyName) + args.UserName, args.ProjectName, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s added GitHub Organization: %s to the whitelist for Project: %s, Company: %s.", - args.userName, ed.GitHubOrganizationName, args.projectName, args.companyName) + args.UserName, ed.GitHubOrganizationName, args.ProjectName, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager: %s removed GitHub Organization: %s from the whitelist for Project: %s, Company: %s.", - args.userName, ed.GitHubOrganizationName, args.projectName, args.companyName) + args.UserName, ed.GitHubOrganizationName, args.ProjectName, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *ClaManagerAccessRequestAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s has requested to be CLA Manager for the project %s, the company %s.", - args.userName, ed.ProjectName, ed.CompanyName) + args.UserName, ed.ProjectName, ed.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *ClaManagerAccessRequestDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s has deleted a request to be CLA Manager.", - args.userName) + args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Group %s was created by the user %s.", - args.projectName, args.userName) + args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was updated by the user %s.", args.projectName, args.userName) + data := fmt.Sprintf("The CLA Group %s was updated by the user %s.", args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CLAGroupDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Group %s was deleted by the user %s.", - args.projectName, args.userName) + args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *GerritProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d Gerrit repositories were deleted due to CLA Group/Project %s deletion.", - ed.DeletedCount, args.projectName) + ed.DeletedCount, args.ProjectName) return data, false } // GetEventSummaryString . . . func (ed *GerritAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gerrit repository %s was added by: %s.", ed.GerritRepositoryName, args.userName) + data := fmt.Sprintf("The Gerrit repository %s was added by: %s.", ed.GerritRepositoryName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *GerritDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gerrit repository %s was deleted by %s.", ed.GerritRepositoryName, args.userName) + data := fmt.Sprintf("The Gerrit repository %s was deleted by %s.", ed.GerritRepositoryName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *GitHubProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d GitHub repositories were deleted due to CLA Group/project %s deletion.", - ed.DeletedCount, args.projectName) + ed.DeletedCount, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *SignatureProjectInvalidatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d signatures were invalidated (approved set to false) due to CLA Group/Project %s deletion.", - ed.InvalidatedCount, args.projectName) + ed.InvalidatedCount, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *ContributorNotifyCompanyAdminData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s notified the company admin %s by the email address %s for the company %s.", - args.userName, ed.AdminName, ed.AdminEmail, args.companyName) + args.UserName, ed.AdminName, ed.AdminEmail, args.CompanyName) return data, true } // GetEventSummaryString . . . func (ed *ContributorNotifyCLADesignee) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s notified the CLA Designee %s by email %s for the project: %s and the company %s.", - args.userName, ed.DesigneeName, ed.DesigneeEmail, - args.projectName, args.companyName) + args.UserName, ed.DesigneeName, ed.DesigneeEmail, + args.ProjectName, args.CompanyName) return data, true } @@ -1050,31 +1108,31 @@ func (ed *ContributorNotifyCLADesignee) GetEventSummaryString(args *LogEventArgs func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s was assigned as CLA Manager Designee for the project: %s for the company %s by the user %s.", ed.DesigneeName, - args.projectName, args.companyName, args.userName) + args.ProjectName, args.CompanyName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s was converted to a contact for the project %s.", - args.LfUsername, args.projectName) + args.LfUsername, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added to the role %s for project: %s.", args.LfUsername, ed.Role, args.projectName) + data := fmt.Sprintf("The user %s was added to the role %s for project: %s.", args.LfUsername, ed.Role, args.ProjectName) return data, true } // GetEventSummaryString . . . func (ed *ClaManagerRoleCreatedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added with to the role %s by user %s.", ed.UserName, ed.Role, args.userName) + data := fmt.Sprintf("The user %s was added with to the role %s by user %s.", ed.UserName, ed.Role, args.UserName) return data, false } // GetEventSummaryString . . . func (ed *ClaManagerRoleDeletedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was removed from the role %s by user %s.", ed.UserName, ed.Role, args.userName) + data := fmt.Sprintf("The user %s was removed from the role %s by user %s.", ed.UserName, ed.Role, args.UserName) return data, false } diff --git a/cla-backend-go/events/event_types.go b/cla-backend-go/events/event_types.go index ab32212d6..d9753be8d 100644 --- a/cla-backend-go/events/event_types.go +++ b/cla-backend-go/events/event_types.go @@ -69,9 +69,11 @@ const ( ClaManagerRoleCreated = "cla_manager.added" ClaManagerRoleDeleted = "cla_manager.deleted" - CLAGroupCreated = "cla_group.created" - CLAGroupUpdated = "cla_group.updated" - CLAGroupDeleted = "cla_group.deleted" + CLAGroupCreated = "cla_group.created" + CLAGroupUpdated = "cla_group.updated" + CLAGroupDeleted = "cla_group.deleted" + CLAGroupEnrolledProject = "cla_group.enrolled.project" + CLAGroupUnenrolledProject = "cla_group.unenrolled.project" InvalidatedSignature = "signature.invalidated" diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 89853230f..880b03cd3 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -104,6 +104,7 @@ func (repo *repository) CreateEvent(event *models.Event) error { Item: map[string]*dynamodb.AttributeValue{}, TableName: aws.String(fmt.Sprintf("cla-%s-events", repo.stage)), } + eventDateAndContainsPII := fmt.Sprintf("%s#%t", toDateFormat(currentTime), event.ContainsPII) addAttribute(input.Item, "event_id", eventID.String()) addAttribute(input.Item, "event_type", event.EventType) @@ -111,23 +112,33 @@ func (repo *repository) CreateEvent(event *models.Event) error { addAttribute(input.Item, "event_user_name", event.UserName) addAttribute(input.Item, "event_lf_username", event.LfUsername) addAttribute(input.Item, "event_user_name_lower", strings.ToLower(event.UserName)) + addAttribute(input.Item, "event_time", currentTimeString) + addAttribute(input.Item, "event_date", toDateFormat(currentTime)) addAttribute(input.Item, "event_data", event.EventData) addAttribute(input.Item, "event_summary", event.EventSummary) + addAttribute(input.Item, "event_company_id", event.EventCompanyID) + addAttribute(input.Item, "event_company_sfid", event.EventCompanySFID) addAttribute(input.Item, "event_company_name", event.EventCompanyName) addAttribute(input.Item, "event_company_name_lower", strings.ToLower(event.EventCompanyName)) + + addAttribute(input.Item, "event_cla_group_id", event.EventCLAGroupID) + addAttribute(input.Item, "event_cla_group_name", event.EventCLAGroupName) + addAttribute(input.Item, "event_cla_group_name_lower", strings.ToLower(event.EventCLAGroupName)) + addAttribute(input.Item, "event_project_id", event.EventProjectID) + addAttribute(input.Item, "event_project_external_id", event.EventProjectExternalID) addAttribute(input.Item, "event_project_name", event.EventProjectName) addAttribute(input.Item, "event_project_name_lower", strings.ToLower(event.EventProjectName)) - addAttribute(input.Item, "event_date", toDateFormat(currentTime)) - addAttribute(input.Item, "event_project_external_id", event.EventProjectExternalID) + addAttribute(input.Item, "event_date_and_contains_pii", eventDateAndContainsPII) + input.Item["contains_pii"] = &dynamodb.AttributeValue{BOOL: &event.ContainsPII} input.Item["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(currentTime.Unix(), 10))} if event.EventCompanyID != "" && event.EventProjectExternalID != "" { - companyIDexternalProjectID := fmt.Sprintf("%s#%s", event.EventCompanyID, event.EventProjectExternalID) - addAttribute(input.Item, "company_id_external_project_id", companyIDexternalProjectID) + companyIDExternalProjectID := fmt.Sprintf("%s#%s", event.EventCompanyID, event.EventProjectExternalID) + addAttribute(input.Item, "company_id_external_project_id", companyIDExternalProjectID) } _, err = repo.dynamoDBClient.PutItem(input) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 34d0439d7..317ea5bf4 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -8,6 +8,10 @@ import ( "errors" "fmt" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/communitybridge/easycla/cla-backend-go/gen/models" @@ -25,6 +29,7 @@ const ( // Service interface defines methods of event service type Service interface { LogEvent(args *LogEventArgs) + LogEventWithContext(ctx context.Context, args *LogEventArgs) SearchEvents(params *eventOps.SearchEventsParams) (*models.EventList, error) GetRecentEvents(paramPageSize *int64) (*models.EventList, error) @@ -108,65 +113,128 @@ func (s *service) GetCompanyEvents(companyID, eventType string, nextKey *string, // EventType, EventData are compulsory. // One of LfUsername, UserID must be present type LogEventArgs struct { - EventType string - ProjectID string - ClaGroupModel *models.ClaGroup - CompanyID string - CompanyModel *models.Company - LfUsername string - UserID string - UserModel *models.User + EventType string + ExternalProjectID string - EventData EventData - userName string - projectName string - companyName string + ProjectName string + ProjectSFID string + + ProjectID string // Should just use CLA GroupID + CLAGroupID string + CLAGroupName string + ClaGroupModel *models.ClaGroup + + CompanyModel *models.Company + CompanyID string + CompanyName string + CompanySFID string + + LfUsername string + UserName string + UserID string + UserModel *models.User + + EventData EventData } func (s *service) loadCompany(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "loadCompany", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + if args.CompanyModel != nil { - args.companyName = args.CompanyModel.CompanyName + args.CompanyName = args.CompanyModel.CompanyName args.CompanyID = args.CompanyModel.CompanyID + args.CompanySFID = args.CompanyModel.CompanyExternalID return nil - } - if args.CompanyID != "" { + } else if args.CompanyID != "" { companyModel, err := s.combinedRepo.GetCompany(ctx, args.CompanyID) if err != nil { + log.WithFields(f).WithError(err).Warnf("failed to load company record ID: %s", args.CompanyID) return err } args.CompanyModel = companyModel - args.companyName = companyModel.CompanyName + args.CompanyName = companyModel.CompanyName + args.CompanySFID = companyModel.CompanyExternalID } + return nil } -func (s *service) loadProject(ctx context.Context, args *LogEventArgs) error { +func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "loadCLAGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + if args.ClaGroupModel != nil { - args.ProjectID = args.ClaGroupModel.ProjectID - args.projectName = args.ClaGroupModel.ProjectName + args.CLAGroupID = args.ClaGroupModel.ProjectID + args.ProjectName = args.ClaGroupModel.ProjectName args.ExternalProjectID = args.ClaGroupModel.ProjectExternalID return nil } - if args.ProjectID != "" { - claGroupModel, err := s.combinedRepo.GetCLAGroupByID(ctx, args.ProjectID, DontLoadRepoDetails) + + claGroupID := "" + if args.CLAGroupID != "" { + claGroupID = args.CLAGroupID + } else if args.ProjectID != "" && utils.IsUUIDv4(args.ProjectID) { // legacy parameter + claGroupID = args.ProjectID + } + + if claGroupID != "" { + claGroupModel, err := s.combinedRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { + log.WithFields(f).WithError(err).Warnf("failed to load CLA Group by ID: %s", claGroupID) return err } args.ClaGroupModel = claGroupModel - args.projectName = claGroupModel.ProjectName + args.ProjectName = claGroupModel.ProjectName args.ExternalProjectID = claGroupModel.ProjectExternalID + return nil } + return nil } -func (s *service) loadUser(args *LogEventArgs) error { +func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "loadSFProject", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + if utils.IsUUIDv4(args.ProjectID) { // internal CLA Group ID + return s.loadCLAGroup(ctx, args) + } else if utils.IsSalesForceID(args.ProjectID) { // external SF project ID + args.ProjectSFID = args.ProjectID + args.ExternalProjectID = args.ProjectID + // Check if project exists in platform project service + project, projectErr := project_service.GetClient().GetProject(args.ProjectSFID) + if projectErr != nil || project == nil { + log.WithFields(f).Warnf("failed to load salesforce project by ID: %s", args.ProjectSFID) + return nil + } + args.ProjectName = project.Name + return nil + } + + return nil +} + +func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "loadUser", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + if args.UserModel != nil { - args.userName = args.UserModel.Username + args.UserName = args.UserModel.Username args.UserID = args.UserModel.UserID args.LfUsername = args.UserModel.LfUsername return nil } if args.UserID == "" && args.LfUsername == "" { + log.WithFields(f).Warn("failed to load user for event log - user ID and username were not set") return errors.New("require userID or LfUsername") } var userModel *models.User @@ -174,77 +242,106 @@ func (s *service) loadUser(args *LogEventArgs) error { if args.LfUsername != "" { userModel, err = s.combinedRepo.GetUserByUserName(args.LfUsername, true) if err != nil { + log.WithFields(f).WithError(err).Warnf("failed to load user by username: %s", args.LfUsername) return err } } if args.UserID != "" { userModel, err = s.combinedRepo.GetUser(args.UserID) if err != nil { + log.WithFields(f).WithError(err).Warnf("failed to load user by ID: %s", args.UserID) return err } } if userModel != nil { args.UserModel = userModel - args.userName = userModel.Username + args.UserName = userModel.Username args.UserID = userModel.UserID args.LfUsername = userModel.LfUsername + } else { + log.WithFields(f).Warnf("unable to set user information for event log entry") } return nil } +// loadDetails fetches and sets additional information into the data model required to fill out the event log entry func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { err := s.loadCompany(ctx, args) if err != nil { return err } - err = s.loadProject(ctx, args) + + err = s.loadCLAGroup(ctx, args) if err != nil { return err } - err = s.loadUser(args) + + err = s.loadSFProject(ctx, args) if err != nil { return err } + + err = s.loadUser(ctx, args) + if err != nil { + return err + } + return nil } -// LogEvent logs the event in database -func (s *service) LogEvent(args *LogEventArgs) { - ctx := utils.NewContext() +// LogEventWithContext logs the event in database +func (s *service) LogEventWithContext(ctx context.Context, args *LogEventArgs) { + f := logrus.Fields{ + "functionName": "events.service.LogEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + defer func() { if r := recover(); r != nil { - log.Error("panic occurred in CreateEvent", fmt.Errorf("%v", r)) + log.WithFields(f).Error("panic occurred in CreateEvent", fmt.Errorf("%v", r)) } }() + if args == nil || args.EventType == "" || args.EventData == nil || (args.UserID == "" && args.LfUsername == "") { - log.Warnf("invalid arguments to LogEvent, missing one or more required values. args %#v", args) + log.WithFields(f).Warnf("invalid arguments to LogEvent, missing one or more required values. args %#v", args) return } + err := s.loadDetails(ctx, args) if err != nil { - log.Error("unable to load details for event", err) + log.WithFields(f).Error("unable to load details for event", err) return } + eventData, containsPII := args.EventData.GetEventDetailsString(args) eventSummary, _ := args.EventData.GetEventSummaryString(args) event := models.Event{ ContainsPII: containsPII, + EventCLAGroupID: args.CLAGroupID, + EventCLAGroupName: args.ProjectName, EventCompanyID: args.CompanyID, - EventCompanyName: args.companyName, + EventCompanySFID: args.CompanySFID, + EventCompanyName: args.CompanyName, EventData: eventData, - EventSummary: eventSummary, EventProjectExternalID: args.ExternalProjectID, EventProjectID: args.ProjectID, - EventProjectName: args.projectName, + EventProjectName: args.ProjectName, + EventProjectSFID: args.ProjectSFID, + EventSummary: eventSummary, EventType: args.EventType, - UserID: args.UserID, - UserName: args.userName, LfUsername: args.LfUsername, + UserID: args.UserID, + UserName: args.UserName, } err = s.repo.CreateEvent(&event) if err != nil { - log.Error(fmt.Sprintf("unable to create event for args %#v", args), err) + log.WithFields(f).Error(fmt.Sprintf("unable to create event for args %#v", args), err) } } + +// LogEvent logs the event in database +func (s *service) LogEvent(args *LogEventArgs) { + s.LogEventWithContext(utils.NewContext(), args) +} diff --git a/cla-backend-go/gerrits/handlers.go b/cla-backend-go/gerrits/handlers.go index d82043782..f34cfa2a4 100644 --- a/cla-backend-go/gerrits/handlers.go +++ b/cla-backend-go/gerrits/handlers.go @@ -52,7 +52,7 @@ func Configure(api *operations.ClaAPI, service Service, projectService ProjectSe return gerrits.NewDeleteGerritBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } // record the event - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.GerritRepositoryDeleted, ClaGroupModel: claGroupModel, UserID: claUser.UserID, @@ -92,7 +92,7 @@ func Configure(api *operations.ClaAPI, service Service, projectService ProjectSe return gerrits.NewAddGerritBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } // record the event - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.GerritRepositoryAdded, ClaGroupModel: claGroupModel, UserID: claUser.UserID, diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index 1e0e87bcb..7d494cdf0 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -89,7 +89,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv if params.Body.BranchProtectionEnabled != nil { branchProtectionEnabled = *params.Body.BranchProtectionEnabled } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ UserID: claUser.UserID, EventType: events.GitHubOrganizationAdded, ExternalProjectID: params.ProjectSFID, @@ -125,7 +125,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv return github_organizations.NewDeleteProjectGithubOrganizationBadRequest().WithPayload(errorResponse(err)) } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ UserID: claUser.UserID, EventType: events.GitHubOrganizationDeleted, ExternalProjectID: params.ProjectSFID, @@ -163,7 +163,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(errorResponse(err)) } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ UserID: claUser.UserID, EventType: events.GitHubOrganizationUpdated, ExternalProjectID: params.ProjectSFID, diff --git a/cla-backend-go/project/handlers.go b/cla-backend-go/project/handlers.go index 74b5321e7..d0973c947 100644 --- a/cla-backend-go/project/handlers.go +++ b/cla-backend-go/project/handlers.go @@ -76,8 +76,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser return project.NewCreateProjectBadRequest().WithPayload(errorResponse(err)) } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CLAGroupCreated, + ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, @@ -200,8 +201,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser // Log gerrit event if howMany > 0 { log.WithFields(f).Debugf("Deleted %d gerrit groups", howMany) - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.GerritRepositoryDeleted, + ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, @@ -221,8 +223,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser log.WithFields(f).Debugf("Deleted %d github repositories", howMany) // Log github delete event - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, + ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, @@ -241,8 +244,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser if howMany > 0 { log.WithFields(f).Debugf("Invalidated %d signatures", howMany) // Log invalidate signatures - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.InvalidatedSignature, + ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, @@ -259,8 +263,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser } return project.NewDeleteProjectByIDBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CLAGroupDeleted, + ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, @@ -304,8 +309,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser } // Log an event - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.CLAGroupUpdated, + ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, diff --git a/cla-backend-go/repositories/handlers.go b/cla-backend-go/repositories/handlers.go index 0ccc68d13..6a327007d 100644 --- a/cla-backend-go/repositories/handlers.go +++ b/cla-backend-go/repositories/handlers.go @@ -52,7 +52,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv if err != nil { return github_repositories.NewAddProjectGithubRepositoryBadRequest().WithPayload(errorResponse(err)) } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryAdded, ProjectID: utils.StringValue(params.GithubRepositoryInput.RepositoryProjectID), ExternalProjectID: params.ProjectSFID, @@ -90,7 +90,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv if err != nil { return github_repositories.NewDeleteProjectGithubRepositoryBadRequest().WithPayload(errorResponse(err)) } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, ExternalProjectID: params.ProjectSFID, ProjectID: ghRepo.RepositoryProjectID, diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index e541edb55..50dfa8ebf 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -211,7 +211,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d projectID = signatureModel.ProjectID companyID = signatureModel.SignatureReferenceID } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ApprovalListGitHubOrganizationAdded, ProjectID: projectID, CompanyID: companyID, @@ -262,7 +262,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d companyID = signatureModel.SignatureReferenceID } - eventsService.LogEvent(&events.LogEventArgs{ + eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ApprovalListGitHubOrganizationDeleted, ProjectID: projectID, CompanyID: companyID, diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index b418da046..ce6d2a9f1 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -430,7 +430,7 @@ func (s service) UpdateApprovalList(ctx context.Context, authUser *auth.User, cl } // Log Events - s.createEventLogEntries(companyModel, claGroupModel, userModel, params) + s.createEventLogEntries(ctx, companyModel, claGroupModel, userModel, params) // Send an email to the CLA Managers for _, claManager := range claManagers { @@ -638,12 +638,12 @@ func (s service) sendRequestAccessEmailToContributors(authUser *auth.User, compa } } -func (s service) createEventLogEntries(companyModel *models.Company, claGroupModel *models.ClaGroup, userModel *models.User, approvalList *models.ApprovalList) { +func (s service) createEventLogEntries(ctx context.Context, companyModel *models.Company, claGroupModel *models.ClaGroup, userModel *models.User, approvalList *models.ApprovalList) { for _, value := range approvalList.AddEmailApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -661,9 +661,9 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.RemoveEmailApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -681,9 +681,9 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.AddDomainApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -701,9 +701,9 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.RemoveDomainApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -721,9 +721,9 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.AddGithubUsernameApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -741,9 +741,9 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.RemoveGithubUsernameApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -761,9 +761,9 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.AddGithubOrgApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, @@ -781,9 +781,10 @@ func (s service) createEventLogEntries(companyModel *models.Company, claGroupMod } for _, value := range approvalList.RemoveGithubOrgApprovalList { // Send an event - s.eventsService.LogEvent(&events.LogEventArgs{ + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectID, + CLAGroupID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, CompanyID: companyModel.CompanyID, CompanyModel: companyModel, diff --git a/cla-backend-go/swagger/common/event.yaml b/cla-backend-go/swagger/common/event.yaml index cb8a03854..a39ab12a5 100644 --- a/cla-backend-go/swagger/common/event.yaml +++ b/cla-backend-go/swagger/common/event.yaml @@ -4,31 +4,32 @@ type: object properties: EventID: - type: string description: unique id of the event + $ref: './common/properties/internal-id.yaml' EventType: type: string description: type of the event UserID: - type: string description: id of the user who created this event + $ref: './common/properties/internal-id.yaml' UserName: - type: string - description: name of the user + $ref: './common/properties/user-name.yaml' LfUsername: type: string description: name of the user + EventCLAGroupID: + description: the CLA Group ID + $ref: './common/properties/internal-id.yaml' + EventCLAGroupName: + description: the CLA Group name + $ref: './common/properties/cla-group-name.yaml' EventProjectID: type: string description: id of the SFID project EventProjectName: - type: string - description: name of the project + $ref: './common/properties/project-name.yaml' EventCompanyName: - type: string - description: name of the company - pattern: '^([\w\p{L}][\w\s\p{L}()\[\]+\-/%!@#$]*){2,255}$' - example: "Linux Foundation" + $ref: './common/properties/company-name.yaml' EventCompanyID: type: string description: id of the organization/company diff --git a/cla-backend-go/tests/utils_test.go b/cla-backend-go/tests/utils_test.go index 7b62da95f..53abcb9ac 100644 --- a/cla-backend-go/tests/utils_test.go +++ b/cla-backend-go/tests/utils_test.go @@ -390,3 +390,27 @@ func TestIsUUIDv4LikeSFID(t *testing.T) { sfid := "0014100000TdznWAAR" assert.False(t, utils.IsUUIDv4(sfid), fmt.Sprintf("%s is not v4 UUID", sfid)) } + +func TestIsSalesForceID(t *testing.T) { + trueTestData := []string{ + "00117000015vpjX", + "00117000015vpjXAAQ", + } + falseTestData := []string{ + "", + "00117", + "-00117", + "00117000015vpj-", + "0011700001-vpjXAAQ", + "0011700001?vpjXAAQ", + "0011700001&vpjXAAQ", + "0011700001_vpjXAAQ", + } + + for i := range trueTestData { + assert.True(t, utils.IsSalesForceID(trueTestData[i])) + } + for i := range falseTestData { + assert.False(t, utils.IsSalesForceID(falseTestData[i])) + } +} diff --git a/cla-backend-go/utils/utils.go b/cla-backend-go/utils/utils.go index eb7a98be0..41dec29cf 100644 --- a/cla-backend-go/utils/utils.go +++ b/cla-backend-go/utils/utils.go @@ -297,3 +297,9 @@ func IsUUIDv4(id string) bool { return value.Version() == uuid.V4 } + +// IsSalesForceID returns true if the specified ID is a SalesForce formatted ID, otherwise returns false +func IsSalesForceID(id string) bool { + regExp := regexp.MustCompile("^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$") + return regExp.MatchString(strings.TrimSpace(id)) +} diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 7de4461b8..eb89bfb2f 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -40,7 +40,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_groups.handlers.ClaGroupCreateClaGroupHandler", + "functionName": "v2.cla_groups.handlers.ClaGroupCreateClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupName": utils.StringValue(params.ClaGroupInput.ClaGroupName), "foundationSFID": utils.StringValue(params.ClaGroupInput.FoundationSfid), @@ -60,7 +60,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P return cla_group.NewCreateClaGroupForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - claGroup, err := service.CreateCLAGroup(ctx, params.ClaGroupInput, utils.StringValue(params.XUSERNAME)) + claGroup, err := service.CreateCLAGroup(ctx, authUser, params.ClaGroupInput, utils.StringValue(params.XUSERNAME)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to create the CLA Group") return cla_group.NewCreateClaGroupBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ @@ -86,7 +86,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_groups.handlers.ClaGroupUpdateClaGroupHandler", + "functionName": "v2.cla_groups.handlers.ClaGroupUpdateClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -135,7 +135,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("unable to update the CLA Group Name or Description - values are the same for CLA Group ID: %s", params.ClaGroupID))) } - claGroup, err := service.UpdateCLAGroup(ctx, claGroupModel, params.Body, utils.StringValue(params.XUSERNAME)) + claGroup, err := service.UpdateCLAGroup(ctx, authUser, claGroupModel, params.Body, utils.StringValue(params.XUSERNAME)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to update the CLA Group Name and/or Description - update failed") return cla_group.NewUpdateClaGroupBadRequest().WithXRequestID(reqID).WithPayload( @@ -161,7 +161,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_groups.handlers.ClaGroupDeleteClaGroupHandler", + "functionName": "v2.cla_groups.handlers.ClaGroupDeleteClaGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -204,12 +204,8 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P err = service.DeleteCLAGroup(ctx, claGroupModel, authUser) if err != nil { log.WithFields(f).Warn(err) - return cla_group.NewDeleteClaGroupInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "500", - Message: fmt.Sprintf("EasyCLA - 500 Internal server error - error deleting CLA Group %s, error: %+v", - params.ClaGroupID, err), - XRequestID: reqID, - }) + return cla_group.NewDeleteClaGroupInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseInternalServerErrorWithError(reqID, fmt.Sprintf("error deleting CLA Group by ID: %s", params.ClaGroupID), err)) } eventsService.LogEvent(&events.LogEventArgs{ @@ -227,7 +223,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_groups.handlers.ClaGroupEnrollProjectsHandler", + "functionName": "v2.cla_groups.handlers.ClaGroupEnrollProjectsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ClaGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -235,28 +231,18 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P "projectSFIDList": strings.Join(params.ProjectSFIDList, ","), } - cg, err := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) - if err != nil { - if err, ok := err.(*utils.CLAGroupNotFound); ok { - return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("EasyCLA - 404 Not Found - %s", err.Error()), - XRequestID: reqID, - }) + cg, getCLAGroupErr := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) + if getCLAGroupErr != nil { + if _, ok := getCLAGroupErr.(*utils.CLAGroupNotFound); ok { + return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("problem loading CLA Group by ID: %s", params.ClaGroupID), getCLAGroupErr)) } - if err == v1Project.ErrProjectDoesNotExist { - return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("EasyCLA - 404 Not Found - cla_group %s not found", - params.ClaGroupID), - XRequestID: reqID, - }) + if getCLAGroupErr == v1Project.ErrProjectDoesNotExist { + return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("problem loading CLA Group by ID: %s", params.ClaGroupID), getCLAGroupErr)) } - return cla_group.NewEnrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: fmt.Sprintf("EasyCLA - 500 Internal server error - error = %s", err.Error()), - XRequestID: reqID, - }) + return cla_group.NewEnrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseInternalServerErrorWithError(reqID, fmt.Sprintf("problem loading CLA Group by ID: %s", params.ClaGroupID), getCLAGroupErr)) } // Check permissions @@ -275,12 +261,11 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P msg := fmt.Sprintf("Failed to get salesforce project: %s", projectSFID) log.WithFields(f).Warn(msg) if _, ok := projectErr.(*v2ProjectServiceClient.GetProjectNotFound); ok { - return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("project not found with given ID. [%s]", projectSFID), - }) + return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("project not found with ID: [%s]", projectSFID), projectErr)) } - return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, projectErr)) + return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, msg, projectErr)) } var parentProject *v2ProjectServiceModels.ProjectOutputDetailed // Handle the ONAP edge case @@ -289,10 +274,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P if parentProject == nil || projectErr != nil { msg := fmt.Sprintf("Failed to get parent: %s", project.Parent) log.WithFields(f).Warnf(msg) - return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload((&models.ErrorResponse{ - Code: "400", - Message: msg, - })) + return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } } if (project.Parent != "" && !utils.IsProjectCategory(project, parentProject)) || (utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { @@ -303,32 +285,22 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } - err = service.EnrollProjectsInClaGroup(ctx, params.ClaGroupID, cg.FoundationSFID, params.ProjectSFIDList) - if err != nil { - if strings.Contains(err.Error(), "bad request") { - return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: fmt.Sprintf("EasyCLA - 400 Bad Request - %s", err.Error()), - XRequestID: reqID, - }) + // Enroll the project(s) into the CLA Group + enrollCLAGroupErr := service.EnrollProjectsInClaGroup(ctx, &EnrollProjectsModel{ + AuthUser: authUser, + CLAGroupID: params.ClaGroupID, + FoundationSFID: cg.FoundationSFID, + ProjectSFIDList: params.ProjectSFIDList, + }) + if enrollCLAGroupErr != nil { + if strings.Contains(enrollCLAGroupErr.Error(), "bad request") { + return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, "unable to enroll projects in CLA Group", enrollCLAGroupErr)) } - return cla_group.NewEnrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "500", - Message: fmt.Sprintf("EasyCLA - 500 Internal server error - error = %s", err.Error()), - XRequestID: reqID, - }) + return cla_group.NewEnrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseInternalServerErrorWithError(reqID, "unable to enroll projects in CLA Group", enrollCLAGroupErr)) } - eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupUpdated, - ClaGroupModel: cg, - LfUsername: authUser.UserName, - EventData: &events.CLAGroupUpdatedEventData{ - ClaGroupName: cg.ProjectName, - ClaGroupDescription: cg.ProjectDescription, - }, - }) - return cla_group.NewEnrollProjectsOK().WithXRequestID(reqID) }) @@ -337,7 +309,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_groups.handlers.ClaGroupUnenrollProjectsHandler", + "functionName": "v2.cla_groups.handlers.ClaGroupUnenrollProjectsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ClaGroupID": params.ClaGroupID, "authUsername": params.XUSERNAME, @@ -348,24 +320,15 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P cg, err := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { if err, ok := err.(*utils.CLAGroupNotFound); ok { - return cla_group.NewUnenrollProjectsNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("EasyCLA - 404 Not Found - %s", err.Error()), - XRequestID: reqID, - }) + return cla_group.NewUnenrollProjectsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("unable to locate CLA Group by ID: %s", params.ClaGroupID), err)) } if err == v1Project.ErrProjectDoesNotExist { - return cla_group.NewUnenrollProjectsNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("EasyCLA - 404 Not Found - cla_group %s not found", params.ClaGroupID), - XRequestID: reqID, - }) + return cla_group.NewUnenrollProjectsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("unable to locate CLA Group by ID: %s", params.ClaGroupID), err)) } - return cla_group.NewUnenrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: fmt.Sprintf("EasyCLA - 500 Internal server error - error = %s", err.Error()), - XRequestID: reqID, - }) + return cla_group.NewUnenrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("problem locating CLA Group by ID: %s", params.ClaGroupID), err)) } // Check permissions @@ -375,20 +338,19 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P return cla_group.NewUnenrollProjectsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - err = service.UnenrollProjectsInClaGroup(ctx, params.ClaGroupID, cg.FoundationSFID, params.ProjectSFIDList) + err = service.UnenrollProjectsInClaGroup(ctx, &UnenrollProjectsModel{ + AuthUser: authUser, + CLAGroupID: params.ClaGroupID, + FoundationSFID: cg.FoundationSFID, + ProjectSFIDList: params.ProjectSFIDList, + }) if err != nil { if strings.Contains(err.Error(), "bad request") { - return cla_group.NewUnenrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: fmt.Sprintf("EasyCLA - 400 Bad Request - %s", err.Error()), - XRequestID: reqID, - }) + return cla_group.NewUnenrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to unenroll projects for CLA Group ID: %s", params.ClaGroupID), err)) } - return cla_group.NewUnenrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "500", - Message: fmt.Sprintf("EasyCLA - 500 Internal server error - error = %s", err.Error()), - XRequestID: reqID, - }) + return cla_group.NewUnenrollProjectsInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseInternalServerErrorWithError(reqID, fmt.Sprintf("unable to unenroll projects for CLA Group ID: %s", params.ClaGroupID), err)) } eventsService.LogEvent(&events.LogEventArgs{ @@ -409,7 +371,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_groups.handlers.ClaGroupListClaGroupsUnderFoundationHandler", + "functionName": "v2.cla_groups.handlers.ClaGroupListClaGroupsUnderFoundationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "authUsername": params.XUSERNAME, @@ -423,12 +385,11 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P msg := fmt.Sprintf("Failed to get salesforce project: %s", params.ProjectSFID) log.WithFields(f).Warn(msg) if _, ok := projectErr.(*v2ProjectServiceClient.GetProjectNotFound); ok { - return cla_group.NewListClaGroupsUnderFoundationNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("project not found with given ID. [%s]", params.ProjectSFID), - }) + return cla_group.NewListClaGroupsUnderFoundationNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("project not found with ID: %s", params.ProjectSFID), projectErr)) } - return cla_group.NewListClaGroupsUnderFoundationBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, projectErr)) + return cla_group.NewListClaGroupsUnderFoundationBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, msg, projectErr)) } log.WithFields(f).Debug("found project - evaluating parent...") @@ -518,7 +479,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // isUserHaveAccessToCLAProject is a helper function to determine if the user has access to the specified project func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, parentProjectSFID string, projectSFIDs []string, projectClaGroupsRepo projects_cla_groups.Repository) bool { // nolint f := logrus.Fields{ - "functionName": "cla_groups.handlers.isUserHaveAccessToCLAProject", + "functionName": "v2.cla_groups.handlers.isUserHaveAccessToCLAProject", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "projectSFIDs": strings.Join(projectSFIDs, ","), diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 7122b6f34..7a0dd68f9 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -10,6 +10,9 @@ import ( "strings" "sync" + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -220,14 +223,6 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI } - // Comment out the below as we want to support stand-alone projects - /* - if len(foundationProjectDetails.Projects) == 0 { - log.WithFields(f).Warn("validation failure - project does not have any subprojects") - return fmt.Errorf("bad request: invalid input to enroll projects. project does not have any subprojects") - } - */ - // Check to see if all the provided enrolled projects are part of this foundation foundationProjectIDList := utils.NewStringSet() for _, pr := range foundationProjectDetails.Projects { @@ -389,35 +384,46 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return nil } -func (s *service) AssociateCLAGroupWithProjects(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { +func (s *service) AssociateCLAGroupWithProjects(ctx context.Context, request *AssociateCLAGroupWithProjectsModel) error { f := logrus.Fields{ "functionName": "AssociateCLAGroupWithProjects", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "foundationSFID": foundationSFID, - "projectSFIDList": strings.Join(projectSFIDList, ","), + "authUserName": request.AuthUser.UserName, + "authUserEmail": request.AuthUser.Email, + "claGroupID": request.CLAGroupID, + "foundationSFID": request.FoundationSFID, + "projectSFIDList": strings.Join(request.ProjectSFIDList, ","), } // Associate the CLA Group with the project list in a go routine var errorList []error var wg sync.WaitGroup - wg.Add(len(projectSFIDList)) + wg.Add(len(request.ProjectSFIDList)) - for _, projectSFID := range projectSFIDList { + for _, projectSFID := range request.ProjectSFIDList { // Invoke the go routine - any errors will be handled below go func(sfid string) { defer wg.Done() log.WithFields(f).Debugf("associating cla_group with project: %s", sfid) - err := s.projectsClaGroupsRepo.AssociateClaGroupWithProject(claGroupID, sfid, foundationSFID) + err := s.projectsClaGroupsRepo.AssociateClaGroupWithProject(request.CLAGroupID, sfid, request.FoundationSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("associating cla_group with project: %s failed", sfid) log.WithFields(f).Debug("deleting stale entries from cla_group project association") - deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(claGroupID, projectSFIDList, false) + deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(request.CLAGroupID, request.ProjectSFIDList, false) if deleteErr != nil { log.WithFields(f).WithError(deleteErr).Warn("deleting stale entries from cla_group project association failed") } // Add the error to the error list errorList = append(errorList, err) } + // add event log entry + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.CLAGroupEnrolledProject, + ProjectSFID: sfid, + CLAGroupID: request.CLAGroupID, + LfUsername: request.AuthUser.UserName, + EventData: &events.CLAGroupEnrolledProjectData{}, + }) }(projectSFID) } @@ -427,36 +433,52 @@ func (s *service) AssociateCLAGroupWithProjects(ctx context.Context, claGroupID // If any errors while associating - return the first one if len(errorList) > 0 { - log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when associating %d projects with the CLA Group", len(errorList), len(projectSFIDList)) + log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when associating %d projects with the CLA Group", len(errorList), len(request.ProjectSFIDList)) return errorList[0] } return nil } -func (s *service) UnassociateCLAGroupWithProjects(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { +func (s *service) UnassociateCLAGroupWithProjects(ctx context.Context, request *UnassociateCLAGroupWithProjectsModel) error { f := logrus.Fields{ "functionName": "UnassociateCLAGroupWithProjects", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "claGroupID": claGroupID, - "foundationSFID": foundationSFID, - "projectSFIDList": strings.Join(projectSFIDList, ","), + "authUserName": request.AuthUser.UserName, + "authUserEmail": request.AuthUser.Email, + "claGroupID": request.CLAGroupID, + "foundationSFID": request.FoundationSFID, + "projectSFIDList": strings.Join(request.ProjectSFIDList, ","), } - deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(claGroupID, projectSFIDList, false) + deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(request.CLAGroupID, request.ProjectSFIDList, false) if deleteErr != nil { log.WithFields(f).Warnf("problem disassociating projects with CLA Group, error: %+v", deleteErr) return deleteErr } + // If this is slow, we may want to run these in a go routine + for _, projectSFID := range request.ProjectSFIDList { + // add event log entry + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.CLAGroupUnenrolledProject, + ProjectSFID: projectSFID, + CLAGroupID: request.CLAGroupID, + LfUsername: request.AuthUser.UserName, + EventData: &events.CLAGroupUnenrolledProjectData{}, + }) + } + return nil } // EnableCLAService enable CLA service attribute in the project service for the specified project list -func (s *service) EnableCLAService(ctx context.Context, projectSFIDList []string) error { +func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error { f := logrus.Fields{ "functionName": "EnableCLAService", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, "projectSFIDList": strings.Join(projectSFIDList, ","), } @@ -478,6 +500,13 @@ func (s *service) EnableCLAService(ctx context.Context, projectSFIDList []string errorList = append(errorList, enableProjectErr) } else { log.WithFields(f).Debugf("enabled CLA service for project: %s", sfid) + // add event log entry + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ProjectServiceCLAEnabled, + ProjectID: sfid, + LfUsername: authUser.UserName, + EventData: &events.ProjectServiceCLAEnabledData{}, + }) } }(psc, projectSFID) } @@ -494,10 +523,12 @@ func (s *service) EnableCLAService(ctx context.Context, projectSFIDList []string } // DisableCLAService disable CLA service attribute in the project service for the specified project list -func (s *service) DisableCLAService(ctx context.Context, projectSFIDList []string) error { +func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error { f := logrus.Fields{ "functionName": "DisableCLAService", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, "projectSFIDList": strings.Join(projectSFIDList, ","), } @@ -519,6 +550,13 @@ func (s *service) DisableCLAService(ctx context.Context, projectSFIDList []strin errorList = append(errorList, disableProjectErr) } else { log.WithFields(f).Debugf("disabled CLA service for project: %s", sfid) + // add event log entry + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ProjectServiceCLADisabled, + ProjectID: sfid, + LfUsername: authUser.UserName, + EventData: &events.ProjectServiceCLADisabledData{}, + }) } }(psc, projectSFID) } diff --git a/cla-backend-go/v2/cla_groups/models.go b/cla-backend-go/v2/cla_groups/models.go new file mode 100644 index 000000000..1473ea896 --- /dev/null +++ b/cla-backend-go/v2/cla_groups/models.go @@ -0,0 +1,40 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package cla_groups + +import ( + "github.com/LF-Engineering/lfx-kit/auth" +) + +// EnrollProjectsModel model to encapsulate the enroll projects request +type EnrollProjectsModel struct { + AuthUser *auth.User + CLAGroupID string + FoundationSFID string + ProjectSFIDList []string +} + +// UnenrollProjectsModel model to encapsulate the unenroll projects request +type UnenrollProjectsModel struct { + AuthUser *auth.User + CLAGroupID string + FoundationSFID string + ProjectSFIDList []string +} + +// AssociateCLAGroupWithProjectsModel to encapsulate the associate request +type AssociateCLAGroupWithProjectsModel struct { + AuthUser *auth.User + CLAGroupID string + FoundationSFID string + ProjectSFIDList []string +} + +// UnassociateCLAGroupWithProjectsModel to encapsulate the unassociate request +type UnassociateCLAGroupWithProjectsModel struct { + AuthUser *auth.User + CLAGroupID string + FoundationSFID string + ProjectSFIDList []string +} diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index c21486fd6..cd056d9f6 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -54,17 +54,17 @@ type service struct { // Service interface type Service interface { - CreateCLAGroup(ctx context.Context, input *models.CreateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) - UpdateCLAGroup(ctx context.Context, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) + CreateCLAGroup(ctx context.Context, authUser *auth.User, input *models.CreateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) + UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) ListClaGroupsForFoundationOrProject(ctx context.Context, foundationSFID string) (*models.ClaGroupListSummary, error) ListAllFoundationClaGroups(ctx context.Context, foundationID *string) (*models.FoundationMappingList, error) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.ClaGroup, authUser *auth.User) error - EnrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error - UnenrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error - AssociateCLAGroupWithProjects(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error - UnassociateCLAGroupWithProjects(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error - EnableCLAService(ctx context.Context, projectSFIDList []string) error - DisableCLAService(ctx context.Context, projectSFIDList []string) error + EnrollProjectsInClaGroup(ctx context.Context, request *EnrollProjectsModel) error + UnenrollProjectsInClaGroup(ctx context.Context, request *UnenrollProjectsModel) error + AssociateCLAGroupWithProjects(ctx context.Context, request *AssociateCLAGroupWithProjectsModel) error + UnassociateCLAGroupWithProjects(ctx context.Context, request *UnassociateCLAGroupWithProjectsModel) error + EnableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error + DisableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error ValidateCLAGroup(ctx context.Context, input *models.ClaGroupValidationRequest) (bool, []string) } @@ -83,7 +83,7 @@ func NewService(projectService v1Project.Service, templateService v1Template.Ser } } -func (s *service) CreateCLAGroup(ctx context.Context, input *models.CreateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { +func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input *models.CreateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { // Validate the input log.WithField("input", input).Debugf("validating create cla group input") if input.IclaEnabled == nil || @@ -97,6 +97,8 @@ func (s *service) CreateCLAGroup(ctx context.Context, input *models.CreateClaGro f := logrus.Fields{ "functionName": "CreateCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, "ClaGroupName": aws.StringValue(input.ClaGroupName), "ClaGroupDescription": input.ClaGroupDescription, "FoundationSfid": aws.StringValue(input.FoundationSfid), @@ -175,15 +177,20 @@ func (s *service) CreateCLAGroup(ctx context.Context, input *models.CreateClaGro log.WithFields(f).Debug("cla_group_template attached", pdfUrls) // Associate the specified projects with our new CLA Group - err = s.EnrollProjectsInClaGroup(ctx, claGroup.ProjectID, *input.FoundationSfid, input.ProjectSfidList) - if err != nil { + enrollErr := s.EnrollProjectsInClaGroup(ctx, &EnrollProjectsModel{ + AuthUser: authUser, + CLAGroupID: claGroup.ProjectID, + FoundationSFID: *input.FoundationSfid, + ProjectSFIDList: input.ProjectSfidList, + }) + if enrollErr != nil { // Oops, roll back logic - log.WithFields(f).Debug("enroll projects in CLA Group failure - deleting created cla group") + log.WithFields(f).WithError(enrollErr).Debug("enroll projects in CLA Group failure - deleting created cla group") deleteErr := s.v1ProjectService.DeleteCLAGroup(ctx, claGroup.ProjectID) if deleteErr != nil { log.WithFields(f).Error("deleting created cla group failed - manual cleanup required.", deleteErr) } - return nil, err + return nil, enrollErr } // Build the response model @@ -222,11 +229,13 @@ func (s *service) CreateCLAGroup(ctx context.Context, input *models.CreateClaGro }, nil } -func (s *service) UpdateCLAGroup(ctx context.Context, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { +func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { // Validate the input f := logrus.Fields{ "functionName": "UpdateCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, "claGroupID": claGroupModel.ProjectID, "ClaGroupName": input.ClaGroupName, "ClaGroupDescription": input.ClaGroupDescription, @@ -689,6 +698,7 @@ func (s *service) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.Cl s.eventsService.LogEvent(&events.LogEventArgs{ EventType: events.GerritRepositoryDeleted, ClaGroupModel: claGroup, + CLAGroupID: claGroup.ProjectID, LfUsername: authUser.UserName, EventData: &events.GerritProjectDeletedEventData{ DeletedCount: numDeleted, @@ -855,8 +865,13 @@ func (s *service) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.Cl } } - // Associate the specified projects with our new CLA Group - err := s.UnenrollProjectsInClaGroup(ctx, claGroupModel.ProjectID, foundationSFID, projectIDList.List()) + // Unenroll the specified projects with the CLA Group + err := s.UnenrollProjectsInClaGroup(ctx, &UnenrollProjectsModel{ + AuthUser: authUser, + CLAGroupID: claGroupModel.ProjectID, + FoundationSFID: foundationSFID, + ProjectSFIDList: projectIDList.List(), + }) if err != nil { log.WithFields(f).WithError(err).Warn("unenrolling projects in CLA Group failed - manual cleanup required.") } @@ -873,17 +888,19 @@ func (s *service) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.Cl } // EnrollProjectsInClaGroup enrolls the specified project list in the CLA Group -func (s *service) EnrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { +func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollProjectsModel) error { f := logrus.Fields{ "functionName": "EnrollProjectsInClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "claGroupID": claGroupID, - "foundationSFID": foundationSFID, - "projectSFIDList": strings.Join(projectSFIDList, ","), + "authUserName": request.AuthUser.UserName, + "authUserEmail": request.AuthUser.Email, + "claGroupID": request.CLAGroupID, + "foundationSFID": request.FoundationSFID, + "projectSFIDList": strings.Join(request.ProjectSFIDList, ","), } log.WithFields(f).Debug("validating enroll project input") - err := s.validateEnrollProjectsInput(ctx, foundationSFID, projectSFIDList) + err := s.validateEnrollProjectsInput(ctx, request.FoundationSFID, request.ProjectSFIDList) if err != nil { log.WithFields(f).Warnf("validating enroll project input failed. error = %s", err) return err @@ -895,49 +912,57 @@ func (s *service) EnrollProjectsInClaGroup(ctx context.Context, claGroupID strin wg.Add(2) // Separate go routine for enrolling projects - go func(c context.Context, claGrID string, fSFID string, projSFIDList []string) { + go func(c context.Context, authUser *auth.User, claGroupID string, foundationSFID string, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("enrolling projects in CLA Group") - enrollErr := s.AssociateCLAGroupWithProjects(c, claGrID, fSFID, projSFIDList) + enrollErr := s.AssociateCLAGroupWithProjects(c, &AssociateCLAGroupWithProjectsModel{ + AuthUser: authUser, + CLAGroupID: claGroupID, + FoundationSFID: foundationSFID, + ProjectSFIDList: projSFIDList, + }) if enrollErr != nil { log.WithFields(f).WithError(enrollErr).Warn("enrolling projects in CLA Group failed") errorList = append(errorList, enrollErr) } - - }(ctx, claGroupID, foundationSFID, projectSFIDList) + }(ctx, request.AuthUser, request.CLAGroupID, request.FoundationSFID, request.ProjectSFIDList) // Separate go routine for enabling the CLA Service in the project service go func(c context.Context, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("enabling CLA service in platform project service") - errEnableCLA := s.EnableCLAService(c, projSFIDList) + // Note: log entry will be created by enable CLA Service call + errEnableCLA := s.EnableCLAService(c, request.AuthUser, projSFIDList) if errEnableCLA != nil { log.WithFields(f).WithError(errEnableCLA).Warn("enabling CLA service in platform project service failed") errorList = append(errorList, errEnableCLA) } - }(ctx, projectSFIDList) + }(ctx, request.ProjectSFIDList) // Wait until all go routines are done wg.Wait() if len(errorList) > 0 { - log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when enrolling and enabling CLA service for %d projects", len(errorList), len(projectSFIDList)) + log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when enrolling and enabling CLA service for %d projects", len(errorList), len(request.ProjectSFIDList)) return errorList[0] } return nil } -func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { +//func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { +func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *UnenrollProjectsModel) error { f := logrus.Fields{ "functionName": "UnenrollProjectsInClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "claGroupID": claGroupID, - "foundationSFID": foundationSFID, - "projectSFIDList": strings.Join(projectSFIDList, ","), + "authUserName": request.AuthUser.UserName, + "authUserEmail": request.AuthUser.Email, + "claGroupID": request.CLAGroupID, + "foundationSFID": request.FoundationSFID, + "projectSFIDList": strings.Join(request.ProjectSFIDList, ","), } log.WithFields(f).Debug("validating unenroll project input") - err := s.validateUnenrollProjectsInput(ctx, foundationSFID, projectSFIDList) + err := s.validateUnenrollProjectsInput(ctx, request.FoundationSFID, request.ProjectSFIDList) if err != nil { log.WithFields(f).Warnf("validating unenroll project input failed. error = %s", err) return err @@ -949,32 +974,37 @@ func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, claGroupID str wg.Add(2) // Separate go routine for unenrolling projects - go func(c context.Context, claGrID string, fSFID string, projSFIDList []string) { + go func(c context.Context, authUser *auth.User, claGroupID string, foundationSFID string, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("unenrolling projects in CLA Group") - unenrollErr := s.UnassociateCLAGroupWithProjects(c, claGrID, fSFID, projSFIDList) + unenrollErr := s.UnassociateCLAGroupWithProjects(c, &UnassociateCLAGroupWithProjectsModel{ + AuthUser: authUser, + CLAGroupID: claGroupID, + FoundationSFID: foundationSFID, + ProjectSFIDList: projSFIDList, + }) if unenrollErr != nil { log.WithFields(f).WithError(unenrollErr).Warn("unenrolling projects in CLA Group failed") errorList = append(errorList, unenrollErr) } - - }(ctx, claGroupID, foundationSFID, projectSFIDList) + }(ctx, request.AuthUser, request.CLAGroupID, request.FoundationSFID, request.ProjectSFIDList) // Separate go routine for disabling the CLA Service in the project service go func(c context.Context, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("disabling CLA service in platform project service") - errDisableCLA := s.DisableCLAService(c, projSFIDList) + // Note: log entry will be created by disable CLA Service call + errDisableCLA := s.DisableCLAService(c, request.AuthUser, projSFIDList) if errDisableCLA != nil { log.WithFields(f).WithError(errDisableCLA).Warn("disabling CLA service in platform project service failed") errorList = append(errorList, errDisableCLA) } - }(ctx, projectSFIDList) + }(ctx, request.ProjectSFIDList) // Wait until all go routines are done wg.Wait() if len(errorList) > 0 { - log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when unenrolling and disabling CLA service for %d projects", len(errorList), len(projectSFIDList)) + log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when unenrolling and disabling CLA service for %d projects", len(errorList), len(request.ProjectSFIDList)) return errorList[0] } diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index 0bde09763..e571f17a1 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" "sync" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" @@ -34,145 +33,145 @@ type ProjectClaGroup struct { RepositoriesCount int64 `json:"repositories_count"` } -// ProjectServiceEnableCLAServiceHandler handles enabling the CLA Service attribute from the project service -func (s *service) ProjectServiceEnableCLAServiceHandler(event events.DynamoDBEventRecord) error { - ctx := utils.NewContext() - f := logrus.Fields{ - "functionName": "dynamo_events.projects_cla_groups.ProjectServiceEnableCLAServiceHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, - } - - log.WithFields(f).Debug("processing request") - var newProject ProjectClaGroup - err := unmarshalStreamImage(event.Change.NewImage, &newProject) - if err != nil { - log.WithFields(f).WithError(err).Warn("project decoding add event") - return err - } - - f["projectSFID"] = newProject.ProjectSFID - f["claGroupID"] = newProject.ClaGroupID - f["foundationSFID"] = newProject.FoundationSFID - - psc := v2ProjectService.GetClient() - log.WithFields(f).Debug("looking up project by SFID...") - projectDetails, prjerr := psc.GetProject(newProject.ProjectSFID) - if prjerr != nil { - log.WithError(err).Warnf("unable to get project details from SFID: %s", newProject.ProjectSFID) - } - projectName := newProject.ProjectSFID - if projectDetails != nil { - projectName = projectDetails.Name - f["projectName"] = projectName - } - - start, _ := utils.CurrentTime() - log.WithFields(f).Debugf("enabling CLA service for project %s with ID: %s", projectName, newProject.ProjectSFID) - err = psc.EnableCLA(newProject.ProjectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warn("enabling CLA service failed") - return err - } - finish, _ := utils.CurrentTime() - log.WithFields(f).Debugf("enabled CLA service for project %s with ID: %s", projectName, newProject.ProjectSFID) - log.WithFields(f).Debugf("enabling CLA service completed - took: %s", finish.Sub(start).String()) - - // Log the event - eventErr := s.eventsRepo.CreateEvent(&models.Event{ - ContainsPII: false, - EventData: fmt.Sprintf("enabled CLA service for project: %s with ID: %s", projectName, newProject.ProjectSFID), - EventFoundationSFID: newProject.FoundationSFID, - EventProjectExternalID: newProject.ProjectSFID, - EventProjectID: newProject.ClaGroupID, - EventProjectName: projectName, - EventProjectSFID: newProject.ProjectSFID, - EventProjectSFName: projectName, - EventSummary: fmt.Sprintf("enabled CLA service for project: %s", projectName), - EventType: claEvents.ProjectServiceCLAEnabled, - LfUsername: "easycla system", - UserID: "easycla system", - UserName: "easycla system", - }) - if eventErr != nil { - log.WithFields(f).WithError(eventErr).Warn("problem logging event for enabling CLA service") - // Ok - don't fail for now - } - - return nil -} - -// ProjectServiceDisableCLAServiceHandler handles disabling/removing the CLA Service attribute from the project service -func (s *service) ProjectServiceDisableCLAServiceHandler(event events.DynamoDBEventRecord) error { - ctx := utils.NewContext() - f := logrus.Fields{ - "functionName": "dynamo_events.projects_cla_groups.ProjectServiceDisableCLAServiceHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, - } - - log.WithFields(f).Debug("processing request") - var oldProject ProjectClaGroup - err := unmarshalStreamImage(event.Change.OldImage, &oldProject) - if err != nil { - log.WithFields(f).WithError(err).Warn("problem unmarshalling stream image") - return err - } - - // Add more fields for the logger - f["ProjectSFID"] = oldProject.ProjectSFID - f["ClaGroupID"] = oldProject.ClaGroupID - f["FoundationSFID"] = oldProject.FoundationSFID - - psc := v2ProjectService.GetClient() - log.WithFields(f).Debug("looking up project by SFID...") - projectDetails, prjerr := psc.GetProject(oldProject.ProjectSFID) - if prjerr != nil { - log.WithError(err).Warnf("unable to get project details from SFID: %s", oldProject.ProjectSFID) - } - projectName := oldProject.ProjectSFID - if projectDetails != nil { - projectName = projectDetails.Name - f["projectName"] = projectName - } - - // Gathering metrics - grab the time before the API call - before, _ := utils.CurrentTime() - log.WithFields(f).Debugf("disabling CLA service for project %s with ID: %s", projectName, oldProject.ProjectSFID) - err = psc.DisableCLA(oldProject.ProjectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warn("disabling CLA service failed") - return err - } - log.WithFields(f).Debugf("disabled CLA service for project %s with ID: %s", projectName, oldProject.ProjectSFID) - log.WithFields(f).Debugf("disabling CLA service completed - took %s", time.Since(before).String()) - - // Log the event - eventErr := s.eventsRepo.CreateEvent(&models.Event{ - ContainsPII: false, - EventData: fmt.Sprintf("disabled CLA service for project: %s with ID: %s", projectName, oldProject.ProjectSFID), - EventFoundationSFID: oldProject.FoundationSFID, - EventProjectExternalID: oldProject.ProjectSFID, - EventProjectID: oldProject.ClaGroupID, - EventProjectName: projectName, - EventProjectSFID: oldProject.ProjectSFID, - EventSummary: fmt.Sprintf("disabled CLA service for project: %s", projectName), - EventType: claEvents.ProjectServiceCLADisabled, - LfUsername: "easycla system", - UserID: "easycla system", - UserName: "easycla system", - }) - if eventErr != nil { - log.WithFields(f).WithError(eventErr).Warn("problem logging event for disabling CLA service") - // Ok - don't fail for now - } - - return nil -} +//// ProjectServiceEnableCLAServiceHandler handles enabling the CLA Service attribute from the project service +//func (s *service) ProjectServiceEnableCLAServiceHandler(event events.DynamoDBEventRecord) error { +// ctx := utils.NewContext() +// f := logrus.Fields{ +// "functionName": "dynamo_events.projects_cla_groups.ProjectServiceEnableCLAServiceHandler", +// utils.XREQUESTID: ctx.Value(utils.XREQUESTID), +// "eventID": event.EventID, +// "eventName": event.EventName, +// "eventSource": event.EventSource, +// } +// +// log.WithFields(f).Debug("processing request") +// var newProject ProjectClaGroup +// err := unmarshalStreamImage(event.Change.NewImage, &newProject) +// if err != nil { +// log.WithFields(f).WithError(err).Warn("project decoding add event") +// return err +// } +// +// f["projectSFID"] = newProject.ProjectSFID +// f["claGroupID"] = newProject.ClaGroupID +// f["foundationSFID"] = newProject.FoundationSFID +// +// psc := v2ProjectService.GetClient() +// log.WithFields(f).Debug("looking up project by SFID...") +// projectDetails, prjerr := psc.GetProject(newProject.ProjectSFID) +// if prjerr != nil { +// log.WithError(err).Warnf("unable to get project details from SFID: %s", newProject.ProjectSFID) +// } +// projectName := newProject.ProjectSFID +// if projectDetails != nil { +// projectName = projectDetails.Name +// f["projectName"] = projectName +// } +// +// start, _ := utils.CurrentTime() +// log.WithFields(f).Debugf("enabling CLA service for project %s with ID: %s", projectName, newProject.ProjectSFID) +// err = psc.EnableCLA(newProject.ProjectSFID) +// if err != nil { +// log.WithFields(f).WithError(err).Warn("enabling CLA service failed") +// return err +// } +// finish, _ := utils.CurrentTime() +// log.WithFields(f).Debugf("enabled CLA service for project %s with ID: %s", projectName, newProject.ProjectSFID) +// log.WithFields(f).Debugf("enabling CLA service completed - took: %s", finish.Sub(start).String()) +// +// // Log the event +// eventErr := s.eventsRepo.CreateEvent(&models.Event{ +// ContainsPII: false, +// EventData: fmt.Sprintf("enabled CLA service for project: %s with ID: %s", projectName, newProject.ProjectSFID), +// EventFoundationSFID: newProject.FoundationSFID, +// EventProjectExternalID: newProject.ProjectSFID, +// EventProjectID: newProject.ClaGroupID, +// EventProjectName: projectName, +// EventProjectSFID: newProject.ProjectSFID, +// EventProjectSFName: projectName, +// EventSummary: fmt.Sprintf("enabled CLA service for project: %s", projectName), +// EventType: claEvents.ProjectServiceCLAEnabled, +// LfUsername: "easycla system", +// UserID: "easycla system", +// UserName: "easycla system", +// }) +// if eventErr != nil { +// log.WithFields(f).WithError(eventErr).Warn("problem logging event for enabling CLA service") +// // Ok - don't fail for now +// } +// +// return nil +//} +// +//// ProjectServiceDisableCLAServiceHandler handles disabling/removing the CLA Service attribute from the project service +//func (s *service) ProjectServiceDisableCLAServiceHandler(event events.DynamoDBEventRecord) error { +// ctx := utils.NewContext() +// f := logrus.Fields{ +// "functionName": "dynamo_events.projects_cla_groups.ProjectServiceDisableCLAServiceHandler", +// utils.XREQUESTID: ctx.Value(utils.XREQUESTID), +// "eventID": event.EventID, +// "eventName": event.EventName, +// "eventSource": event.EventSource, +// } +// +// log.WithFields(f).Debug("processing request") +// var oldProject ProjectClaGroup +// err := unmarshalStreamImage(event.Change.OldImage, &oldProject) +// if err != nil { +// log.WithFields(f).WithError(err).Warn("problem unmarshalling stream image") +// return err +// } +// +// // Add more fields for the logger +// f["ProjectSFID"] = oldProject.ProjectSFID +// f["ClaGroupID"] = oldProject.ClaGroupID +// f["FoundationSFID"] = oldProject.FoundationSFID +// +// psc := v2ProjectService.GetClient() +// log.WithFields(f).Debug("looking up project by SFID...") +// projectDetails, prjerr := psc.GetProject(oldProject.ProjectSFID) +// if prjerr != nil { +// log.WithError(err).Warnf("unable to get project details from SFID: %s", oldProject.ProjectSFID) +// } +// projectName := oldProject.ProjectSFID +// if projectDetails != nil { +// projectName = projectDetails.Name +// f["projectName"] = projectName +// } +// +// // Gathering metrics - grab the time before the API call +// before, _ := utils.CurrentTime() +// log.WithFields(f).Debugf("disabling CLA service for project %s with ID: %s", projectName, oldProject.ProjectSFID) +// err = psc.DisableCLA(oldProject.ProjectSFID) +// if err != nil { +// log.WithFields(f).WithError(err).Warn("disabling CLA service failed") +// return err +// } +// log.WithFields(f).Debugf("disabled CLA service for project %s with ID: %s", projectName, oldProject.ProjectSFID) +// log.WithFields(f).Debugf("disabling CLA service completed - took %s", time.Since(before).String()) +// +// // Log the event +// eventErr := s.eventsRepo.CreateEvent(&models.Event{ +// ContainsPII: false, +// EventData: fmt.Sprintf("disabled CLA service for project: %s with ID: %s", projectName, oldProject.ProjectSFID), +// EventFoundationSFID: oldProject.FoundationSFID, +// EventProjectExternalID: oldProject.ProjectSFID, +// EventProjectID: oldProject.ClaGroupID, +// EventProjectName: projectName, +// EventProjectSFID: oldProject.ProjectSFID, +// EventSummary: fmt.Sprintf("disabled CLA service for project: %s", projectName), +// EventType: claEvents.ProjectServiceCLADisabled, +// LfUsername: "easycla system", +// UserID: "easycla system", +// UserName: "easycla system", +// }) +// if eventErr != nil { +// log.WithFields(f).WithError(eventErr).Warn("problem logging event for disabling CLA service") +// // Ok - don't fail for now +// } +// +// return nil +//} func (s *service) ProjectUnenrolledDisableRepositoryHandler(event events.DynamoDBEventRecord) error { ctx := utils.NewContext() diff --git a/cla-backend-go/v2/dynamo_events/service.go b/cla-backend-go/v2/dynamo_events/service.go index 378a4edc9..7fcef6ef3 100644 --- a/cla-backend-go/v2/dynamo_events/service.go +++ b/cla-backend-go/v2/dynamo_events/service.go @@ -122,8 +122,9 @@ func NewService(stage string, s.registerCallback(eventsTable, Insert, s.EventAddedEvent) // Enable or Disable the CLA Service Enabled/Disabled flag/attribute in the platform Project Service - s.registerCallback(projectsCLAGroupsTable, Insert, s.ProjectServiceEnableCLAServiceHandler) - s.registerCallback(projectsCLAGroupsTable, Remove, s.ProjectServiceDisableCLAServiceHandler) + // These are called by the API via the service layer - includes the user who did it + //s.registerCallback(projectsCLAGroupsTable, Insert, s.ProjectServiceEnableCLAServiceHandler) + //s.registerCallback(projectsCLAGroupsTable, Remove, s.ProjectServiceDisableCLAServiceHandler) s.registerCallback(projectsCLAGroupsTable, Remove, s.ProjectUnenrolledDisableRepositoryHandler) // Add or Remove any CLA Permissions for the specified project From 5b9633adb6f3a5966a090341ac35971ffb4cf9ee Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 1 Mar 2021 23:39:45 -0800 Subject: [PATCH 0126/1276] Search CLA Group Events by CLA Group ID (#2726) - Added new key/index for searching events by CLA Group ID - Updated query logic - Updated python data models - Cleaned up event message english - Updated serverless config Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 6 +- cla-backend-go/events/repository.go | 5 +- cla-backend-go/repositories/handlers.go | 5 +- cla-backend-go/serverless.yml | 1 + cla-backend-go/v2/cla_groups/service.go | 14 +-- cla-backend-go/v2/events/handlers.go | 2 + cla-backend/cla/models/dynamo_models.py | 137 +++++++++++++++++------- cla-backend/serverless.yml | 1 + 8 files changed, 121 insertions(+), 50 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 0e48bbb00..28d74b035 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -775,19 +775,19 @@ func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArg // GetEventSummaryString . . . func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Repository: %s was added to Project: %s by: %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was added to the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Repository: %s was deleted from Project: %s by: %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was deleted from the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *RepositoryUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Repository: %s was updated for the project project: %s by: %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was updated for the project project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) return data, true } diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 880b03cd3..f99780f1f 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -44,6 +44,7 @@ const ( CompanyIDEventTypeIndex = "company-id-event-type-index" EventFoundationSFIDEpochIndex = "event-foundation-sfid-event-time-epoch-index" EventProjectIDEpochIndex = "event-project-id-event-time-epoch-index" + EventCLAGroupIDEpochIndex = "event-cla-group-id-event-time-epoch-index" ) // constants @@ -516,8 +517,8 @@ func (repo *repository) GetFoundationEvents(foundationSFID string, nextKey *stri // GetClaGroupEvents returns the list of cla-group events func (repo *repository) GetClaGroupEvents(claGroupID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { - keyCondition := expression.Key("event_project_id").Equal(expression.Value(claGroupID)) - return repo.queryEventsTable(EventProjectIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) + keyCondition := expression.Key("event_cla_group_id").Equal(expression.Value(claGroupID)) + return repo.queryEventsTable(EventCLAGroupIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } // toString encodes the map as a string diff --git a/cla-backend-go/repositories/handlers.go b/cla-backend-go/repositories/handlers.go index 6a327007d..8bd5a6a67 100644 --- a/cla-backend-go/repositories/handlers.go +++ b/cla-backend-go/repositories/handlers.go @@ -54,7 +54,8 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryAdded, - ProjectID: utils.StringValue(params.GithubRepositoryInput.RepositoryProjectID), + CLAGroupID: utils.StringValue(params.GithubRepositoryInput.RepositoryProjectID), + ProjectID: params.ProjectSFID, ExternalProjectID: params.ProjectSFID, UserID: claUser.UserID, LfUsername: claUser.LFUsername, @@ -92,8 +93,8 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, + ProjectID: params.ProjectSFID, ExternalProjectID: params.ProjectSFID, - ProjectID: ghRepo.RepositoryProjectID, UserID: claUser.UserID, LfUsername: claUser.LFUsername, EventData: &events.RepositoryDisabledEventData{ diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index 3188aaac3..aac6235f3 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -181,6 +181,7 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index cd056d9f6..3499a5d9d 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -95,7 +95,7 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input } f := logrus.Fields{ - "functionName": "CreateCLAGroup", + "functionName": "v2.cla_groups.service.CreateCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, @@ -232,7 +232,7 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { // Validate the input f := logrus.Fields{ - "functionName": "UpdateCLAGroup", + "functionName": "v2.cla_groups.service.UpdateCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, @@ -352,7 +352,7 @@ func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGr // ListClaGroupsForFoundationOrProject returns the CLA Group list for the specified foundation ID func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, projectOrFoundationSFID string) (*models.ClaGroupListSummary, error) { // nolint f := logrus.Fields{ - "functionName": "ListClaGroupsForFoundationOrProject", + "functionName": "v2.cla_groups.service.ListClaGroupsForFoundationOrProject", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectOrFoundationSFID": projectOrFoundationSFID, } @@ -611,7 +611,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje func (s *service) ListAllFoundationClaGroups(ctx context.Context, foundationID *string) (*models.FoundationMappingList, error) { f := logrus.Fields{ - "functionName": "ListAllFoundationClaGroups", + "functionName": "v2.cla_groups.service.ListAllFoundationClaGroups", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "foundationID": foundationID, } @@ -632,7 +632,7 @@ func (s *service) ListAllFoundationClaGroups(ctx context.Context, foundationID * // DeleteCLAGroup handles deleting and invalidating the CLA group, removing permissions, cleaning up pending requests, etc. func (s *service) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.ClaGroup, authUser *auth.User) error { f := logrus.Fields{ - "functionName": "DeleteCLAGroup", + "functionName": "v2.cla_groups.service.DeleteCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupModel.ProjectID, "claGroupExternalID": claGroupModel.ProjectExternalID, @@ -890,7 +890,7 @@ func (s *service) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.Cl // EnrollProjectsInClaGroup enrolls the specified project list in the CLA Group func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollProjectsModel) error { f := logrus.Fields{ - "functionName": "EnrollProjectsInClaGroup", + "functionName": "v2.cla_groups.service.EnrollProjectsInClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": request.AuthUser.UserName, "authUserEmail": request.AuthUser.Email, @@ -952,7 +952,7 @@ func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollP //func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *UnenrollProjectsModel) error { f := logrus.Fields{ - "functionName": "UnenrollProjectsInClaGroup", + "functionName": "v2.cla_groups.service.UnenrollProjectsInClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": request.AuthUser.UserName, "authUserEmail": request.AuthUser.Email, diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index 365bad29f..5d45ad2bb 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -224,6 +224,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe } // Lookup the CLA Group associated with this Project SFID... + log.WithFields(f).Debugf("loading CLA Group for projectSFID: %s", params.ProjectSFID) pm, err := projectsClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) if err != nil { msg := fmt.Sprintf("problem loading CLA Group from Project SFID:: %s", params.ProjectSFID) @@ -241,6 +242,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe } // Lookup any events for this CLA Group.... + log.WithFields(f).Debugf("loading CLA Group %s events using ID: %s", pm.ClaGroupName, pm.ClaGroupID) result, err := service.GetClaGroupEvents(pm.ClaGroupID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents), params.SearchTerm) if err != nil { msg := fmt.Sprintf("problem loading events for CLA Group: %s with ID: %s error: %v", pm.ClaGroupName, pm.ClaGroupID, err.Error()) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 123ec1b79..920308633 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -4142,20 +4142,31 @@ class Meta: event_id = UnicodeAttribute(hash_key=True) event_user_id = UnicodeAttribute(null=True) event_type = UnicodeAttribute(null=True) + + event_cla_group_id = UnicodeAttribute(null=True) + event_cla_group_name = UnicodeAttribute(null=True) + event_cla_group_name_lower = UnicodeAttribute(null=True) + event_project_id = UnicodeAttribute(null=True) + event_project_name = UnicodeAttribute(null=True) + event_project_name_lower = UnicodeAttribute(null=True) + event_project_external_id = UnicodeAttribute(null=True) + event_company_id = UnicodeAttribute(null=True) + event_company_sfid = UnicodeAttribute(null=True) event_company_name = UnicodeAttribute(null=True) event_company_name_lower = UnicodeAttribute(null=True) - event_project_name = UnicodeAttribute(null=True) - event_project_name_lower = UnicodeAttribute(null=True) + event_user_name = UnicodeAttribute(null=True) event_user_name_lower = UnicodeAttribute(null=True) + event_time = UTCDateTimeAttribute(default=datetime.datetime.utcnow()) event_time_epoch = NumberAttribute(default=int(time.time())) + event_date = UnicodeAttribute(null=True) + event_data = UnicodeAttribute(null=True) event_summary = UnicodeAttribute(null=True) - event_date = UnicodeAttribute(null=True) - event_project_external_id = UnicodeAttribute(null=True) + event_date_and_contains_pii = UnicodeAttribute(null=True) company_id_external_project_id = UnicodeAttribute(null=True) contains_pii = BooleanAttribute(null=True) @@ -4173,8 +4184,11 @@ def __init__( event_id=None, event_type=None, user_id=None, + event_cla_group_id=None, + event_cla_group_name=None, event_project_id=None, event_company_id=None, + event_company_sfid=None, event_data=None, event_summary=None, event_company_name=None, @@ -4187,38 +4201,57 @@ def __init__( self.model = EventModel() self.model.event_id = event_id self.model.event_type = event_type + self.model.event_user_id = user_id - self.model.event_project_id = event_project_id - self.model.event_company_id = event_company_id - self.model.event_data = event_data - self.model.event_summary = event_summary - self.model.event_company_name = event_company_name - self.model.contains_pii = contains_pii - if self.model.event_company_name: - self.model.event_company_name_lower = self.model.event_company_name.lower() self.model.event_user_name = event_user_name if self.model.event_user_name: self.model.event_user_name_lower = self.model.event_user_name.lower() + + self.model.event_cla_group_id = event_cla_group_id + self.model.event_cla_group_name = event_cla_group_name + if self.model.event_cla_group_name: + self.model.event_cla_group_name_lower = self.model.event_cla_group_name.lower() + + self.model.event_project_id = event_project_id self.model.event_project_name = event_project_name if self.model.event_project_name: self.model.event_project_name_lower = self.model.event_project_name.lower() + self.model.event_company_id = event_company_id + self.model.event_company_sfid = event_company_sfid + self.model.event_company_name = event_company_name + if self.model.event_company_name: + self.model.event_company_name_lower = self.model.event_company_name.lower() + + self.model.event_data = event_data + self.model.event_summary = event_summary + self.model.contains_pii = contains_pii + def __str__(self): return ( f"id:{self.model.event_id}, " f"event type:{self.model.event_type}, " + f"event_user id:{self.model.event_user_id}, " + f"event user name: {self.model.event_user_name}," + + f"event cla group id:{self.model.event_cla_group_id}, " + f"event cla group name:{self.model.event_cla_group_name}, " + f"event project id:{self.model.event_project_id}, " + f"event project name: {self.model.event_project_name}, " + f"event project external id: {self.model.event_project_external_id}," + f"event company id: {self.model.event_company_id}, " + f"event company sfid: {self.model.event_company_sfid}, " + f"event company name: {self.model.event_company_name}, " + f"event time: {self.model.event_time}, " f"event time epoch: {self.model.event_time_epoch}, " + f"event date: {self.model.event_date}," + f"event data: {self.model.event_data}, " f"event summary: {self.model.event_summary}, " - f"event company name: {self.model.event_company_name}, " - f"event project name: {self.model.event_project_name}, " - f"event user name: {self.model.event_user_name}," - f"event date: {self.model.event_date}," - f"event project external id: {self.model.event_project_external_id}," f"contains pii: {self.model.contains_pii}" ) @@ -4236,12 +4269,6 @@ def load(self, event_id): raise cla.models.DoesNotExist("Event not found") self.model = event - def get_event_company_id(self): - return self.model.event_company_id - - def get_event_company_name(self): - return self.model.event_company_name - def get_event_user_id(self): return self.model.event_user_id @@ -4257,9 +4284,21 @@ def get_event_date(self): def get_event_id(self): return self.model.event_id + def get_event_cla_group_id(self): + return self.model.event_cla_group_id + + def get_event_cla_group_name(self): + return self.model.event_cla_group_name + + def get_event_cla_group_name_lower(self): + return self.model.event_cla_group_name_lower + def get_event_project_id(self): return self.model.event_project_id + def get_event_project_external_id(self): + return self.model.event_project_external_id + def get_event_project_name(self): return self.model.event_project_name @@ -4275,6 +4314,15 @@ def get_event_time(self): def get_event_time_epoch(self): return self.model.event_time_epoch + def get_event_company_id(self): + return self.model.event_company_id + + def get_event_company_sfid(self): + return self.model.event_company_sfid + + def get_event_company_name(self): + return self.model.event_company_name + def get_event_company_name_lower(self): return self.model.event_company_name_lower @@ -4284,9 +4332,6 @@ def get_event_user_name(self): def get_event_user_name_lower(self): return self.model.event_user_name_lower - def get_event_project_external_id(self): - return self.model.event_project_external_id - def get_company_id_external_project_id(self): return self.model.company_id_external_project_id @@ -4302,13 +4347,14 @@ def all(self, ids=None): ret.append(ev) return ret - def set_event_company_id(self, company_id): - self.model.event_company_id = company_id - - def set_event_company_name(self, company_name): - self.model.event_company_name = company_name - if company_name: - self.model.event_company_name_lower = company_name.lower() + def all_limit(self, limit: Optional[int] = None, last_evaluated_key: Optional[str] = None): + result_iterator = self.model.scan(limit=limit, last_evaluated_key=last_evaluated_key) + ret = [] + for signature in result_iterator: + evt = Event() + evt.model = signature + ret.append(evt) + return ret, result_iterator.last_evaluated_key, result_iterator.total_count def set_event_data(self, event_data): self.model.event_data = event_data @@ -4319,12 +4365,34 @@ def set_event_summary(self, event_summary): def set_event_id(self, event_id): self.model.event_id = event_id + def set_event_company_id(self, company_id): + self.model.event_company_id = company_id + + def set_event_company_sfid(self, company_sfid): + self.model.event_company_sfid = company_sfid + + def set_event_company_name(self, company_name): + self.model.event_company_name = company_name + if company_name: + self.model.event_company_name_lower = company_name.lower() + def set_event_user_id(self, user_id): self.model.event_user_id = user_id + def set_event_cla_group_id(self, event_cla_group_id): + self.model.event_cla_group_id = event_cla_group_id + + def set_event_cla_group_name(self, event_cla_group_name): + self.model.event_cla_group_name = event_cla_group_name + if event_cla_group_name: + self.model.event_cla_group_name_lower = event_cla_group_name.lower() + def set_event_project_id(self, event_project_id): self.model.event_project_id = event_project_id + def set_event_project_external_id(self, event_project_external_id): + self.model.event_project_external_id = event_project_external_id + def set_event_project_name(self, event_project_name): self.model.event_project_name = event_project_name if event_project_name: @@ -4337,9 +4405,6 @@ def set_event_user_name(self, event_user_name): self.model.event_user_name = event_user_name self.model.event_user_name_lower = event_user_name.lower() - def set_event_project_external_id(self, event_project_external_id): - self.model.event_project_external_id = event_project_external_id - def set_event_date_and_contains_pii(self, contains_pii=False): dateDDMMYYYY = datetime.date.today().strftime("%d-%m-%Y") self.model.contains_pii = contains_pii diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 9044a474e..b216dcdc0 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -271,6 +271,7 @@ provider: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" From d7722c3c536a9b19c11ae48736a5b9bc42094041 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 2 Mar 2021 09:40:14 +0200 Subject: [PATCH 0127/1276] change email content for cla signatory (#2719) Signed-off-by: makkalot --- cla-backend/cla/models/docusign_models.py | 63 +++++++++++++++---- .../cla/tests/unit/test_docusign_models.py | 24 ++++++- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 8c9aca376..17a57d8bf 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -14,10 +14,11 @@ import urllib.request import uuid import xml.etree.ElementTree as ET -from typing import Dict, Any, Optional +from typing import Dict, Any, Optional, List from urllib.parse import urlparse import pydocusign # type: ignore +from attr import dataclass from pydocusign.exceptions import DocuSignException # type: ignore import cla @@ -25,11 +26,12 @@ from cla.models import signing_service_interface, DoesNotExist from cla.models.dynamo_models import Signature, User, \ Project, Company, Gerrit, \ - Document, Event + Document, Event, ProjectCLAGroupModel from cla.models.event_types import EventType from cla.models.s3_storage import S3Storage from cla.user_service import UserService -from cla.utils import get_email_help_content, append_email_help_sign_off_content, get_corporate_url +from cla.utils import get_email_help_content, append_email_help_sign_off_content, get_corporate_url, \ + get_project_cla_group_instance api_base_url = os.environ.get('CLA_API_BASE', '') root_url = os.environ.get('DOCUSIGN_ROOT_URL', '') @@ -44,8 +46,6 @@ lf_group = LFGroup(lf_group_client_url, lf_group_client_id, lf_group_client_secret, lf_group_refresh_token) - - class ProjectDoesNotExist(Exception): pass @@ -1270,18 +1270,27 @@ def populate_sign_url(self, signature, callback_url=None, # Not assigning a clientUserId sends an email. project_name = project.get_project_name() + cla_group_name = project_name company_name = company.get_company_name() + project_cla_group = get_project_cla_group_instance() + project_cla_groups = project_cla_group.get_by_cla_group_id(project.get_project_id()) + project_names = [p.get_project_name() for p in project_cla_groups] + if not project_names: + project_names = [project_name] cla.log.debug(f'{fn} - {sig_type} - sending document as email with ' f'name: {signatory_name}, email: {signatory_email} ' f'project name: {project_name}, company: {company_name}') - email_subject = f'EasyCLA: CLA Signature Request for {project_name}' - email_body = f'

        Hello {signatory_name},

        ' - email_body += f'

        This is a notification email from EasyCLA regarding the project {project_name}. {cla_manager_name} has designated you as being an authorized signatory for {company_name}. In order for employees of your company to contribute to the open source project {project_name}, they must do so under a Contributor License Agreement signed by someone with authority to sign on behalf of your company.

        ' - email_body += f'

        After you sign, {cla_manager_name} (as the initial CLA Manager for your company) will be able to maintain the list of specific employees authorized to contribute to the project under this signed CLA.

        ' - email_body += f'

        If you are authorized to sign on your company’s behalf, and if you approve {cla_manager_name} as your initial CLA Manager for {project_name}, please review the document and sign the CLA.If you have questions, or if you are not an authorized signatory of this company, please contact the requester at {cla_manager_email}.

        ' - email_body = append_email_help_sign_off_content(email_body, project.get_version()) + email_subject, email_body = cla_signatory_email_content( + ClaSignatoryEmailParams(cla_group_name=cla_group_name, + signatory_name=signatory_name, + cla_manager_name=cla_manager_name, + cla_manager_email=cla_manager_email, + company_name=company_name, + project_version=project.get_version(), + 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, @@ -2194,7 +2203,7 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa pdf_link = (f'{cla.conf["API_BASE_URL"]}/v3/' f'signatures/{project.get_project_id()}/' f'{signature.get_signature_reference_id()}/ccla/pdf') - + corporate_url = get_corporate_url(project.get_version()) recipient_name = user.get_user_name() or user.get_lf_username() or None @@ -2206,7 +2215,7 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa recipient_name = "CLA Manager" subject = f'EasyCLA: CLA Signed for {project.get_project_name()}' - body = f''' + body = f'''

        Hello {recipient_name},

        This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

        The CLA has now been signed. You can download the signed CLA as a PDF @@ -2216,3 +2225,31 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa ''' body = append_email_help_sign_off_content(body, project.get_version()) return subject, body + + +@dataclass +class ClaSignatoryEmailParams: + cla_group_name: str + signatory_name: str + cla_manager_name: str + cla_manager_email: str + company_name: str + project_version: str + project_names: List[str] + + +def cla_signatory_email_content(params: ClaSignatoryEmailParams) -> (str, str): + """ + cla_signatory_email_content prepares the content for cla signatory + :param params: ClaSignatoryEmailParams + :return: + """ + project_names_list = ", ".join(params.project_names) + + email_subject = f'EasyCLA: CLA Signature Request for {params.cla_group_name}' + email_body = f'

        Hello {params.signatory_name},

        ' + email_body += f'

        This is a notification email from EasyCLA regarding the project(s) {project_names_list} associated with the CLA Group {params.cla_group_name}. {params.cla_manager_name} has designated you as being an authorized signatory for the organization {params.company_name}. In order for employees of your company to contribute to any of the above project(s), they must do so under a Contributor License Agreement signed by someone with authority n behalf of your company.

        ' + email_body += f'

        After you sign, {params.cla_manager_name} (as the initial CLA Manager for your company) will be able to maintain the list of specific employees authorized to contribute to the project(s) under this signed CLA.

        ' + email_body += f'

        If you are authorized to sign on your company’s behalf, and if you approve {params.cla_manager_name} as your initial CLA Manager, please review the document and sign the CLA. If you have questions, or if you are not an authorized signatory of this company, please contact the requester at {params.cla_manager_email}.

        ' + email_body = append_email_help_sign_off_content(email_body, params.project_version) + return email_subject, email_body diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index f07b856c6..34a0bed2d 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -4,7 +4,7 @@ import xml.etree.ElementTree as ET from cla.models.docusign_models import populate_signature_from_ccla_callback, populate_signature_from_icla_callback, \ - create_default_company_values, document_signed_email_content + create_default_company_values, document_signed_email_content, ClaSignatoryEmailParams, cla_signatory_email_content from cla.models.dynamo_models import Signature, Company, Project, User content_icla_agreement_date = """ @@ -860,3 +860,25 @@ def test_document_signed_email_content(): assert "Hello Contributor" in body + +def test_cla_signatory_email_content(): + params = ClaSignatoryEmailParams( + cla_group_name="cla_group_name_value", + signatory_name="signatory_name_value", + cla_manager_name="john", + cla_manager_email="john@example.com", + company_name="IBM", + project_version="v1", + project_names=["project1", "project2"] + ) + + email_subject, email_body = cla_signatory_email_content(params) + assert "EasyCLA: CLA Signature Request for cla_group_name_value" == email_subject + assert "

        Hello signatory_name_value,

        " in email_body + assert "EasyCLA regarding the project(s) project1, project2 associated" in email_body + assert "with the CLA Group cla_group_name_value" in email_body + assert "john has designated you as being an authorized signatory" in email_body + assert "signatory for the organization IBM" in email_body + assert "

        After you sign, john (as the initial CLA Manager for your company)" in email_body + assert "and if you approve john as your initial CLA Manager" in email_body + assert "contact the requester at john@example.com" in email_body From af79079e3ca05937c8111293913adef4997aafb2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 2 Mar 2021 14:12:22 -0800 Subject: [PATCH 0128/1276] Updated Event Log Message Formatting (#2727) - Updated event log summary messages using a common format - Use LogEventWithContext when possible, added more logging Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 654 +++++++++++++++--- cla-backend-go/v2/cla_manager/service.go | 8 +- cla-backend-go/v2/company/service.go | 16 +- cla-backend-go/v2/gerrits/handlers.go | 4 +- cla-backend-go/v2/github_activity/service.go | 62 +- .../v2/github_organizations/handlers.go | 6 +- cla-backend-go/v2/repositories/handlers.go | 6 +- 7 files changed, 624 insertions(+), 132 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 28d74b035..bf0f750e8 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -753,128 +753,292 @@ func (ed *ClaManagerRoleDeletedData) GetEventDetailsString(args *LogEventArgs) ( // GetEventDetailsString . . . func (ed *CLAGroupEnrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s enabled the the project %s from the CLA Group %s.", + return fmt.Sprintf("The user %s enabled the the project %s from the CLA Group %s.", args.UserName, args.ProjectName, args.ClaGroupModel.ProjectName), false } // GetEventDetailsString . . . func (ed *CLAGroupUnenrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s unenrolled the the project %s from the CLA Group %s.", + return fmt.Sprintf("The user %s unenrolled the the project %s from the CLA Group %s.", args.UserName, args.ProjectName, args.ClaGroupModel.ProjectName), false } // GetEventDetailsString . . . func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s enabled the CLA Service for the project %s.", args.UserName, args.ProjectName), false + data := fmt.Sprintf("The user %s enabled the CLA Service", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, false } // GetEventDetailsString . . . func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s disabled the CLA Service for the project %s.", args.UserName, args.ProjectName), false + data := fmt.Sprintf("The user %s disabled the CLA Service", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, false } // GetEventSummaryString . . . func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was added to the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was added", ed.RepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was deleted from the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was deleted", ed.RepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *RepositoryUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was updated for the project project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was updated", ed.RepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository branch protection %s was added", ed.RepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository branch protection %s was disabled", ed.RepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository branch protection %s was updated", ed.RepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *UserCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was added, User Details: %+v.", args.UserName, args.UserModel) + data := fmt.Sprintf("The user %s was added with the user details: %+v.", args.UserName, args.UserModel) return data, true } // GetEventSummaryString . . . func (ed *UserUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("User: %s was updated, User Details: %+v.", args.UserName, *args.UserModel), true + return fmt.Sprintf("The user %s was updated with the user details: %+v.", args.UserName, *args.UserModel), true } // GetEventSummaryString . . . func (ed *UserDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User ID : %s was deleted by: %s.", ed.DeletedUserID, args.UserName) + data := fmt.Sprintf("The user ID %s was deleted by the user %s.", ed.DeletedUserID, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CompanyACLRequestAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s with ID: %s, Email: %s requested Company Invite for Company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) + data := fmt.Sprintf("The user %s with ID %s and with the email %s requested a company invitation", + ed.UserName, ed.UserID, ed.UserEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CompanyACLRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Company Invite was approved access for User: %s with ID: %s, Email: %s for company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) + data := fmt.Sprintf("A company invite was approved for the user %s with the ID of %s with the email %s", + ed.UserName, ed.UserID, ed.UserEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CompanyACLRequestDeniedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Company Invite was denied access for User: %s with ID: %s, Email %s for Company: %s.", - ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) + data := fmt.Sprintf("A company invite was denied for the user %s with the ID of %s with the email %s", + ed.UserName, ed.UserID, ed.UserEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CompanyACLUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User with LF Username %s was added to the ACL for Company: %s by: %s.", + data := fmt.Sprintf("The user with LF username %s was added to the access list for the company %s by the user %s.", ed.UserLFID, args.CompanyName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *CLATemplateCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("PDF templates were created for Project %s by: %s.", args.ProjectName, args.UserName) + data := "The PDF templates were created" + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *GitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization: %s was added with auto-enabled: %t, branch protection enabled: %t", + data := fmt.Sprintf("The GitHub organization %s was added with auto-enabled set to %t with branch protection enabled set to %t", ed.GitHubOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) if ed.AutoEnabledClaGroupID != "" { - data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + data = data + fmt.Sprintf(" with auto-enabled-cla-group set to %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf(" by: %s.", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *GitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization: %s was deleted by: %s.", - ed.GitHubOrganizationName, args.UserName) + data := fmt.Sprintf("The GitHub organization %s was deleted", ed.GitHubOrganizationName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -885,161 +1049,367 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEve if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf(" by: %s.", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CCLAApprovalListRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s approved a CCLA Approval Request for Project: %s, Company: %s.", - args.UserName, args.ProjectName, args.CompanyName) + data := fmt.Sprintf("The user %s approved a CCLA approval request", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CCLAApprovalListRequestRejectedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s rejected a CCLA Approval Request for Project: %s, Company: %s.", - args.UserName, args.ProjectName, args.CompanyName) + data := fmt.Sprintf("The user %s rejected a CCLA approval request", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAManagerRequestCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s added CLA Manager Request: %s for Company: %s, Project: %s.", - ed.UserName, ed.RequestID, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The user %s added a CLA Manager request", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAManagerCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was added as CLA Manager for Company: %s, Project: %s.", - ed.UserName, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The user %s was added as CLA Manager", ed.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAManagerDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was removed as CLA Manager for Company: %s, Project: %s.", - ed.UserLFID, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The user %s was removed as CLA Manager", ed.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAManagerRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager Request: %s for User: %s was approved by: %s for Company: %s, Project: %s.", - ed.RequestID, ed.UserName, ed.ManagerName, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The CLA Manager request for the user %s was approved by the CLA Manager %s", + ed.UserName, ed.ManagerName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAManagerRequestDeniedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager Request: %s for User: %s was denied by: %s for Company: %s, Project: %s.", - ed.RequestID, ed.UserName, ed.ManagerName, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The CLA Manager request for the user %s was denied by the CLA Manager %s", + ed.UserName, ed.ManagerName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAManagerRequestDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager Request: %s for User: %s was deleted by: %s for Company: %s, Project: %s.", - ed.RequestID, ed.UserName, ed.ManagerName, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The CLA Manager request for the user %s was deleted by the CLA Manager %s", + ed.UserName, ed.ManagerName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s added Email: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s added the email %s to the approval list", ed.UserName, ed.ApprovalListEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s removed Email: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s removed the email %s from the approval list", ed.UserName, ed.ApprovalListEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s added Domain: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s added the domain %s to the approval list", ed.UserName, ed.ApprovalListDomain) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s removed Domain: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s removed the domain %s from the approval list", ed.UserName, ed.ApprovalListDomain) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s added GitHub Username: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s added the GitHub username %s to the approval list", ed.UserName, ed.ApprovalListGitHubUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s removed GitHub Username: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s removed the GitHub username %s from the approval list", ed.UserName, ed.ApprovalListGitHubUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s added GitHub Organization: %s to the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s added the GitHub organization %s to the approval list", ed.UserName, ed.ApprovalListGitHubOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s removed GitHub Organization: %s from the approval list for Company: %s, Project: %s.", - ed.UserName, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The CLA Manager %s removed the GitHub organization %s from the approval list", ed.UserName, ed.ApprovalListGitHubOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CCLAApprovalListRequestCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s created a CCLA Approval Request for Project: %s, Company: %s.", - args.UserName, args.ProjectName, args.CompanyName) + data := fmt.Sprintf("The user %s created a CCLA Approval Request", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s added GitHub Organization: %s to the whitelist for Project: %s, Company: %s.", - args.UserName, ed.GitHubOrganizationName, args.ProjectName, args.CompanyName) + data := fmt.Sprintf("The CLA Manager %s added the GitHub organization %s to the approval list", args.UserName, ed.GitHubOrganizationName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s removed GitHub Organization: %s from the whitelist for Project: %s, Company: %s.", - args.UserName, ed.GitHubOrganizationName, args.ProjectName, args.CompanyName) + data := fmt.Sprintf("The CLA Manager %s removed the GitHub organization %s from the approval list", args.UserName, ed.GitHubOrganizationName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ClaManagerAccessRequestAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s has requested to be CLA Manager for the project %s, the company %s.", - args.UserName, ed.ProjectName, ed.CompanyName) + data := fmt.Sprintf("The user %s has requested to be CLA Manager", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ClaManagerAccessRequestDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s has deleted a request to be CLA Manager.", - args.UserName) + data := fmt.Sprintf("The user %s has deleted a request to be CLA Manager", args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was created by the user %s.", - args.ProjectName, args.UserName) + data := fmt.Sprintf("The CLA Group %s was created by the user %s.", args.ProjectName, args.UserName) return data, true } @@ -1051,34 +1421,60 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString . . . func (ed *CLAGroupDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was deleted by the user %s.", - args.ProjectName, args.UserName) + data := fmt.Sprintf("The CLA Group %s was deleted by the user %s.", args.ProjectName, args.UserName) return data, true } // GetEventSummaryString . . . func (ed *GerritProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("%d Gerrit repositories were deleted due to CLA Group/Project %s deletion.", - ed.DeletedCount, args.ProjectName) + data := fmt.Sprintf("%d Gerrit repositories were deleted due to CLA Group/Project deletion", ed.DeletedCount) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + data = data + "." return data, false } // GetEventSummaryString . . . func (ed *GerritAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gerrit repository %s was added by: %s.", ed.GerritRepositoryName, args.UserName) + data := fmt.Sprintf("The Gerrit repository %s was added by the user %s", ed.GerritRepositoryName, args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *GerritDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gerrit repository %s was deleted by %s.", ed.GerritRepositoryName, args.UserName) + data := fmt.Sprintf("The Gerrit repository %s was deleted by the user %s", ed.GerritRepositoryName, args.UserName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *GitHubProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("%d GitHub repositories were deleted due to CLA Group/project %s deletion.", - ed.DeletedCount, args.ProjectName) + data := fmt.Sprintf("%d GitHub repositories were deleted due to CLA Group/project deletion", + ed.DeletedCount) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + data = data + "." return data, true } @@ -1091,48 +1487,122 @@ func (ed *SignatureProjectInvalidatedEventData) GetEventSummaryString(args *LogE // GetEventSummaryString . . . func (ed *ContributorNotifyCompanyAdminData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s notified the company admin %s by the email address %s for the company %s.", - args.UserName, ed.AdminName, ed.AdminEmail, args.CompanyName) + data := fmt.Sprintf("The user %s notified the company admin %s by the email address %s", + args.UserName, ed.AdminName, ed.AdminEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ContributorNotifyCLADesignee) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s notified the CLA Designee %s by email %s for the project: %s and the company %s.", - args.UserName, ed.DesigneeName, ed.DesigneeEmail, - args.ProjectName, args.CompanyName) + data := fmt.Sprintf("The user %s notified the CLA Designee %s by email %s", args.UserName, ed.DesigneeName, ed.DesigneeEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was assigned as CLA Manager Designee for the project: %s for the company %s by the user %s.", - ed.DesigneeName, - args.ProjectName, args.CompanyName, args.UserName) + data := fmt.Sprintf("The user %s was assigned as CLA Manager Designee", ed.DesigneeName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was converted to a contact for the project %s.", - args.LfUsername, args.ProjectName) + data := fmt.Sprintf("The user %s was converted to a contact", args.LfUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added to the role %s for project: %s.", args.LfUsername, ed.Role, args.ProjectName) + data := fmt.Sprintf("The user %s was added to the role %s", args.LfUsername, ed.Role) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ClaManagerRoleCreatedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added with to the role %s by user %s.", ed.UserName, ed.Role, args.UserName) + data := fmt.Sprintf("The user %s was added to the role %s", ed.UserName, ed.Role) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, false } // GetEventSummaryString . . . func (ed *ClaManagerRoleDeletedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was removed from the role %s by user %s.", ed.UserName, ed.Role, args.UserName) + data := fmt.Sprintf("The user %s was removed from the role %s", ed.UserName, ed.Role) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, false } diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index f6ce5a327..6a95c6b95 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -396,7 +396,7 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companyID string userEmail, utils.CLADesigneeRole, roleID, projectSFID, v1CompanyModel.CompanyExternalID) // Log Event - s.eventService.LogEvent( + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.AssignUserRoleScopeType, LfUsername: lfxUser.Username, @@ -730,7 +730,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool } s.SendEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) // Make a note in the event log - s.eventService.LogEvent(&events.LogEventArgs{ + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ContributorNotifyCompanyAdminType, LfUsername: authUser.UserName, ExternalProjectID: projectID, @@ -781,7 +781,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debug("creating a contributor assigned CLA designee log event...") // Make a note in the event log - s.eventService.LogEvent(&events.LogEventArgs{ + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ContributorAssignCLADesigneeType, LfUsername: authUser.UserName, ExternalProjectID: projectID, @@ -798,7 +798,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debug("creating a contributor notify CLA designee log event...") // Make a note in the event log - s.eventService.LogEvent(&events.LogEventArgs{ + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ContributorNotifyCLADesigneeType, LfUsername: authUser.UserName, ExternalProjectID: projectID, diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 68cd5e6ff..a72c5d83a 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -36,7 +36,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/signatures" "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/utils" - acs_service "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" + acsService "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" orgModels "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service/models" orgService "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" @@ -74,10 +74,6 @@ const ( HugePageSize = int64(10000) // LoadRepoDetails = true DontLoadRepoDetails = false - // FoundationType the SF foundation type string - previously was "Foundation", now "Project Group" - FoundationType = "Project Group" - // Lead representing type of user - Lead = "lead" //NoAccount NoAccount = "Individual - No Account" //OrgAssociated stating whether user has user association with another org @@ -402,7 +398,7 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN f["updatedSigningEntityName"] = org.Name } - acsClient := acs_service.GetClient() + acsClient := acsService.GetClient() userClient := v2UserService.GetClient() lfUser, lfErr := userClient.SearchUserByEmail(userEmail) @@ -622,7 +618,7 @@ func (s *service) AssociateContributor(ctx context.Context, companySFID string, return nil, userErr } - acsServiceClient := acs_service.GetClient() + acsServiceClient := acsService.GetClient() log.WithFields(f).Info("Getting roleID for the contributor role") roleID, roleErr := acsServiceClient.GetRoleID("contributor") @@ -662,7 +658,7 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje } // integrate user,acs,org and project services userClient := v2UserService.GetClient() - acServiceClient := acs_service.GetClient() + acServiceClient := acsService.GetClient() orgClient := orgService.GetClient() user, userErr := userClient.SearchUserByEmail(userEmail) @@ -714,7 +710,7 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje } // Log Event - s.eventService.LogEvent( + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.AssignUserRoleScopeType, LfUsername: user.Username, @@ -722,6 +718,8 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje ExternalProjectID: projectID, CompanyModel: v1CompanyModel, ClaGroupModel: projectModel, + CLAGroupID: projectModel.ProjectID, + CLAGroupName: projectModel.ProjectName, UserModel: &v1Models.User{LfUsername: user.Username, UserID: user.ID}, EventData: &events.AssignRoleScopeData{ Role: "contributor", diff --git a/cla-backend-go/v2/gerrits/handlers.go b/cla-backend-go/v2/gerrits/handlers.go index 1dc419ab2..364daef5e 100644 --- a/cla-backend-go/v2/gerrits/handlers.go +++ b/cla-backend-go/v2/gerrits/handlers.go @@ -65,7 +65,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS } // record the event - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.GerritRepositoryDeleted, ProjectID: gerrit.ProjectID, LfUsername: authUser.UserName, @@ -126,7 +126,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS } // record the event - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.GerritRepositoryAdded, ProjectID: params.ClaGroupID, LfUsername: authUser.UserName, diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index d537b0fc4..f4682e6b4 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -9,6 +9,8 @@ import ( "fmt" "strconv" + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/communitybridge/easycla/cla-backend-go/gen/models" @@ -47,24 +49,34 @@ func NewService(githubRepo repositories.Repository, } func (s *eventHandlerService) ProcessRepositoryEvent(event *github.RepositoryEvent) error { + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "v2.github_activity.service.ProcessRepositoryEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } log.Debugf("ProcessRepositoryEvent called for action : %s", *event.Action) if event.Action == nil { return fmt.Errorf("no action found in event payload") } switch *event.Action { case "created": - return s.handleRepositoryAddedAction(event.Sender, event.Repo) + return s.handleRepositoryAddedAction(ctx, event.Sender, event.Repo) case "deleted": - return s.handleRepositoryRemovedAction(event.Sender, event.Repo) + return s.handleRepositoryRemovedAction(ctx, event.Sender, event.Repo) default: - log.Warnf("ProcessRepositoryEvent no handler for action : %s", *event.Action) + log.WithFields(f).Warnf("no handler for action : %s", *event.Action) } return nil } -func (s *eventHandlerService) handleRepositoryAddedAction(sender *github.User, repo *github.Repository) error { +func (s *eventHandlerService) handleRepositoryAddedAction(ctx context.Context, sender *github.User, repo *github.Repository) error { + f := logrus.Fields{ + "functionName": "v2.github_activity.service.handleRepositoryAddedAction", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + if repo.ID == nil || *repo.ID == 0 { return fmt.Errorf("missing repo id") } @@ -76,27 +88,28 @@ func (s *eventHandlerService) handleRepositoryAddedAction(sender *github.User, r if repo.FullName == nil || *repo.FullName == "" { return fmt.Errorf("repo full name missing") } + repoModel, err := s.autoEnableService.CreateAutoEnabledRepository(repo) if err != nil { if errors.Is(err, dynamo_events.ErrAutoEnabledOff) { - log.Warnf("autoEnable is off for this repo : %s can't continue", *repo.FullName) + log.WithFields(f).Warnf("autoEnable is off for this repo : %s can't continue", *repo.FullName) return nil } return err } if err := s.autoEnableService.NotifyCLAManagerForRepos(repoModel.RepositoryProjectID, []*models.GithubRepository{repoModel}); err != nil { - log.Warnf("notifyCLAManager for autoEnabled repo : %s for claGroup : %s failed : %v", repoModel.RepositoryName, repoModel.RepositoryProjectID, err) + log.WithFields(f).Warnf("notifyCLAManager for autoEnabled repo : %s for claGroup : %s failed : %v", repoModel.RepositoryName, repoModel.RepositoryProjectID, err) } if sender == nil || sender.Login == nil || *sender.Login == "" { - log.Warnf("not able to send event empty sender") + log.WithFields(f).Warnf("not able to send event empty sender") return nil } // sending the log event for the added repository log.Debugf("handleRepositoryAddedAction sending RepositoryAdded Event for repo %s", *repo.FullName) - s.eventService.LogEvent(&events.LogEventArgs{ + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryAdded, ProjectID: repoModel.RepositoryProjectID, UserID: *sender.Login, @@ -108,7 +121,12 @@ func (s *eventHandlerService) handleRepositoryAddedAction(sender *github.User, r return nil } -func (s *eventHandlerService) handleRepositoryRemovedAction(sender *github.User, repo *github.Repository) error { +func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, sender *github.User, repo *github.Repository) error { + f := logrus.Fields{ + "functionName": "v2.github_activity.service.handleRepositoryRemovedAction", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + if repo.ID == nil || *repo.ID == 0 { return fmt.Errorf("missing repo id") } @@ -116,19 +134,19 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(sender *github.User, repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { - log.Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) + log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) return nil } return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) } if err := s.githubRepo.DisableRepository(context.Background(), repoModel.RepositoryID); err != nil { - log.Warnf("disabling repo : %s failed : %v", *repo.FullName, err) + log.WithFields(f).Warnf("disabling repo : %s failed : %v", *repo.FullName, err) return err } // sending event for the action - s.eventService.LogEvent(&events.LogEventArgs{ + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, ProjectID: repoModel.RepositoryProjectID, UserID: *sender.Login, @@ -141,6 +159,12 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(sender *github.User, } func (s *eventHandlerService) ProcessInstallationRepositoriesEvent(event *github.InstallationRepositoriesEvent) error { + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "v2.github_activity.service.ProcessInstallationRepositoriesEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + log.Debugf("ProcessInstallationRepositoriesEvent called for action : %s", *event.Action) if event.Action == nil { return fmt.Errorf("no action found in event payload") @@ -148,28 +172,28 @@ func (s *eventHandlerService) ProcessInstallationRepositoriesEvent(event *github switch *event.Action { case "added": if len(event.RepositoriesAdded) == 0 { - log.Warnf("repositories list is empty nothing to add") + log.WithFields(f).Warnf("repositories list is empty nothing to add") return nil } for _, r := range event.RepositoriesAdded { - if err := s.handleRepositoryAddedAction(event.Sender, r); err != nil { + if err := s.handleRepositoryAddedAction(ctx, event.Sender, r); err != nil { // we just log it don't want to stop the whole process at this stage - log.Warnf("adding the repository : %s failed : %v", *r.FullName, err) + log.WithFields(f).Warnf("adding the repository : %s failed : %v", *r.FullName, err) } } case "removed": if len(event.RepositoriesRemoved) == 0 { - log.Warnf("repositories list is empty nothing to remove") + log.WithFields(f).Warnf("repositories list is empty nothing to remove") return nil } for _, r := range event.RepositoriesRemoved { - if err := s.handleRepositoryRemovedAction(event.Sender, r); err != nil { - log.Warnf("removing the repository : %s failed : %v", *r.FullName, err) + if err := s.handleRepositoryRemovedAction(ctx, event.Sender, r); err != nil { + log.WithFields(f).Warnf("removing the repository : %s failed : %v", *r.FullName, err) } } default: - log.Warnf("ProcessInstallationRepositoriesEvent no handler for action : %s", *event.Action) + log.WithFields(f).Warnf("ProcessInstallationRepositoriesEvent no handler for action : %s", *event.Action) } return nil diff --git a/cla-backend-go/v2/github_organizations/handlers.go b/cla-backend-go/v2/github_organizations/handlers.go index ac4f893c5..d3022a639 100644 --- a/cla-backend-go/v2/github_organizations/handlers.go +++ b/cla-backend-go/v2/github_organizations/handlers.go @@ -128,7 +128,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } // Log the event - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ LfUsername: authUser.UserName, EventType: events.GitHubOrganizationAdded, ExternalProjectID: params.ProjectSFID, @@ -173,7 +173,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. return github_organizations.NewDeleteProjectGithubOrganizationBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ LfUsername: authUser.UserName, EventType: events.GitHubOrganizationDeleted, ExternalProjectID: params.ProjectSFID, @@ -227,7 +227,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } // Log the event - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ LfUsername: authUser.UserName, EventType: events.GitHubOrganizationUpdated, ExternalProjectID: params.ProjectSFID, diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 14aae468c..5bafd1a10 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -127,7 +127,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. // Log the events for _, result := range results { - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryAdded, ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), ExternalProjectID: params.ProjectSFID, @@ -202,7 +202,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, ExternalProjectID: params.ProjectSFID, ProjectID: ghRepo.RepositoryProjectID, @@ -338,7 +338,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. // We could extract the parameter values from the branch protection payload to determine if it was added/remove or simply updated // For now, let's just set the updated event log - eventService.LogEvent(&events.LogEventArgs{ + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryBranchProtectionUpdated, ExternalProjectID: params.ProjectSFID, ProjectID: params.ProjectSFID, From b819983d06c3516ea1d4fe18909f3adf6f50120d Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 2 Mar 2021 15:28:32 -0800 Subject: [PATCH 0129/1276] =?UTF-8?q?Added=20Event=20Delete=20Python=20Fun?= =?UTF-8?q?ction=20for=20Database=20Cleanup=20for=20DEV=20and=20S=E2=80=A6?= =?UTF-8?q?=20(#2728)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: David Deal --- cla-backend/cla/models/dynamo_models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 920308633..564b8e981 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -4262,6 +4262,9 @@ def save(self) -> None: self.model.date_modified = datetime.datetime.utcnow() self.model.save() + def delete(self): + self.model.delete() + def load(self, event_id): try: event = self.model.get(str(event_id)) From bb920618bbbbb1082149723bc41c25470708df13 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 2 Mar 2021 15:49:26 -0800 Subject: [PATCH 0130/1276] Updated CodeQL Configuration (#2729) - Resolved CodeQL warning: 1 issue was detected with this workflow: git checkout HEAD^2 is no longer necessary. Please remove this step as Code Scanning recommends analyzing the merge commit for best results. Signed-off-by: David Deal --- .github/workflows/codeql-analysis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c4d7d1cbf..272ef956d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,8 +24,9 @@ jobs: # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + # Note: git checkout HEAD^2 is no longer necessary. Please remove this step as Code Scanning recommends analyzing the merge commit for best results. + #- run: git checkout HEAD^2 + # if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL From 82919cacabd1a9f5d52e7ecc6f5259c068d658d6 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Wed, 3 Mar 2021 17:48:47 +0300 Subject: [PATCH 0131/1276] [#2730,#2731] Feature/Corporate Email updates - Updated email content sent to Users with no lfid for invite flow - Refactored utility functions for sending email - Updated unit tests Signed-off-by: wanyaland --- cla-backend-go/approval_list/service.go | 2 +- cla-backend-go/cla_manager/service.go | 5 ++-- .../emails/approval_list_templates.go | 4 +-- .../emails/cla_manager_templates.go | 9 ++++-- cla-backend-go/emails/uitls.go | 15 ++++++++-- .../emails/v2_cla_manager_templates.go | 29 ++++++++++++------- .../tests/v2_cla_manager_templates_test.go | 20 ++++++++----- cla-backend-go/v2/cla_manager/emails.go | 4 +-- cla-backend-go/v2/cla_manager/service.go | 4 +-- 9 files changed, 60 insertions(+), 32 deletions(-) diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index cf8ad6731..9ba532bac 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -313,7 +313,7 @@ func (s service) sendRequestEmailToRecipient(projectClaGroupRepository projects_ // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Request to Authorize %s for %s", contributorName, projectName) recipients := []string{recipientAddress} - body, err := emails.RenderRequestToAuthorizeTemplate(projectClaGroupRepository, claGroupModel.Version, claGroupModel.ProjectExternalID, + body, err := emails.RenderRequestToAuthorizeTemplate(projectClaGroupRepository, s.projectService, claGroupModel.Version, claGroupModel.ProjectExternalID, emails.RequestToAuthorizeTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: recipientName, diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 7ed134f6e..de658b095 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -338,7 +338,7 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou } // Notify the removed manager - sendRemovedClaManagerEmailToRecipient(s.projectClaRepository, companyModel, claGroupModel, userModel.LfUsername, userModel.LfEmail, claManagers) + s.sendRemovedClaManagerEmailToRecipient(s.projectClaRepository, companyModel, claGroupModel, userModel.LfUsername, userModel.LfEmail, claManagers) // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ @@ -437,7 +437,7 @@ func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGrou } // sendRequestRejectedEmailToRecipient generates and sends an email to the specified recipient -func sendRemovedClaManagerEmailToRecipient(projectsClaGroupRepository projects_cla_groups.Repository, companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, claManagers []models.User) { +func (s *service) sendRemovedClaManagerEmailToRecipient(projectsClaGroupRepository projects_cla_groups.Repository, companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, claManagers []models.User) { companyName := companyModel.CompanyName projectName := claGroupModel.ProjectName @@ -488,6 +488,7 @@ func sendRemovedClaManagerEmailToRecipient(projectsClaGroupRepository projects_c recipients := []string{recipientAddress} body, err := emails.RenderRemovedCLAManagerTemplate( projectsClaGroupRepository, + s.projectService, claGroupModel.Version, recipientName, companyName, diff --git a/cla-backend-go/emails/approval_list_templates.go b/cla-backend-go/emails/approval_list_templates.go index 4b0dc1613..faaf50642 100644 --- a/cla-backend-go/emails/approval_list_templates.go +++ b/cla-backend-go/emails/approval_list_templates.go @@ -86,8 +86,8 @@ repository. This will permit them to begin contributing to {{.Project.ExternalPr ) // RenderRequestToAuthorizeTemplate renders RequestToAuthorizeTemplate -func RenderRequestToAuthorizeTemplate(repository projects_cla_groups.Repository, claGroupVersion string, projecSFID string, params RequestToAuthorizeTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projecSFID, ¶ms.CLAManagerTemplateParams); err != nil { +func RenderRequestToAuthorizeTemplate(repository projects_cla_groups.Repository, projectService project.Service, claGroupVersion string, projecSFID string, params RequestToAuthorizeTemplateParams) (string, error) { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projecSFID, ¶ms.CLAManagerTemplateParams, nil); err != nil { return "", err } diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go index b4d8f318c..ac47c6085 100644 --- a/cla-backend-go/emails/cla_manager_templates.go +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -3,7 +3,10 @@ package emails -import "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" +import ( + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" +) // RemovedCLAManagerTemplateParams is email params for RemovedCLAManagerTemplate type RemovedCLAManagerTemplateParams struct { @@ -29,14 +32,14 @@ const ( ) // RenderRemovedCLAManagerTemplate renders the RemovedCLAManagerTemplate -func RenderRemovedCLAManagerTemplate(repository projects_cla_groups.Repository, claGroupModelVersion, recipientName, companyName, projectSFID string, claManagers []ClaManagerInfoParams) (string, error) { +func RenderRemovedCLAManagerTemplate(repository projects_cla_groups.Repository, projectService project.Service, claGroupModelVersion, recipientName, companyName, projectSFID string, claManagers []ClaManagerInfoParams) (string, error) { params := CLAManagerTemplateParams{ RecipientName: recipientName, CompanyName: companyName, CLAManagers: claManagers, } - err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms) + err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms, nil) if err != nil { return "", err } diff --git a/cla-backend-go/emails/uitls.go b/cla-backend-go/emails/uitls.go index b1bf2af89..2a3f909cf 100644 --- a/cla-backend-go/emails/uitls.go +++ b/cla-backend-go/emails/uitls.go @@ -15,7 +15,7 @@ import ( // PrefillCLAManagerTemplateParamsFromClaGroup fetches data from projects_cla_groups.Repository to prefill some of the fields // of CLAManagerTemplateParams, like childCount and FoundationName and etc -func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups.Repository, projectSFID string, params *CLAManagerTemplateParams) error { +func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params *CLAManagerTemplateParams, corporateConsole *string) error { projectCLAGroup, err := repository.GetClaGroupIDForProject(projectSFID) if err != nil { if errors.Is(err, projects_cla_groups.ErrProjectNotAssociatedWithClaGroup) { @@ -24,6 +24,13 @@ func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups. } return err } + ctx := context.Background() + + //Check if signed at foundationLevel + signedAtFoundationLevel, err := projectService.SignedAtFoundationLevel(ctx, projectCLAGroup.FoundationSFID) + if err != nil { + return err + } params.CLAGroupName = projectCLAGroup.ClaGroupName params.Project = CLAProjectParams{ @@ -31,8 +38,10 @@ func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups. ProjectSFID: projectSFID, FoundationName: projectCLAGroup.FoundationName, FoundationSFID: projectCLAGroup.FoundationSFID, - SignedAtFoundationLevel: false, - CorporateConsole: "", + SignedAtFoundationLevel: signedAtFoundationLevel, + } + if corporateConsole != nil { + params.Project.CorporateConsole = *corporateConsole } projects, err := repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) if err != nil { diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 3457c3d1f..1f26ee664 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -62,7 +62,7 @@ Either you or someone whom to designate from your company can login to this port // RenderV2OrgAdminTemplate renders V2OrgAdminTemplate func RenderV2OrgAdminTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params V2OrgAdminTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms.CLAManagerTemplateParams, nil); err != nil { return "", err } @@ -132,7 +132,7 @@ const (

      • {{.Project.ExternalProjectName}}

      Before the contribution can be accepted, your organization must sign a CLA. -Either you or someone whom to designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.Project.GetProjectFullURL}}

      +Either you or someone whom you designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.Project.GetProjectFullURL}}

      If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

      Please notify the user once CLA setup is complete.

      ` @@ -140,7 +140,7 @@ Either you or someone whom to designate from your company can login to this port // RenderV2CLAManagerDesigneeCorporateTemplate renders V2CLAManagerDesigneeCorporateTemplate func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params V2CLAManagerDesigneeCorporateTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams); err != nil { + if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms.CLAManagerTemplateParams, nil); err != nil { return "", err } @@ -245,6 +245,7 @@ type V2CLAManagerToUserWithNoLFIDTemplateParams struct { CLAManagerTemplateParams RequesterUserName string RequesterEmail string + Projects []CLAProjectParams } const ( @@ -253,25 +254,33 @@ const ( // V2CLAManagerToUserWithNoLFIDTemplate is email template V2CLAManagerToUserWithNoLFIDTemplate = `

      Hello {{.RecipientName}},

      -

      This is a notification email from EasyCLA regarding the Project {{.GetProjectNameOrFoundation}} and CLA Group {{.CLAGroupName}}.

      -

      User {{.RequesterUserName}} ({{.RequesterEmail}}) was trying to add you as a CLA Manager for the Project {{.Project.ExternalProjectName}} but was unable to identify your account details in -the EasyCLA system. In order to become a CLA Manager for the Project {{.Project.ExternalProjectName}}, you will need to accept the invite below. -Once complete, notify the user {{.RequesterUserName}} and they will be able to add you as a CLA Manager.

      -

      Accept Invite

      +

      This is a notification email from EasyCLA regarding the CLA setup and signing process for the organization {{.CompanyName}}.The user {{.RequesterUserName}} ({{.RequesterEmail}}) has identified you as a potential candidate to setup the Corporate CLA for the organization {{.CompanyName }} and the project {{.GetProjectNameOrFoundation}}

      +

      Before the user contribution can be accepted, your organization must sign a Corporate CLA(CCLA).

      +

      Please complete the following steps:

      +
        +
      1. Please click on Accept Invite to create your LF Login.This is used to access the EasyCLA CLA Manager console.
      2. +
      3. After login, you will be redirected to the portal {{.Project.CorporateConsole}} where you can either sign the CLA for the project: {{.Project.GetProjectFullURL}}, or send it to an authorized signatory for your company.
      4. +
      5. After signing the CLA, you will need to add this contributor to the approved list in the CLA Manager console.
      6. +
      7. After adding the contributor, please notify them so that they can complete the contribution process.
      8. +
      + ` ) // RenderV2CLAManagerToUserWithNoLFIDTemplate renders V2CLAManagerToUserWithNoLFIDTemplate -func RenderV2CLAManagerToUserWithNoLFIDTemplate(repository projects_cla_groups.Repository, recipientName, projectName, projectSFID, requesterName, requesterEmail string) (string, error) { +func RenderV2CLAManagerToUserWithNoLFIDTemplate(repository projects_cla_groups.Repository, projectService project.Service, recipientName, projectName, projectSFID, requesterName, requesterEmail, corporateConsole string) (string, error) { params := V2CLAManagerToUserWithNoLFIDTemplateParams{ CLAManagerTemplateParams: CLAManagerTemplateParams{ RecipientName: recipientName, + Project: CLAProjectParams{ + ExternalProjectName: projectName, + }, }, RequesterUserName: requesterName, RequesterEmail: requesterEmail, } - err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectSFID, ¶ms.CLAManagerTemplateParams) + err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms.CLAManagerTemplateParams, &corporateConsole) if err != nil { return "", err } diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 33999a9bf..10292337e 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -191,9 +191,14 @@ func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { params := emails.V2CLAManagerToUserWithNoLFIDTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProjectExternal", + CorporateConsole: "http://CorporateConsole.com", + SignedAtFoundationLevel: false, + ProjectSFID: "ProjectSFID", + FoundationSFID: "FoundationSFID", + }, + CLAGroupName: "JohnsCLAGroupName", + CompanyName: "JohnsCompany", }, RequesterUserName: "RequesterUserNameValue", RequesterEmail: "RequesterEmailValue", @@ -203,8 +208,9 @@ func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the Project JohnsProjectExternal and CLA Group JohnsCLAGroupName") - assert.Contains(t, result, "User RequesterUserNameValue (RequesterEmailValue) was trying") - assert.Contains(t, result, "CLA Manager for the Project JohnsProject") - assert.Contains(t, result, "notify the user RequesterUserNameValue") + assert.Contains(t, result, "regarding the CLA setup and signing process for the organization JohnsCompany") + assert.Contains(t, result, "The user RequesterUserNameValue (RequesterEmailValue) has identified you as a potential candidate to setup the Corporate CLA for the organization JohnsCompany and the project JohnsProjectExternal") + assert.Contains(t, result, "After login, you will be redirected to the portal http://CorporateConsole.com") + assert.Contains(t, result, "After adding the contributor, please notify them") + assert.Contains(t, result, `where you can either sign the CLA for the project: JohnsProjectExternal`) } diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index 18dce0f9e..e4b41deb9 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -275,7 +275,7 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project } // sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID -func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error { +func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role, corporateConsole string) error { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -291,7 +291,7 @@ func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository proj // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", role) - body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(repository, userWithNoLFIDName, projectName, *projectID, requesterUsername, requesterEmail) + body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(repository, projectService, userWithNoLFIDName, projectName, *projectID, requesterUsername, requesterEmail, corporateConsole) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering email : %s failed : %v", emails.V2CLAManagerToUserWithNoLFIDTemplateName, err) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 6a95c6b95..b6596b0bd 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -97,7 +97,7 @@ type Service interface { SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectIDs []string, foundationSFID, role string, corporateConsoleV2URL string) error - SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role string) error + SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role, corporateConsole string) error } // NewService returns instance of CLA Manager service @@ -755,7 +755,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool msg := fmt.Sprintf("User: %s does not have an LF Login", userEmail) log.WithFields(f).Warn(msg) // Send email - sendEmailErr := s.SendEmailToUserWithNoLFID(ctx, s.projectCGRepo, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole) + sendEmailErr := s.SendEmailToUserWithNoLFID(ctx, s.projectCGRepo, s.projectService, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole, corporateConsole) if sendEmailErr != nil { log.WithFields(f).Warnf("Error sending email: %+v", sendEmailErr) return nil, sendEmailErr From 6bd986d6e42a6ba0912985f5617f36d9924ac9d9 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 3 Mar 2021 19:22:31 +0200 Subject: [PATCH 0132/1276] [#2734]use project name instead of cla group name (#2735) Signed-off-by: makkalot --- cla-backend-go/cla_manager/service.go | 15 ++++++++++++++- cla-backend-go/emails/cla_manager_templates.go | 2 +- .../emails/cla_manager_templates_test.go | 8 ++++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index de658b095..a1e38b65d 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -408,6 +410,16 @@ func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGrou companyName := companyModel.CompanyName projectName := claGroupModel.ProjectName + // we want the Project Name in the email not the cla group name + ps := v2ProjectService.GetClient() + projectSF, projectErr := ps.GetProject(claGroupModel.ProjectExternalID) + if projectErr != nil { + msg := fmt.Sprintf("Project service lookup error for SFID: %s, error : %+v", + claGroupModel.ProjectExternalID, projectErr) + log.Warn(msg) + return + } + // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Added Notice for %s", projectName) recipients := []string{recipientAddress} @@ -415,8 +427,9 @@ func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGrou emails.ClaManagerAddedToCLAManagersTemplate, emails.ClaManagerAddedToCLAManagersTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + CLAGroupName: projectName, RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, + Project: emails.CLAProjectParams{ExternalProjectName: projectSF.Name}, CompanyName: companyName, }, Name: name, diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go index ac47c6085..fddde4530 100644 --- a/cla-backend-go/emails/cla_manager_templates.go +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -200,7 +200,7 @@ const ( // ClaManagerAddedToCLAManagersTemplate is email template for ClaManagerAddedToCLAManagersTemplate = `

      Hello {{.RecipientName}},

      -

      This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.

      +

      This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}} associated with the CLA Group {{.CLAGroupName}}.

      The following user has been added as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that they can now maintain the list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of company’s CLA Managers for {{.Project.ExternalProjectName}}.

      diff --git a/cla-backend-go/emails/cla_manager_templates_test.go b/cla-backend-go/emails/cla_manager_templates_test.go index 913882052..c23001ee0 100644 --- a/cla-backend-go/emails/cla_manager_templates_test.go +++ b/cla-backend-go/emails/cla_manager_templates_test.go @@ -203,10 +203,10 @@ func TestClaManagerAddedToCLAManagersTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the project JohnsProject") - assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") - assert.Contains(t, result, "contribute to JohnsProject") - assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "regarding the project JohnsProjectExternal associated with the CLA Group JohnsProject") + assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProjectExternal") + assert.Contains(t, result, "contribute to JohnsProjectExternal") + assert.Contains(t, result, "CLA Managers for JohnsProjectExternal") assert.Contains(t, result, "
    • John (john@example.com)
    • ") } From 6e80db7db873d93b726bab3ead23f150dc07d63e Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 3 Mar 2021 21:14:55 +0300 Subject: [PATCH 0133/1276] [#2733,#2705] Feature/Email content (#2737) - Updated approval and signatory emails Signed-off-by: wanyaland --- cla-backend-go/signatures/service.go | 4 ++-- cla-backend/cla/models/docusign_models.py | 5 ++--- cla-backend/cla/tests/unit/test_docusign_models.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index ce6d2a9f1..86134c8ac 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -830,13 +830,13 @@ func sendRequestAccessEmailToContributorRecipient(authUser *auth.User, companyMo body := fmt.Sprintf(`

      Hello %s,

      This is a notification email from EasyCLA regarding the project %s.

      -

      You have been %s %s the Approval List of %s for %s by CLA Manager %s. This means that %s on behalf of %s.

      +

      You have been %s %s the Approval List of %s for %s by CLA Manager %s. This means that %s.

      If you had previously submitted a pull request to EasyCLA Test Group that had failed, you can now go back to it and follow the link to verify with your organization.

      %s %s`, recipientName, projectName, addRemove, toFrom, - companyName, projectName, authUser.UserName, authorizedString, projectName, + companyName, projectName, authUser.UserName, authorizedString, utils.GetEmailHelpContent(claGroupModel.Version == utils.V2), utils.GetEmailSignOffContent()) err := utils.SendEmail(subject, body, recipients) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 17a57d8bf..ad9c5f1bd 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -1289,8 +1289,7 @@ def populate_sign_url(self, signature, callback_url=None, cla_manager_email=cla_manager_email, company_name=company_name, project_version=project.get_version(), - project_names=project_names)) - + 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, @@ -2248,7 +2247,7 @@ def cla_signatory_email_content(params: ClaSignatoryEmailParams) -> (str, str): email_subject = f'EasyCLA: CLA Signature Request for {params.cla_group_name}' email_body = f'

      Hello {params.signatory_name},

      ' - email_body += f'

      This is a notification email from EasyCLA regarding the project(s) {project_names_list} associated with the CLA Group {params.cla_group_name}. {params.cla_manager_name} has designated you as being an authorized signatory for the organization {params.company_name}. In order for employees of your company to contribute to any of the above project(s), they must do so under a Contributor License Agreement signed by someone with authority n behalf of your company.

      ' + email_body += f'

      This is a notification email from EasyCLA regarding the project(s) {project_names_list} associated with the CLA Group {params.cla_group_name}. {params.cla_manager_name} has designated you as an authorized signatory for the organization {params.company_name}. In order for employees of your company to contribute to any of the above project(s), they must do so under a Contributor License Agreement signed by someone with authority n behalf of your company.

      ' email_body += f'

      After you sign, {params.cla_manager_name} (as the initial CLA Manager for your company) will be able to maintain the list of specific employees authorized to contribute to the project(s) under this signed CLA.

      ' email_body += f'

      If you are authorized to sign on your company’s behalf, and if you approve {params.cla_manager_name} as your initial CLA Manager, please review the document and sign the CLA. If you have questions, or if you are not an authorized signatory of this company, please contact the requester at {params.cla_manager_email}.

      ' email_body = append_email_help_sign_off_content(email_body, params.project_version) diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index 34a0bed2d..fc95cefbd 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -877,7 +877,7 @@ def test_cla_signatory_email_content(): assert "

      Hello signatory_name_value,

      " in email_body assert "EasyCLA regarding the project(s) project1, project2 associated" in email_body assert "with the CLA Group cla_group_name_value" in email_body - assert "john has designated you as being an authorized signatory" in email_body + assert "john has designated you as an authorized signatory" in email_body assert "signatory for the organization IBM" in email_body assert "

      After you sign, john (as the initial CLA Manager for your company)" in email_body assert "and if you approve john as your initial CLA Manager" in email_body From fed1462ed23c3e4d2ed60fbff5b6905f2d3098ef Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 3 Mar 2021 13:23:23 -0800 Subject: [PATCH 0134/1276] Resolved CLA Group Project Document TimeFormat Issue (#2738) - resolves issue with OpenVDB ICLA/CCLA template documents not being loaded in the PCC - Added additional time format parsing options, added/verified with unit tests Signed-off-by: David Deal --- cla-backend-go/project/helpers.go | 4 ++-- cla-backend-go/tests/utils_test.go | 2 ++ cla-backend-go/utils/utils.go | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/project/helpers.go b/cla-backend-go/project/helpers.go index 0d1491860..06a63329a 100644 --- a/cla-backend-go/project/helpers.go +++ b/cla-backend-go/project/helpers.go @@ -69,7 +69,7 @@ func buildCLAGroupDocumentModels(dbDocumentModels []DBProjectDocumentModel) []mo func (s service) fillRepoInfo(ctx context.Context, project *models.ClaGroup) { f := logrus.Fields{ - "functionName": "fillRepoInfo", + "functionName": "v1.project.helpers.fillRepoInfo", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } @@ -108,7 +108,7 @@ func (s service) fillRepoInfo(ctx context.Context, project *models.ClaGroup) { // GetCurrentDocument returns the current document based on the version and date/time func GetCurrentDocument(ctx context.Context, docs []models.ClaGroupDocument) (models.ClaGroupDocument, error) { f := logrus.Fields{ - "functionName": "GetCurrentDocument", + "functionName": "v1.project.helpers.GetCurrentDocument", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } var currentDoc models.ClaGroupDocument diff --git a/cla-backend-go/tests/utils_test.go b/cla-backend-go/tests/utils_test.go index 53abcb9ac..94a8c1578 100644 --- a/cla-backend-go/tests/utils_test.go +++ b/cla-backend-go/tests/utils_test.go @@ -89,6 +89,8 @@ func TestParseDateTimeMS(t *testing.T) { "2020-03-27T15:04:05.000000+0000", "2016-12-02T05:14:05.000000+0800", "2006-08-31T10:24:05.000000-1000", + "2019-04-15T20:30:12.13589", + "2019-04-15T20:30:19.321645", } for _, dateTimeStr := range validInput { diff --git a/cla-backend-go/utils/utils.go b/cla-backend-go/utils/utils.go index 41dec29cf..b4f84a098 100644 --- a/cla-backend-go/utils/utils.go +++ b/cla-backend-go/utils/utils.go @@ -63,6 +63,8 @@ func ParseDateTime(dateTimeStr string) (time.Time, error) { "2006-01-02T15:04:05Z-07:00", "2006-01-02T15:04:05-07:00", "2006-01-02T15:04:05.000000-0700", + "2006-01-02T15:04:05.00000", + "2006-01-02T15:04:05.000000", time.RFC850, time.RFC1123, time.RFC1123Z, From 5571873aa01806ebf8408c0aaff8958526099e2b Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 4 Mar 2021 19:11:14 +0300 Subject: [PATCH 0135/1276] [#2739] Feature/Email|Username Labels (#2741) - Added labels to User emails speficying type (GH, LF|EasyCLA) Signed-off-by: wanyaland --- .../emails/v2_cla_manager_templates.go | 15 ++++-- .../tests/v2_cla_manager_templates_test.go | 34 +++++++++--- cla-backend-go/utils/constants.go | 12 +++++ cla-backend-go/v2/cla_manager/emails.go | 20 ++++--- cla-backend-go/v2/cla_manager/service.go | 52 ++++++++++++------- 5 files changed, 92 insertions(+), 41 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 1f26ee664..fdabd7460 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -12,6 +12,14 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" ) +//Contributor representing GH user details +type Contributor struct { + Email string + Username string + EmailLabel string + UsernameLabel string +} + // V2ContributorApprovalRequestTemplateParams is email template params for V2ContributorApprovalRequestTemplate type V2ContributorApprovalRequestTemplateParams struct { CLAManagerTemplateParams @@ -159,8 +167,7 @@ func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups. type V2ToCLAManagerDesigneeTemplateParams struct { RecipientName string Projects []CLAProjectParams - ContributorEmail string - ContributorName string + Contributor Contributor CorporateConsole string CompanyName string } @@ -186,7 +193,7 @@ const ( V2ToCLAManagerDesigneeTemplate = `

      Hello {{.RecipientName}},

      This is a notification email from EasyCLA regarding the project(s): {{.GetProjectsOrProject}}.

      -

      We received a request from {{.ContributorName}} ({{.ContributorEmail}}) to contribute to the above projects on behalf of your organization.

      +

      We received a request from {{.Contributor.UsernameLabel}}: {{.Contributor.Username}} ({{.Contributor.EmailLabel}}: {{.Contributor.Email}}) to contribute to the above projects on behalf of your organization.

      Before the user contribution can be accepted, your organization must sign a Corporate CLA (CCLA).The requester has stated that you would be the initial CLA Manager for this CCLA, to coordinate the signing of the CCLA and then manage the list of employees who are authorized to contribute.

      Please complete the following steps:

        @@ -226,7 +233,7 @@ const ( V2DesigneeToUserWithNoLFIDTemplate = `

        Hello {{.RecipientName}},

        This is a notification email from EasyCLA regarding the project(s): {{.GetProjectsOrProject}}.

        -

        We received a request from {{.ContributorName}} ({{.ContributorEmail}}) to contribute to any of the above projects on behalf of your +

        We received a request from {{.Contributor.UsernameLabel}}: {{.Contributor.Username}} ({{.Contributor.EmailLabel}}: {{.Contributor.Email}}) to contribute to any of the above projects on behalf of your organization {{.CompanyName}}.

        Before the user contribution can be accepted, your organization must sign a Corporate CLA(CCLA). The requester has stated that you would be the initial CLA Manager for this CCLA, to coordinate the signing of the CCLA and then manage the list of employees who are authorized to contribute.

        diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 10292337e..8b1b39965 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -139,8 +139,12 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, }, - ContributorEmail: "ContributorIDValue", - ContributorName: "ContributorNameValue", + Contributor: emails.Contributor{ + Email: "ContributorEmailValue", + Username: "ContributorNameValue", + EmailLabel: utils.EmailLabel, + UsernameLabel: utils.UserLabel, + }, CorporateConsole: "http://CorporateConsole.com", } @@ -149,18 +153,21 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s): Project1, Project2") - assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue)") + assert.Contains(t, result, "from Username: ContributorNameValue (Email Address: ContributorEmailValue)") assert.Contains(t, result, `CLA for any of the project(s): Project1,Project2`) params.Projects = []emails.CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, } + params.Contributor.EmailLabel = utils.GitHubEmailLabel + params.Contributor.UsernameLabel = utils.GitHubUserLabel + result, err = emails.RenderTemplate(utils.V1, emails.V2ToCLAManagerDesigneeTemplateName, emails.V2ToCLAManagerDesigneeTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the project(s): Project1") - assert.Contains(t, result, "from ContributorNameValue (ContributorIDValue)") + assert.Contains(t, result, "from GitHub Username: ContributorNameValue (GitHub Email Address: ContributorEmailValue)") assert.Contains(t, result, `CLA for any of the project(s): Project1`) } @@ -172,8 +179,12 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "https://corporate.dev.lfcla.com"}, {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "https://corporate.dev.lfcla.com"}, }, - ContributorEmail: "ContributorIDValue", - ContributorName: "ContributorNameValue", + Contributor: emails.Contributor{ + Email: "ContributorEmail", + Username: "ContributorUsername", + EmailLabel: utils.EmailLabel, + UsernameLabel: utils.UserLabel, + }, CorporateConsole: "https://corporate.dev.lfcla.com", } @@ -181,10 +192,19 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager,") - assert.Contains(t, result, "We received a request from ContributorNameValue (ContributorIDValue)") + assert.Contains(t, result, "We received a request from Username: ContributorUsername (Email Address: ContributorEmail)") assert.Contains(t, result, "After login, you will be redirected to the portal https://corporate.dev.lfcla.com ") assert.Contains(t, result, `where you can either sign the CLA for any of the project(s): Project1`) assert.Contains(t, result, "or send it to an authorized signatory for your company.") + + params.Contributor.EmailLabel = utils.GitHubEmailLabel + params.Contributor.UsernameLabel = utils.GitHubUserLabel + + result, err = emails.RenderTemplate(utils.V1, emails.V2DesigneeToUserWithNoLFIDTemplateName, emails.V2DesigneeToUserWithNoLFIDTemplate, + params) + + assert.NoError(t, err) + assert.Contains(t, result, "We received a request from GitHub Username: ContributorUsername (GitHub Email Address: ContributorEmail)") } func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 98b23103b..c219dca8e 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -134,3 +134,15 @@ const GithubRepoNotFound = "GitHub repository not found" //GithubRepoExists is a string that indicates the GitHub repository already exists const GithubRepoExists = "GitHub repository exists" + +//GitHubEmailLabel represents the GH Email label used for email +const GitHubEmailLabel = "GitHub Email Address" + +//GitHubUserLabel represents the GH username Label used for email +const GitHubUserLabel = "GitHub Username" + +//EmailLabel represents LF/EasyCLA Email address +const EmailLabel = "Email Address" + +//UserLabel represents the LF/EasyCLA username +const UserLabel = "Username" diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index e4b41deb9..99ce1bdf2 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -188,7 +188,7 @@ func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, re } } -func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorEmail string, contributorName string) { +func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorModel emails.Contributor) { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToCLAManagerDesignee", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -197,18 +197,17 @@ func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository "projectNames": strings.Join(projectNames, ","), "designeeEmail": designeeEmail, "designeeName": designeeName, - "contributorEmail": contributorEmail, - "contributorName": contributorName, + "contributorEmail": contributorModel.Email, + "contributorName": contributorModel.Username, } subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", - companyName, contributorEmail) + companyName, contributorModel.Email) recipients := []string{designeeEmail} body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ RecipientName: designeeName, - ContributorEmail: contributorEmail, - ContributorName: contributorName, + Contributor: contributorModel, CorporateConsole: corporateConsole, }, emails.V2ToCLAManagerDesigneeTemplate, emails.V2ToCLAManagerDesigneeTemplateName) @@ -224,7 +223,7 @@ func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository } } -func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectSFIDs []string, foundationSFID, role string, corporateConsoleV2URL string) error { +func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectSFIDs []string, foundationSFID, role string, corporateConsoleV2URL string, contributorModel emails.Contributor) error { f := logrus.Fields{ "functionName": "cla_manager.service.SendDesigneeEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -234,8 +233,8 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project "projectNames": strings.Join(projectNames, ","), "role": role, "corporateConsoleV2URL": corporateConsoleV2URL, - "requesterUsername": requesterUsername, - "requesterEmail": requesterEmail, + "requesterUsername": contributorModel.Username, + "requesterEmail": contributorModel.Email, } subject := "EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager" @@ -243,8 +242,7 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ RecipientName: userWithNoLFIDName, - ContributorEmail: requesterEmail, - ContributorName: requesterUsername, + Contributor: contributorModel, CorporateConsole: corporateConsoleV2URL, }, emails.V2DesigneeToUserWithNoLFIDTemplate, emails.V2DesigneeToUserWithNoLFIDTemplateName) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index b6596b0bd..ce8acf7d8 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -16,6 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/LF-Engineering/lfx-kit/auth" + "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -95,8 +96,8 @@ type Service interface { SendEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) - SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorID string, contributorName string) - SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectIDs []string, foundationSFID, role string, corporateConsoleV2URL string) error + SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorModel emails.Contributor) + SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectIDs []string, foundationSFID, role string, corporateConsoleV2URL string, contributorModel emails.Contributor) error SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role, corporateConsole string) error } @@ -983,19 +984,21 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com // Get suggested CLA Manager user details user, userErr := userService.SearchUserByEmail(userEmail) if userErr != nil || (user != nil && user.Username == "") { - contributorEmail, contributorUsername := "", "" + var contributorModel emails.Contributor msg := fmt.Sprintf("UserEmail: %s has no LF Login and has been sent an invite email to create an account , error: %+v", userEmail, userErr) log.Warn(msg) // Get username and useremail details for contributor if contributor.LFEmail != "" && contributor.UserName != "" { - contributorEmail = contributor.LFEmail - contributorUsername = contributor.UserName + contributorModel.Email = contributor.LFEmail + contributorModel.Username = contributor.LFUsername + contributorModel.EmailLabel = utils.EmailLabel + contributorModel.UsernameLabel = utils.UserLabel } else { - contributorUsername, contributorEmail = getContributorPublicEmail(contributor) + contributorModel = getContributorPublicEmail(contributor) } - sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectService, s.projectCGRepo, contributorUsername, contributorEmail, name, userEmail, organization.Name, organization.ID, projectSFs, projectSFIDs, foundationSFID, "cla-manager-designee", LfxPortalURL) + sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectService, s.projectCGRepo, name, userEmail, organization.Name, organization.ID, projectSFs, projectSFIDs, foundationSFID, "cla-manager-designee", LfxPortalURL, contributorModel) if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) @@ -1030,13 +1033,20 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com log.Debugf("Sending Email to CLA Manager Designee email: %s ", userEmail) + var contributorModel emails.Contributor + if contributor.LFUsername != "" && contributor.LFEmail != "" && len(projectSFs) > 0 { - s.SendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributor.LFEmail, contributor.LFUsername) + contributorModel.Email = contributor.LFEmail + contributorModel.Username = contributor.LFUsername + contributorModel.EmailLabel = utils.EmailLabel + contributorModel.UsernameLabel = utils.UserLabel + } else { - contributorUserName, contributorEmail := getContributorPublicEmail(contributor) - s.SendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributorUserName, contributorEmail) + contributorModel = getContributorPublicEmail(contributor) } + s.SendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributorModel) + log.Debugf("CLA Manager designee created : %+v", designeeScopes) return designeeScopes, nil @@ -1152,29 +1162,33 @@ func getBestUserName(model *v1Models.User) string { return "User Name Unknown" } -func getContributorPublicEmail(model *v1User.User) (string, string) { - var contributorUserName, contributorEmail string +func getContributorPublicEmail(model *v1User.User) emails.Contributor { + var contributorModel emails.Contributor if model.LFUsername != "" { - contributorUserName = model.LFUsername + contributorModel.Username = model.LFUsername + contributorModel.UsernameLabel = utils.UserLabel } if model.LFEmail != "" { - contributorEmail = model.LFEmail + contributorModel.Email = model.LFEmail + contributorModel.EmailLabel = utils.EmailLabel } - if contributorUserName == "" { - contributorUserName = model.UserGithubUsername + if contributorModel.Username == "" { + contributorModel.Username = model.UserGithubUsername + contributorModel.UsernameLabel = utils.GitHubUserLabel } - if contributorEmail == "" && len(model.UserEmails) > 0 { + if contributorModel.Email == "" && len(model.UserEmails) > 0 { for _, email := range model.UserEmails { if strings.Contains(email, "users.noreply.github.com") { continue } - contributorEmail = email + contributorModel.Email = email + contributorModel.EmailLabel = utils.GitHubEmailLabel } } - return contributorUserName, contributorEmail + return contributorModel } // getFormattedUserDetails is a helper function to extract what information we can from the user record for purposes of displaying the user's information From 2f7c89770a7a895a45430575962d3d99bc6c7292 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 4 Mar 2021 09:57:15 -0800 Subject: [PATCH 0136/1276] Resolved IndexError When User Not Found in User Service (#2742) - Added defensive checks when the logged in user is not defined in the User Service. Now we handle the situation. - Resolved logging error with trailing comma - Added return types for User service helper functions -> returns List[dict] type helpers for IDE Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 76 ++++++++++++----------- cla-backend/cla/user_service.py | 12 ++-- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index ad9c5f1bd..23f0480ab 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -936,7 +936,7 @@ def request_corporate_signature(self, auth_user: object, f'project id: {project_id}, ' f'company id: {company_id}, ' f'signing entity name: {signing_entity_name}, ' - f'send email: {send_as_email}, ', + f'send email: {send_as_email}, ' f'signatory name: {signatory_name}, ' f'signatory email: {signatory_email}, ' ) @@ -951,7 +951,7 @@ def request_corporate_signature(self, auth_user: object, return {'errors': {'company_id': 'request_corporate_signature - company_id is empty'}} if auth_user is None: - return {'errors': {'user_error': 'request_corporate_signature - auth_user is empty'}} + return {'errors': {'user_error': 'request_corporate_signature - auth_user object is empty'}} if auth_user.username is None: return {'errors': {'user_error': 'request_corporate_signature - auth_user.username is empty'}} @@ -1016,43 +1016,49 @@ def request_corporate_signature(self, auth_user: object, # unlikely we'll have more than one cla_manager_user = users_list[0] - # Add some defensive checks to ensure the Name and Email are set for the CLA Manager + # Add some defensive checks to ensure the Name and Email are set for the CLA Manager - lookup the values + # from the platform user service - use this as the source of truth us = UserService cla.log.debug(f'{fn} - Loading user by username: {auth_user.username} from the platform user service...') platform_users = us.get_users_by_username(auth_user.username) - if platform_users is None: - cla.log.warning(f'{fn} - Unable to load auth_user by username: {auth_user.username}. ' - 'Returning an error response') - return {'errors': {'user_error': 'user does not exist'}} - platform_user = platform_users[0] - - if cla_manager_user.get_user_name() is None: - # Lookup user in the platform user service... - cla.log.warning(f'{fn} - Loaded CLA Manager by username: {auth_user.username}, but ' - 'the user_name is missing from profile - required for DocuSign.') - user_name = platform_user.get('Name', None) - if user_name: - cla.log.debug(f'{fn} - user_name: {user_name} update for cla_manager : {auth_user.username}...') - cla_manager_user.set_user_name(user_name) - cla_manager_user.save() - else: - return {'errors': {'user_error': 'user does not have user_name'}} + if platform_users: + platform_user = platform_users[0] - if cla_manager_user.get_user_email() is None: - cla.log.warning(f'{fn} - Loaded CLA Manager by username: {auth_user.username}, but ' - 'the user email is missing from profile - required for DocuSign.') - # Add the emails - platform_user_emails = platform_user.get('Emails', None) - if len(platform_user_emails) > 0: - email_list = [] - for platform_email in platform_user_emails: - email_list.append(platform_email['EmailAddress']) - if platform_email['IsPrimary']: - cla_manager_user.set_lf_email(platform_email['EmailAddress']) - cla_manager_user.set_user_emails(email_list) - cla_manager_user.save() - else: - return {'errors': {'user_error': 'user does not have an email'}} + if cla_manager_user.get_user_name() is None: + # Lookup user in the platform user service... + cla.log.warning(f'{fn} - Loaded CLA Manager by username: {auth_user.username}, but ' + 'the user_name is missing from profile - required for DocuSign.') + user_name = platform_user.get('Name', None) + if user_name: + if cla_manager_user.get_user_name() != user_name: + cla.log.debug(f'{fn} - user_name: {user_name} update for cla_manager : {auth_user.username}...') + cla_manager_user.set_user_name(user_name) + cla_manager_user.save() + else: + cla.log.debug(f'{fn} - user_name values match - no need to update the local record') + else: + cla.log.warning(f'{fn} - Unable to locate the user\'s name from the platform user service model. ' + 'Unable to update the local user record.') + + if cla_manager_user.get_user_email() is None: + cla.log.warning(f'{fn} - Loaded CLA Manager by username: {auth_user.username}, but ' + 'the user email is missing from profile - required for DocuSign.') + # Add the emails + platform_user_emails = platform_user.get('Emails', None) + if len(platform_user_emails) > 0: + email_list = [] + for platform_email in platform_user_emails: + email_list.append(platform_email['EmailAddress']) + if platform_email['IsPrimary']: + cla_manager_user.set_lf_email(platform_email['EmailAddress']) + cla_manager_user.set_user_emails(email_list) + cla_manager_user.save() + else: + cla.log.warning(f'{fn} - Unable to locate the user\'s email from the platform user service model. ' + 'Unable to update the local user record.') + else: + cla.log.warning(f'{fn} - Unable to load auth_user from the platform user service ' + f'by username: {auth_user.username}. Unable to update our local user record.') cla.log.debug(f'{fn} - Loaded user {cla_manager_user} - this is our CLA Manager') # Ensure the project exists diff --git a/cla-backend/cla/user_service.py b/cla-backend/cla/user_service.py index 6c39e1a48..da8c42a34 100644 --- a/cla-backend/cla/user_service.py +++ b/cla-backend/cla/user_service.py @@ -52,7 +52,7 @@ def get_user_by_sf_id(self, sf_user_id: str): log.warning(msg) return None - def _get_users_by_key_value(self, key: str, value: str): + def _get_users_by_key_value(self, key: str, value: str) -> List[dict]: """ Queries the platform user service for the specified criteria. The result will return summary information for the users as a @@ -65,7 +65,7 @@ def _get_users_by_key_value(self, key: str, value: str): 'accept': 'application/json' } - users = [] + users: List[dict] = [] offset = 0 pagesize = 1000 @@ -91,16 +91,16 @@ def _get_users_by_key_value(self, key: str, value: str): log.debug(f'{fn} - total users : {len(users)}') return users - def get_users_by_username(self, user_name: str): + def get_users_by_username(self, user_name: str) -> List[dict]: return self._get_users_by_key_value("username", user_name) - def get_users_by_firstname(self, first_name: str): + def get_users_by_firstname(self, first_name: str) -> List[dict]: return self._get_users_by_key_value("firstname", first_name) - def get_users_by_lastname(self, last_name: str): + def get_users_by_lastname(self, last_name: str) -> List[dict]: return self._get_users_by_key_value("lastname", last_name) - def get_users_by_email(self, email: str): + def get_users_by_email(self, email: str) -> List[dict]: return self._get_users_by_key_value("email", email) def has_role(self, username: str, role: str, organization_id: str, cla_group_id: str) -> bool: From 6dba66a51c0d279f33f46cfe69151c8fd511cadd Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 4 Mar 2021 12:29:35 -0800 Subject: [PATCH 0137/1276] Enabled Landing Page PROD v2 Links (#2743) Signed-off-by: David Deal --- .../cla-console-section.component.html | 4 ++-- .../cla-console-section.component.scss | 16 ++++++++-------- .../cla-console-section.component.ts | 10 +--------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index 03bb868a3..65907e703 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -80,8 +80,8 @@
        - EasyCLA Version 2
        + [ngClass]="{'active':selectedVersion==='2'}">EasyCLA Version 2 +
    diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss index 18ef96d76..7d7127705 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.scss @@ -6,7 +6,7 @@ .section { border: 1px solid #8492a6; border-radius: 6px; - font-family: "Source Sans Pro"; + font-family: "Source Sans Pro", sans-serif; padding-top: 20px; .head { min-height: 90px; @@ -39,7 +39,7 @@ .body { color: #333333; - font-family: "Source Sans Pro"; + font-family: "Source Sans Pro", sans-serif; font-size: 12px; text-align: left; min-height: 130px; @@ -58,7 +58,7 @@ .title { color: #000000; - font-family: "Open Sans"; + font-family: "Open Sans", sans-serif; font-size: 14px; text-align: center; padding: 15px 0; @@ -68,7 +68,7 @@ margin-bottom: 15px; .button { color: #ffffff; - font-family: "Source Sans Pro bold"; + font-family: "Source Sans Pro bold", sans-serif; font-size: 14px; font-weight: bold; text-align: center; @@ -94,7 +94,7 @@ .dialog { .dialog-header { color: #8492a6; - font-family: "Poppins"; + font-family: "Poppins", sans-serif; font-size: 18px; font-weight: bold; text-align: center; @@ -111,7 +111,7 @@ .message { color: #333333; - font-family: "Source Sans Pro"; + font-family: "Source Sans Pro", sans-serif; font-size: 16px; text-align: center; margin-bottom: 15px; @@ -122,7 +122,7 @@ cursor: pointer; padding: 5px; color: #000000; - font-family: "Source Sans Pro"; + font-family: "Source Sans Pro", sans-serif; font-size: 14px; font-weight: bold; text-align: center; @@ -153,7 +153,7 @@ padding: 5px; cursor: pointer; color: #ffffff; - font-family: "Source Sans Pro"; + font-family: "Source Sans Pro", sans-serif; font-size: 14px; font-weight: bold; text-align: center; diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index b7d54232b..2eb973ede 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -105,15 +105,7 @@ export class ClaConsoleSectionComponent implements OnInit { } onClickVersion(version) { - if (version == '1') { - this.selectedVersion = version; - } else if (version == '2') { - if (environment.environment !== 'prod') { - this.selectedVersion = version; - } else { - this.selectedVersion = ''; - } - } + this.selectedVersion = version; } onClickVersionProceed() { From b63f514bd311f7fb9b2063ae4b2fe21883663f27 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 5 Mar 2021 01:13:24 +0300 Subject: [PATCH 0138/1276] [#2736] Feature/Approval List Request (#2744) - Refactored swagger spec allowing claGroupID in place of CLAGroup Name - Updated email content sent to CLA Mgrs with claGroup associated Projects - Updated unit tests Signed-off-by: wanyaland --- .../emails/v2_cla_manager_templates.go | 32 +++++++++++++++++-- cla-backend-go/swagger/cla.v2.yaml | 7 ++-- .../tests/v2_cla_manager_templates_test.go | 9 ++++-- cla-backend-go/v2/cla_manager/emails.go | 24 ++++++-------- cla-backend-go/v2/cla_manager/handlers.go | 4 +-- cla-backend-go/v2/cla_manager/service.go | 19 ++++++++--- 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index fdabd7460..e48df93f4 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -26,6 +26,21 @@ type V2ContributorApprovalRequestTemplateParams struct { SigningEntityName string UserDetails string CorporateConsoleV2URL string + Projects []CLAProjectParams +} + +// GetProjectsOrProject returns the single Project or comma separated projects if more than one +func (p V2ContributorApprovalRequestTemplateParams) GetProjectsOrProject() string { + if len(p.Projects) == 1 { + return p.Projects[0].ExternalProjectName + } + + var projectNames []string + for _, p := range p.Projects { + projectNames = append(projectNames, p.ExternalProjectName) + } + + return strings.Join(projectNames, ", ") } const ( @@ -35,13 +50,26 @@ const ( V2ContributorApprovalRequestTemplate = `

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

    -

    The following contributor would like to submit a contribution to the CLA Group {{.CLAGroupName}} and is requesting to be approved as a contributor for your organization:

    +

    The following contributor would like to submit a contribution to the projects(s): {{.GetProjectsOrProject}} and is requesting to be approved as a contributor for your organization:

    {{.UserDetails}}

    -

    Approval can be done at {{.CorporateConsoleV2URL}}

    +

    Approval can be done at {{.CorporateConsoleV2URL}}. Visit any of the project(s):{{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}} and add the contributor to the approved list.

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

    ` ) +// RenderV2ContributorApprovalRequestTemplate renders V2ContributorApprovalRequestTemplate +func RenderV2ContributorApprovalRequestTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ContributorApprovalRequestTemplateParams) (string, error) { + + projectParams, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsoleV2URL) + if err != nil { + return "", err + } + + params.Projects = projectParams + + return RenderTemplate(utils.V2, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, params) +} + // V2OrgAdminTemplateParams is email params for V2OrgAdminTemplate type V2OrgAdminTemplateParams struct { CLAManagerTemplateParams diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 70b97d040..0cdb4e9ce 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4554,8 +4554,11 @@ definitions: $ref: './common/properties/company-name.yaml' signingEntityName: $ref: './common/properties/company-signing-entity-name.yaml' - claGroupName: - $ref: './common/properties/cla-group-name.yaml' + cla_group_id: + title: CLA Group ID + description: The CLA Group ID + $ref: './common/properties/internal-id.yaml' + x-omitempty: false notify-cla-manager: type: object diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 8b1b39965..d72c4869f 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -17,9 +17,12 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ RecipientName: "JohnsClaManager", Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProject"}, - CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, + Projects: []emails.CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, UserDetails: "UserDetailsValue", CorporateConsoleV2URL: "http://CorporateConsoleV2URL.com", } @@ -29,7 +32,7 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the organization JohnsCompany") - assert.Contains(t, result, "contribution to the CLA Group JohnsCLAGroupName") + assert.Contains(t, result, "The following contributor would like to submit a contribution to the projects(s): Project1, Project2") assert.Contains(t, result, "UserDetailsValue") assert.Contains(t, result, "Approval can be done at http://CorporateConsoleV2URL.com") @@ -40,7 +43,7 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") assert.Contains(t, result, "regarding the organization JohnsCompany") - assert.Contains(t, result, "contribution to the CLA Group JohnsCLAGroupName") + assert.Contains(t, result, "The following contributor would like to submit a contribution to the projects(s): Project1, Project2") assert.Contains(t, result, "UserDetailsValue") assert.Contains(t, result, "Approval can be done at http://CorporateConsoleV2URL.com") } diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index 99ce1bdf2..72854d2b4 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -29,7 +29,7 @@ type EmailToCLAManagerModel struct { } // SendEmailToCLAManager handles sending an email to the specified CLA Manager -func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel) { +func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel, repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string) { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -47,20 +47,16 @@ func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAMa subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(input.Contributor)) recipients := []string{input.CLAManagerEmail} - body, err := emails.RenderTemplate( - utils.V2, emails.V2ContributorApprovalRequestTemplateName, - emails.V2ContributorApprovalRequestTemplate, - emails.V2ContributorApprovalRequestTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: input.CLAGroupName, - CompanyName: input.CompanyName, - CLAGroupName: input.CLAGroupName, - }, - SigningEntityName: input.CompanyName, - UserDetails: getFormattedUserDetails(input.Contributor), - CorporateConsoleV2URL: input.CorporateConsoleURL, + body, err := emails.RenderV2ContributorApprovalRequestTemplate(repository, projectService, projectSFIDs, emails.V2ContributorApprovalRequestTemplateParams{ + CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + RecipientName: input.CLAGroupName, + CompanyName: input.CompanyName, + CLAGroupName: input.CLAGroupName, }, - ) + SigningEntityName: input.CompanyName, + UserDetails: getFormattedUserDetails(input.Contributor), + CorporateConsoleV2URL: input.CorporateConsoleURL, + }) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering email template: %s", emails.V2ContributorApprovalRequestTemplateName) return diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 9b6275776..61be52814 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -374,7 +374,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C "companyName": params.Body.CompanyName, "signingEntityName": params.Body.SigningEntityName, "userID": params.Body.UserID, - "claGroupName": params.Body.ClaGroupName, + "claGroupName": params.Body.ClaGroupID, } log.WithFields(f).Debug("notifying CLA managers...") err := service.NotifyCLAManagers(ctx, params.Body, CorporateConsoleV2URL) @@ -386,7 +386,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C utils.ErrorResponseNotFound(reqID, msg)) } - msg := fmt.Sprintf("unable to notify cla managers - cla group: %s, company: %s", params.Body.ClaGroupName, params.Body.CompanyName) + msg := fmt.Sprintf("unable to notify cla managers - cla group: %s, company: %s", params.Body.ClaGroupID, params.Body.CompanyName) log.WithFields(f).WithError(err).Warn(err) return cla_manager.NewNotifyCLAManagersBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index ce8acf7d8..e4273c64c 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -92,7 +92,7 @@ type Service interface { IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) // Email Functions - SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel) + SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel, repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string) SendEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) @@ -1119,7 +1119,7 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode "companyName": notifyCLAManagers.CompanyName, "signingEntityName": notifyCLAManagers.SigningEntityName, "userID": notifyCLAManagers.UserID, - "claGroupName": notifyCLAManagers.ClaGroupName, + "claGroupName": notifyCLAManagers.ClaGroupID, } // Search for Easy CLA User log.WithFields(f).Debugf("Getting user by ID: %s", notifyCLAManagers.UserID) @@ -1130,6 +1130,18 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode return ErrCLAUserNotFound } + // Get mappings + var projectSFIDs []string + pcgs, pcgErr := s.projectCGRepo.GetProjectsIdsForClaGroup(notifyCLAManagers.ClaGroupID) + if pcgErr != nil { + log.WithFields(f).Warnf("problem getting cla_group_mappings by claGroupID: %s ", notifyCLAManagers.ClaGroupID) + return pcgErr + } + + for _, pcg := range pcgs { + projectSFIDs = append(projectSFIDs, pcg.ProjectSFID) + } + log.Debugf("Sending notification emails to CLA Managers: %+v", notifyCLAManagers.List) for _, claManager := range notifyCLAManagers.List { s.SendEmailToCLAManager(ctx, &EmailToCLAManagerModel{ @@ -1137,9 +1149,8 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode CLAManagerName: claManager.Name, CLAManagerEmail: claManager.Email.String(), CompanyName: notifyCLAManagers.CompanyName, - CLAGroupName: notifyCLAManagers.ClaGroupName, CorporateConsoleURL: CorporateConsoleV2URL, - }) + }, s.projectCGRepo, s.projectService, projectSFIDs) } return nil From 687c2f0cf5a51872a0bf467786467e3559122971 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 5 Mar 2021 16:23:55 +0300 Subject: [PATCH 0139/1276] [#2736] Bug/Notify CLA Managers - Updated swagger body parameter for claGroupID Signed-off-by: wanyaland --- cla-backend-go/swagger/cla.v2.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 0cdb4e9ce..bbe94ec82 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4554,7 +4554,7 @@ definitions: $ref: './common/properties/company-name.yaml' signingEntityName: $ref: './common/properties/company-signing-entity-name.yaml' - cla_group_id: + claGroupID: title: CLA Group ID description: The CLA Group ID $ref: './common/properties/internal-id.yaml' From 65f6838c69d9fbd5070286fe7375df48880aa47c Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 5 Mar 2021 16:48:42 +0300 Subject: [PATCH 0140/1276] [#2340] Feature/Contributor Added Instructions - Specified instructions in list format with bold format Signed-off-by: wanyaland --- cla-backend-go/signatures/service.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 86134c8ac..837bcf477 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -831,8 +831,15 @@ func sendRequestAccessEmailToContributorRecipient(authUser *auth.User, companyMo

    Hello %s,

    This is a notification email from EasyCLA regarding the project %s.

    You have been %s %s the Approval List of %s for %s by CLA Manager %s. This means that %s.

    -

    If you had previously submitted a pull request to EasyCLA Test Group that had failed, -you can now go back to it and follow the link to verify with your organization.

    + +

    If you had previously submitted a pull request to EasyCLA Test Group that had failed, you can now go back to it, re-click the “Not Covered” button in the EasyCLA message in your pull request, and then follow these steps

    +
      +
    1. Select “Corporate Contributor”.
    2. +
    3. Select your company from the organization drop down list
    4. +
    5. Click Proceed
    6. +
    +

    These steps will confirm your organization association and you will only need to do these once. After completing these steps, the EasyCLA check will be complete and enabled for all future code contributions for this project.

    +
    %s %s`, recipientName, projectName, addRemove, toFrom, From d413698bec6272b274b18132ae1bef25f1e4cd79 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 5 Mar 2021 20:26:26 +0300 Subject: [PATCH 0141/1276] [#2736, #2739] Feature/ Email Updates (#2747) - Updated notification email to CLA Manager - Updated contributor request email Signed-off-by: wanyaland --- cla-backend-go/emails/v2_cla_manager_templates.go | 2 +- cla-backend-go/v2/cla_manager/emails.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index e48df93f4..39a137f47 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -50,7 +50,7 @@ const ( V2ContributorApprovalRequestTemplate = `

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

    -

    The following contributor would like to submit a contribution to the projects(s): {{.GetProjectsOrProject}} and is requesting to be approved as a contributor for your organization:

    +

    The following contributor would like to submit a contribution to the projects(s): {{.GetProjectsOrProject}} and is requesting to be added to the approval list as a contributor for your organization:

    {{.UserDetails}}

    Approval can be done at {{.CorporateConsoleV2URL}}. Visit any of the project(s):{{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}} and add the contributor to the approved list.

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

    diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index 72854d2b4..01158dc65 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -49,9 +49,8 @@ func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAMa recipients := []string{input.CLAManagerEmail} body, err := emails.RenderV2ContributorApprovalRequestTemplate(repository, projectService, projectSFIDs, emails.V2ContributorApprovalRequestTemplateParams{ CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: input.CLAGroupName, + RecipientName: input.CLAManagerName, CompanyName: input.CompanyName, - CLAGroupName: input.CLAGroupName, }, SigningEntityName: input.CompanyName, UserDetails: getFormattedUserDetails(input.Contributor), From c065ccd415c9499c762bbed95f96ecf9a33edf80 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Mar 2021 14:12:32 -0800 Subject: [PATCH 0142/1276] Ensure Company ACL Is Set for New Companies (#2749) Signed-off-by: David Deal --- cla-backend-go/swagger/cla.v2.yaml | 4 ++ cla-backend-go/v2/company/handlers.go | 4 +- cla-backend-go/v2/company/service.go | 57 +++++++++++++++++++-------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index bbe94ec82..b3735a92e 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4063,6 +4063,10 @@ definitions: description: the company website userEmail: $ref: './common/properties/email.yaml' + note: + description: 'Optional note associated with the new company request. This information will be attached to the company record.' + type: string + maxLength: 256 company-output: type: object diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 76b151cf6..f0d7f3428 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -347,7 +347,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("creating company...") - companyModel, err := service.CreateCompany(ctx, *params.Input.CompanyName, params.Input.SigningEntityName, *params.Input.CompanyWebsite, params.Input.UserEmail.String(), params.UserID, "") + companyModel, err := service.CreateCompany(ctx, ¶ms) if err != nil { log.Warnf("error returned from create company api: %+v", err) if strings.Contains(err.Error(), "website already exists") { @@ -395,7 +395,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debugf("found company: '%s' in the organization service - creating local record...", params.CompanyName) - companyModelOutput, companyCreateErr := service.CreateCompanyFromSFModel(ctx, orgModels[0]) + companyModelOutput, companyCreateErr := service.CreateCompanyFromSFModel(ctx, orgModels[0], authUser) if companyCreateErr != nil || companyModelOutput == nil { msg := fmt.Sprintf("unable to create company '%s' from salesforce record", params.CompanyName) log.WithFields(f).WithError(err).Warn(msg) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index a72c5d83a..e1fcaf05d 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -39,6 +39,7 @@ import ( acsService "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" orgModels "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service/models" + v2Ops "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/company" orgService "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service/client/organizations" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" @@ -86,8 +87,8 @@ type Service interface { GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) - CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID, note string) (*models.CompanyOutput, error) - CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization) (*models.CompanyOutput, error) + CreateCompany(ctx context.Context, params *v2Ops.CreateCompanyParams) (*models.CompanyOutput, error) + CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization, authUser *auth.User) (*models.CompanyOutput, error) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) GetCompanyByID(ctx context.Context, companyID string) (*models.Company, error) @@ -367,18 +368,25 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID }, nil } -func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityName, companyWebsite, userEmail, userID string, note string) (*models.CompanyOutput, error) { +func (s *service) CreateCompany(ctx context.Context, params *v2Ops.CreateCompanyParams) (*models.CompanyOutput, error) { f := logrus.Fields{ "functionName": "service.CreateCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companyName": companyName, - "signingEntityName": signingEntityName, - "companyWebsite": companyWebsite, - "userEmail": userEmail, - "userID": userID, - "note": note, + "companyName": params.Input.CompanyName, + "signingEntityName": params.Input.SigningEntityName, + "companyWebsite": params.Input.CompanyWebsite, + "userEmail": params.Input.UserEmail.String(), + "userID": params.UserID, + "note": params.Input.Note, } + var lfUser *v2UserServiceModels.User + companyName := *params.Input.CompanyName + signingEntityName := params.Input.SigningEntityName + companyWebsite := *params.Input.CompanyWebsite + userEmail := params.Input.UserEmail.String() + userID := params.UserID + note := params.Input.Note // Create SalesForce company orgClient := orgService.GetClient() @@ -447,17 +455,16 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN // OrgID used as externalID for the easyCLA Company // Create a new company model for the create function createCompanyModel := &v1Models.Company{ - CompanyACL: nil, CompanyExternalID: org.ID, CompanyManagerID: userID, CompanyName: companyName, SigningEntityName: signingEntityName, + Note: note, } if lfUser != nil && lfUser.Username != "" { createCompanyModel.CompanyACL = []string{lfUser.Username} - } - if note != "" { - createCompanyModel.Note = note + } else { + createCompanyModel.CompanyACL = []string{} } _, createErr := s.companyRepo.CreateCompany(ctx, createCompanyModel) @@ -476,7 +483,7 @@ func (s *service) CreateCompany(ctx context.Context, companyName, signingEntityN }, nil } -func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization) (*models.CompanyOutput, error) { +func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization, authUser *auth.User) (*models.CompanyOutput, error) { f := logrus.Fields{ "functionName": "company.service.CreateCompanyFromSFModel", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -488,8 +495,26 @@ func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgMod } log.WithFields(f).Debugf("Creating company: %s...", orgModel.Name) - return s.CreateCompany(ctx, orgModel.Name, orgModel.Name, orgModel.Link, - "", "", fmt.Sprintf("created from platform organization service model: %s", orgModel.ID)) + companyInput := &models.CompanyInput{ + CompanyName: &orgModel.Name, + CompanyWebsite: &orgModel.Link, + Note: fmt.Sprintf("created from platform organization service model: %s", orgModel.ID), + SigningEntityName: orgModel.Name, + } + if orgModel.Owner != nil { + userServiceClient := v2UserService.GetClient() + userModel, userLookupErr := userServiceClient.GetUser(orgModel.ID) + if userLookupErr != nil { + log.WithFields(f).WithError(userLookupErr).Warnf("unable to lookup user by SFID: %s", orgModel.ID) + } else { + userEmail := strfmt.Email(*userModel.Email) + companyInput.UserEmail = &userEmail + } + } + return s.CreateCompany(ctx, &v2Ops.CreateCompanyParams{ + Input: companyInput, + UserID: authUser.UserName, + }) } // GetCompanyByName deletes the company by name From 6ade1bfa81b3a1936718f9cef5831a20035ecefd Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 5 Mar 2021 14:25:06 -0800 Subject: [PATCH 0143/1276] Resolved #2748 - Remove GitHub User ID From Contributor Approval Email (#2750) - Removed GitHub ID from email content - Added horizontal spacing to comma-separated list Signed-off-by: David Deal --- cla-backend-go/v2/cla_manager/service.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index e4273c64c..3753b2435 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -1213,10 +1213,6 @@ func getFormattedUserDetails(model *v1Models.User) string { details = append(details, fmt.Sprintf("GitHub User Name: %s", model.GithubUsername)) } - if model.GithubID != "" { - details = append(details, fmt.Sprintf("GitHub ID: %s", model.GithubID)) - } - if model.LfUsername != "" { details = append(details, fmt.Sprintf("LF Login: %s", model.LfUsername)) } @@ -1229,7 +1225,7 @@ func getFormattedUserDetails(model *v1Models.User) string { details = append(details, fmt.Sprintf("Emails: %s", strings.Join(model.Emails, ", "))) } - return strings.Join(details, ",") + return strings.Join(details, ", ") } // isSigned is a helper function to check if project/claGroup is signed From 760069a5725ae1d61235e992e0c147311302d918 Mon Sep 17 00:00:00 2001 From: David Deal Date: Sat, 6 Mar 2021 00:09:12 -0800 Subject: [PATCH 0144/1276] Resolved 2716 Added Parent Project SFID to Event Log (#2751) --- cla-backend-go/cla_manager/handlers.go | 18 +-- cla-backend-go/cla_manager/service.go | 44 +++--- cla-backend-go/events/event_data.go | 12 +- cla-backend-go/events/models.go | 80 ++++++---- cla-backend-go/events/repository.go | 48 +++--- cla-backend-go/events/service.go | 112 ++++++++------ .../github_organizations/handlers.go | 24 +-- cla-backend-go/repositories/handlers.go | 20 ++- cla-backend-go/signatures/service.go | 146 +++++++++--------- cla-backend-go/swagger/common/event.yaml | 45 ++++-- cla-backend-go/v2/cla_manager/service.go | 36 ++--- cla-backend-go/v2/company/service.go | 18 +-- .../v2/dynamo_events/projects_cla_groups.go | 90 ++++++----- .../v2/github_organizations/handlers.go | 18 +-- .../v2/organization-service/client.go | 15 +- cla-backend-go/v2/repositories/handlers.go | 24 +-- 16 files changed, 400 insertions(+), 350 deletions(-) diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index a4f9752a9..0b1cda6f1 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -166,15 +166,15 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. // Send an event eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaManagerAccessRequestCreated, - ProjectID: params.ProjectID, - ClaGroupModel: claGroupModel, - CompanyID: params.CompanyID, - CompanyModel: companyModel, - LfUsername: params.Body.UserLFID, - UserID: params.Body.UserLFID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaManagerAccessRequestCreated, + ProjectID: params.ProjectID, + ClaGroupModel: claGroupModel, + CompanyID: params.CompanyID, + CompanyModel: companyModel, + LfUsername: params.Body.UserLFID, + UserID: params.Body.UserLFID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAManagerRequestCreatedEventData{ RequestID: request.RequestID, CompanyName: companyModel.CompanyName, diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index a1e38b65d..3a1e8907e 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -242,17 +242,17 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaManagerCreated, - ProjectID: claGroupModel.ProjectExternalID, - CLAGroupID: claGroupID, - CLAGroupName: claGroupModel.ProjectName, - ClaGroupModel: claGroupModel, - CompanyID: companyID, - CompanyModel: companyModel, - LfUsername: LFID, - UserID: LFID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaManagerCreated, + ProjectID: claGroupModel.ProjectExternalID, + CLAGroupID: claGroupID, + CLAGroupName: claGroupModel.ProjectName, + ClaGroupModel: claGroupModel, + CompanyID: companyID, + CompanyModel: companyModel, + LfUsername: LFID, + UserID: LFID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAManagerCreatedEventData{ CompanyName: companyModel.CompanyName, ProjectName: claGroupModel.ProjectName, @@ -344,17 +344,17 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.ClaManagerDeleted, - ProjectID: claGroupModel.ProjectExternalID, - CLAGroupID: claGroupID, - CLAGroupName: claGroupModel.ProjectName, - ClaGroupModel: claGroupModel, - CompanyID: companyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: LFID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaManagerDeleted, + ProjectID: claGroupModel.ProjectExternalID, + CLAGroupID: claGroupID, + CLAGroupName: claGroupModel.ProjectName, + ClaGroupModel: claGroupModel, + CompanyID: companyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: LFID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAManagerDeletedEventData{ CompanyName: companyModel.CompanyName, ProjectName: claGroupModel.ProjectName, diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index bf0f750e8..ae3ddecb3 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -708,7 +708,7 @@ func (ed *ContributorNotifyCompanyAdminData) GetEventDetailsString(args *LogEven func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s notified CLA Designee: %s by Email: %s for Project Name : %s, ID: %s and Company Name: %s, ID: %s.", args.UserName, ed.DesigneeName, ed.DesigneeEmail, - args.ProjectName, args.ExternalProjectID, + args.ProjectName, args.ProjectSFID, args.CompanyName, args.CompanyID) return data, true } @@ -717,23 +717,23 @@ func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs func (ed *ContributorAssignCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User Name: %s, Email: %s was assigned as CLA Manager Designee for project Name: %s, ID: %s and Company Name: %s, ID: %s by: %s.", ed.DesigneeName, ed.DesigneeEmail, - args.ProjectName, args.ExternalProjectID, + args.ProjectName, args.ProjectSFID, args.CompanyName, args.CompanyID, args.UserName) return data, true } // GetEventDetailsString . . . func (ed *UserConvertToContactData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was converted to Contact state for Project: %s.", - args.LfUsername, args.ExternalProjectID) + data := fmt.Sprintf("User: %s was converted to Contact state for Project: %s with ID: %s.", + args.LfUsername, args.ProjectName, args.ProjectSFID) return data, true } // GetEventDetailsString . . . func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was assigned Scope: %s with Role: %s for Project: %s.", + data := fmt.Sprintf("User: %s was assigned Scope: %s with Role: %s for Project: %s with ID: %s.", args.LfUsername, - ed.Scope, ed.Role, args.ExternalProjectID) + ed.Scope, ed.Role, args.ProjectName, args.ProjectSFID) return data, true } diff --git a/cla-backend-go/events/models.go b/cla-backend-go/events/models.go index fa05cc407..de832080e 100644 --- a/cla-backend-go/events/models.go +++ b/cla-backend-go/events/models.go @@ -10,24 +10,32 @@ const IndividualSignedEvent = "IndividualSignatureSigned" // Event data model type Event struct { - EventID string `dynamodbav:"event_id"` - EventType string `dynamodbav:"event_type"` - EventUserID string `dynamodbav:"event_user_id"` - EventUserName string `dynamodbav:"event_user_name"` - EventLfUsername string `dynamodbav:"event_lf_username"` - EventProjectID string `dynamodbav:"event_project_id"` - EventProjectExternalID string `dynamodbav:"event_project_external_id"` - EventProjectName string `dynamodbav:"event_project_name"` - EventCompanyID string `dynamodbav:"event_company_id"` - EventCompanyName string `dynamodbav:"event_company_name"` - EventTime string `dynamodbav:"event_time"` - EventTimeEpoch int64 `dynamodbav:"event_time_epoch"` - EventData string `dynamodbav:"event_data"` - EventSummary string `dynamodbav:"event_summary"` - EventFoundationSFID string `dynamodbav:"event_foundation_sfid"` - EventSFProjectName string `dynamodbav:"event_sf_project_name"` + EventID string `dynamodbav:"event_id"` + EventType string `dynamodbav:"event_type"` + + EventUserID string `dynamodbav:"event_user_id"` + EventUserName string `dynamodbav:"event_user_name"` + EventLfUsername string `dynamodbav:"event_lf_username"` + + EventCLAGroupID string `dynamodbav:"event_cla_group_id"` + EventCLAGroupName string `dynamodbav:"event_cla_group_name"` + EventCLAGroupNameLower string `dynamodbav:"event_cla_group_name_lower"` + + EventProjectID string `dynamodbav:"event_project_id"` // legacy, same as the SFID EventProjectSFID string `dynamodbav:"event_project_sfid"` - EventCompanySFID string `dynamodbav:"event_company_sfid"` + EventProjectName string `dynamodbav:"event_project_name"` + EventParentProjectSFID string `dynamodbav:"event_parent_project_sfid"` + EventParentProjectName string `dynamodbav:"event_parent_project_name"` + + EventCompanyID string `dynamodbav:"event_company_id"` + EventCompanySFID string `dynamodbav:"event_company_sfid"` + EventCompanyName string `dynamodbav:"event_company_name"` + + EventData string `dynamodbav:"event_data"` + EventSummary string `dynamodbav:"event_summary"` + + EventTime string `dynamodbav:"event_time"` + EventTimeEpoch int64 `dynamodbav:"event_time_epoch"` } // DBUser data model @@ -50,22 +58,32 @@ type DBUser struct { func (e *Event) toEvent() *models.Event { //nolint event := &models.Event{ - EventData: e.EventData, - EventSummary: e.EventSummary, - EventID: e.EventID, + EventID: e.EventID, + EventType: e.EventType, + + UserID: e.EventUserID, + UserName: e.EventUserName, + LfUsername: e.EventLfUsername, + + EventCLAGroupID: e.EventCLAGroupID, + EventCLAGroupName: e.EventCLAGroupName, + EventCLAGroupNameLower: e.EventCLAGroupNameLower, + EventProjectID: e.EventProjectID, - EventProjectExternalID: e.EventProjectExternalID, - EventProjectName: e.EventProjectName, - EventTime: e.EventTime, - EventType: e.EventType, - UserID: e.EventUserID, - UserName: e.EventUserName, - LfUsername: e.EventLfUsername, - EventTimeEpoch: e.EventTimeEpoch, - EventFoundationSFID: e.EventFoundationSFID, EventProjectSFID: e.EventProjectSFID, - EventProjectSFName: e.EventSFProjectName, - EventCompanySFID: e.EventCompanySFID, + EventProjectName: e.EventProjectName, + EventParentProjectSFID: e.EventParentProjectSFID, + EventParentProjectName: e.EventParentProjectName, + + EventCompanyID: e.EventCompanyID, + EventCompanySFID: e.EventCompanySFID, + EventCompanyName: e.EventCompanyName, + + EventTime: e.EventTime, + EventTimeEpoch: e.EventTimeEpoch, + + EventData: e.EventData, + EventSummary: e.EventSummary, } // Disregard Company details for ICLA event if event.EventType != IndividualSignedEvent { diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index f99780f1f..7fd1aafae 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -56,7 +56,7 @@ const ( // Repository interface defines methods of event repository service type Repository interface { CreateEvent(event *models.Event) error - AddDataToEvent(eventID, foundationSFID, projectSFID, projectSFName, companySFID, projectID string) error + AddDataToEvent(eventID, parentProjectSFID, projectSFID, projectSFName, companySFID, projectID string) error SearchEvents(params *eventOps.SearchEventsParams, pageSize int64) (*models.EventList, error) GetRecentEvents(pageSize int64) (*models.EventList, error) @@ -109,15 +109,11 @@ func (repo *repository) CreateEvent(event *models.Event) error { eventDateAndContainsPII := fmt.Sprintf("%s#%t", toDateFormat(currentTime), event.ContainsPII) addAttribute(input.Item, "event_id", eventID.String()) addAttribute(input.Item, "event_type", event.EventType) + addAttribute(input.Item, "event_user_id", event.UserID) addAttribute(input.Item, "event_user_name", event.UserName) - addAttribute(input.Item, "event_lf_username", event.LfUsername) addAttribute(input.Item, "event_user_name_lower", strings.ToLower(event.UserName)) - - addAttribute(input.Item, "event_time", currentTimeString) - addAttribute(input.Item, "event_date", toDateFormat(currentTime)) - addAttribute(input.Item, "event_data", event.EventData) - addAttribute(input.Item, "event_summary", event.EventSummary) + addAttribute(input.Item, "event_lf_username", event.LfUsername) addAttribute(input.Item, "event_company_id", event.EventCompanyID) addAttribute(input.Item, "event_company_sfid", event.EventCompanySFID) @@ -129,16 +125,23 @@ func (repo *repository) CreateEvent(event *models.Event) error { addAttribute(input.Item, "event_cla_group_name_lower", strings.ToLower(event.EventCLAGroupName)) addAttribute(input.Item, "event_project_id", event.EventProjectID) - addAttribute(input.Item, "event_project_external_id", event.EventProjectExternalID) + addAttribute(input.Item, "event_project_sfid", event.EventProjectSFID) addAttribute(input.Item, "event_project_name", event.EventProjectName) addAttribute(input.Item, "event_project_name_lower", strings.ToLower(event.EventProjectName)) + addAttribute(input.Item, "event_parent_project_sfid", strings.ToLower(event.EventParentProjectSFID)) + addAttribute(input.Item, "event_parent_project_name", strings.ToLower(event.EventParentProjectName)) + addAttribute(input.Item, "event_data", event.EventData) + addAttribute(input.Item, "event_summary", event.EventSummary) + + addAttribute(input.Item, "event_time", currentTimeString) + addAttribute(input.Item, "event_date", toDateFormat(currentTime)) addAttribute(input.Item, "event_date_and_contains_pii", eventDateAndContainsPII) input.Item["contains_pii"] = &dynamodb.AttributeValue{BOOL: &event.ContainsPII} input.Item["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(currentTime.Unix(), 10))} - if event.EventCompanyID != "" && event.EventProjectExternalID != "" { - companyIDExternalProjectID := fmt.Sprintf("%s#%s", event.EventCompanyID, event.EventProjectExternalID) + if event.EventCompanyID != "" && event.EventProjectSFID != "" { + companyIDExternalProjectID := fmt.Sprintf("%s#%s", event.EventCompanyID, event.EventProjectSFID) addAttribute(input.Item, "company_id_external_project_id", companyIDExternalProjectID) } @@ -458,7 +461,7 @@ func buildNextKey(indexName string, event *models.Event) (string, error) { switch indexName { case CompanySFIDFoundationSFIDEpochIndex: nextKey["company_sfid_foundation_sfid"] = &dynamodb.AttributeValue{ - S: aws.String(fmt.Sprintf("%s#%s", event.EventCompanySFID, event.EventFoundationSFID)), + S: aws.String(fmt.Sprintf("%s#%s", event.EventCompanySFID, event.EventParentProjectSFID)), } nextKey["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(event.EventTimeEpoch, 10))} case CompanySFIDProjectIDEpochIndex: @@ -467,7 +470,7 @@ func buildNextKey(indexName string, event *models.Event) (string, error) { } nextKey["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(event.EventTimeEpoch, 10))} case EventFoundationSFIDEpochIndex: - nextKey["event_foundation_sfid"] = &dynamodb.AttributeValue{S: aws.String(event.EventFoundationSFID)} + nextKey["event_parent_project_sfid"] = &dynamodb.AttributeValue{S: aws.String(event.EventParentProjectSFID)} nextKey["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(event.EventTimeEpoch, 10))} case EventProjectIDEpochIndex: nextKey["event_project_id"] = &dynamodb.AttributeValue{S: aws.String(event.EventProjectID)} @@ -511,7 +514,7 @@ func (repo *repository) GetCompanyEvents(companyID, eventType string, nextKey *s // GetFoundationEvents returns the list of foundation events func (repo *repository) GetFoundationEvents(foundationSFID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { - keyCondition := expression.Key("event_foundation_sfid").Equal(expression.Value(foundationSFID)) + keyCondition := expression.Key("event_parent_project_sfid").Equal(expression.Value(foundationSFID)) return repo.queryEventsTable(EventFoundationSFIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } @@ -668,7 +671,7 @@ func (repo repository) getEventByDay(day string, containsPII bool, pageSize int6 return events, nil } -func (repo repository) AddDataToEvent(eventID, foundationSFID, projectSFID, projectSFName, companySFID, projectID string) error { +func (repo repository) AddDataToEvent(eventID, parentProjectSFID, projectSFID, projectSFName, companySFID, projectID string) error { tableName := fmt.Sprintf("cla-%s-events", repo.stage) input := &dynamodb.UpdateItemInput{ TableName: aws.String(tableName), @@ -678,28 +681,31 @@ func (repo repository) AddDataToEvent(eventID, foundationSFID, projectSFID, proj }, }, } - companySFIDFoundationSFID := fmt.Sprintf("%s#%s", companySFID, foundationSFID) + companySFIDFoundationSFID := fmt.Sprintf("%s#%s", companySFID, parentProjectSFID) companySFIDProjectID := fmt.Sprintf("%s#%s", companySFID, projectID) ue := utils.NewDynamoUpdateExpression() - ue.AddAttributeName("#foundation_sfid", "event_foundation_sfid", foundationSFID != "") + ue.AddAttributeName("#parent_project_sfid", "event_parent_project_sfid", parentProjectSFID != "") ue.AddAttributeName("#project_sfid", "event_project_sfid", projectSFID != "") ue.AddAttributeName("#project_sf_name", "event_sf_project_name", projectSFName != "") + ue.AddAttributeName("#company_sfid", "event_company_sfid", companySFID != "") - ue.AddAttributeName("#company_sfid_foundation_sfid", "company_sfid_foundation_sfid", companySFID != "" && foundationSFID != "") + ue.AddAttributeName("#company_sfid_foundation_sfid", "company_sfid_foundation_sfid", companySFID != "" && parentProjectSFID != "") ue.AddAttributeName("#company_sfid_project_id", "company_sfid_project_id", companySFID != "" && projectID != "") - ue.AddAttributeValue(":foundation_sfid", &dynamodb.AttributeValue{S: aws.String(foundationSFID)}, foundationSFID != "") + ue.AddAttributeValue(":foundation_sfid", &dynamodb.AttributeValue{S: aws.String(parentProjectSFID)}, parentProjectSFID != "") ue.AddAttributeValue(":project_sfid", &dynamodb.AttributeValue{S: aws.String(projectSFID)}, projectSFID != "") ue.AddAttributeValue(":project_sf_name", &dynamodb.AttributeValue{S: aws.String(projectSFName)}, projectSFName != "") + ue.AddAttributeValue(":company_sfid", &dynamodb.AttributeValue{S: aws.String(companySFID)}, companySFID != "") - ue.AddAttributeValue(":company_sfid_foundation_sfid", &dynamodb.AttributeValue{S: aws.String(companySFIDFoundationSFID)}, companySFID != "" && foundationSFID != "") + ue.AddAttributeValue(":company_sfid_foundation_sfid", &dynamodb.AttributeValue{S: aws.String(companySFIDFoundationSFID)}, companySFID != "" && parentProjectSFID != "") ue.AddAttributeValue(":company_sfid_project_id", &dynamodb.AttributeValue{S: aws.String(companySFIDProjectID)}, companySFID != "" && projectID != "") - ue.AddUpdateExpression("#foundation_sfid = :foundation_sfid", foundationSFID != "") + ue.AddUpdateExpression("#parent_project_sfid = :parent_project_sfid", parentProjectSFID != "") ue.AddUpdateExpression("#project_sfid = :project_sfid", projectSFID != "") ue.AddUpdateExpression("#project_sf_name = :project_sf_name", projectSFName != "") + ue.AddUpdateExpression("#company_sfid = :company_sfid", companySFID != "") - ue.AddUpdateExpression("#company_sfid_foundation_sfid = :company_sfid_foundation_sfid", companySFID != "" && foundationSFID != "") + ue.AddUpdateExpression("#company_sfid_foundation_sfid = :company_sfid_foundation_sfid", companySFID != "" && parentProjectSFID != "") ue.AddUpdateExpression("#company_sfid_project_id = :company_sfid_project_id", companySFID != "" && projectID != "") if ue.Expression == "" { // nothing to update diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 317ea5bf4..177f299e5 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -115,24 +115,25 @@ func (s *service) GetCompanyEvents(companyID, eventType string, nextKey *string, type LogEventArgs struct { EventType string - ExternalProjectID string - ProjectName string - ProjectSFID string + UserID string + LfUsername string + UserName string + UserModel *models.User - ProjectID string // Should just use CLA GroupID CLAGroupID string CLAGroupName string ClaGroupModel *models.ClaGroup - CompanyModel *models.Company + ProjectID string // Should just use CLA GroupID + ProjectSFID string + ProjectName string + ParentProjectSFID string + ParentProjectName string + CompanyID string CompanyName string CompanySFID string - - LfUsername string - UserName string - UserID string - UserModel *models.User + CompanyModel *models.Company EventData EventData } @@ -168,30 +169,31 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } + // First, attempt to user the CLA Group model that was provided... if args.ClaGroupModel != nil { args.CLAGroupID = args.ClaGroupModel.ProjectID args.ProjectName = args.ClaGroupModel.ProjectName - args.ExternalProjectID = args.ClaGroupModel.ProjectExternalID - return nil - } - - claGroupID := "" - if args.CLAGroupID != "" { - claGroupID = args.CLAGroupID - } else if args.ProjectID != "" && utils.IsUUIDv4(args.ProjectID) { // legacy parameter - claGroupID = args.ProjectID - } + args.ProjectSFID = args.ClaGroupModel.ProjectExternalID + } else { + // Did they set the CLA Group ID? + claGroupID := "" + if args.CLAGroupID != "" { + claGroupID = args.CLAGroupID + } else if args.ProjectID != "" && utils.IsUUIDv4(args.ProjectID) { // legacy parameter + claGroupID = args.ProjectID + } - if claGroupID != "" { - claGroupModel, err := s.combinedRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) - if err != nil { - log.WithFields(f).WithError(err).Warnf("failed to load CLA Group by ID: %s", claGroupID) - return err + // Load the CLA Group ID if set... + if claGroupID != "" { + claGroupModel, err := s.combinedRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) + if err != nil { + log.WithFields(f).WithError(err).Warnf("failed to load CLA Group by ID: %s", claGroupID) + return err + } + args.ClaGroupModel = claGroupModel + args.CLAGroupName = claGroupModel.ProjectName + args.ProjectSFID = claGroupModel.ProjectExternalID } - args.ClaGroupModel = claGroupModel - args.ProjectName = claGroupModel.ProjectName - args.ExternalProjectID = claGroupModel.ProjectExternalID - return nil } return nil @@ -207,14 +209,29 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { return s.loadCLAGroup(ctx, args) } else if utils.IsSalesForceID(args.ProjectID) { // external SF project ID args.ProjectSFID = args.ProjectID - args.ExternalProjectID = args.ProjectID // Check if project exists in platform project service + log.WithFields(f).Debugf("loading salesforce project by ID: %s...", args.ProjectSFID) project, projectErr := project_service.GetClient().GetProject(args.ProjectSFID) if projectErr != nil || project == nil { log.WithFields(f).Warnf("failed to load salesforce project by ID: %s", args.ProjectSFID) return nil } + log.WithFields(f).Debugf("loaded salesforce project by ID: %s", args.ProjectSFID) args.ProjectName = project.Name + + // Try to load and set the parent information + if project.Parent != "" { + log.WithFields(f).Debugf("loading salesforce project parent by ID: %s...", project.Parent) + parentProject, parentProjectErr := project_service.GetClient().GetProject(project.Parent) + if parentProjectErr != nil || parentProject == nil { + log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", project.Parent) + return nil + } + log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s", project.Parent) + args.ParentProjectSFID = parentProject.ID + args.ParentProjectName = parentProject.Name + } + return nil } @@ -318,22 +335,29 @@ func (s *service) LogEventWithContext(ctx context.Context, args *LogEventArgs) { eventData, containsPII := args.EventData.GetEventDetailsString(args) eventSummary, _ := args.EventData.GetEventSummaryString(args) event := models.Event{ - ContainsPII: containsPII, - EventCLAGroupID: args.CLAGroupID, - EventCLAGroupName: args.ProjectName, - EventCompanyID: args.CompanyID, - EventCompanySFID: args.CompanySFID, - EventCompanyName: args.CompanyName, - EventData: eventData, - EventProjectExternalID: args.ExternalProjectID, + EventType: args.EventType, + + UserID: args.UserID, + UserName: args.UserName, + LfUsername: args.LfUsername, + + EventCLAGroupID: args.CLAGroupID, + EventCLAGroupName: args.ProjectName, + + EventCompanyID: args.CompanyID, + EventCompanySFID: args.CompanySFID, + EventCompanyName: args.CompanyName, + EventProjectID: args.ProjectID, - EventProjectName: args.ProjectName, EventProjectSFID: args.ProjectSFID, - EventSummary: eventSummary, - EventType: args.EventType, - LfUsername: args.LfUsername, - UserID: args.UserID, - UserName: args.UserName, + EventProjectName: args.ProjectName, + EventParentProjectSFID: args.ParentProjectSFID, + EventParentProjectName: args.ParentProjectName, + + EventData: eventData, + EventSummary: eventSummary, + + ContainsPII: containsPII, } err = s.repo.CreateEvent(&event) if err != nil { diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index 7d494cdf0..c9f1b690c 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -90,10 +90,10 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv branchProtectionEnabled = *params.Body.BranchProtectionEnabled } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - UserID: claUser.UserID, - EventType: events.GitHubOrganizationAdded, - ExternalProjectID: params.ProjectSFID, - LfUsername: claUser.LFUsername, + UserID: claUser.UserID, + EventType: events.GitHubOrganizationAdded, + ProjectSFID: params.ProjectSFID, + LfUsername: claUser.LFUsername, EventData: &events.GitHubOrganizationAddedEventData{ GitHubOrganizationName: *params.Body.OrganizationName, AutoEnabled: autoEnabled, @@ -126,10 +126,10 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - UserID: claUser.UserID, - EventType: events.GitHubOrganizationDeleted, - ExternalProjectID: params.ProjectSFID, - LfUsername: claUser.LFUsername, + UserID: claUser.UserID, + EventType: events.GitHubOrganizationDeleted, + ProjectSFID: params.ProjectSFID, + LfUsername: claUser.LFUsername, EventData: &events.GitHubOrganizationDeletedEventData{ GitHubOrganizationName: params.OrgName, }, @@ -164,10 +164,10 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - UserID: claUser.UserID, - EventType: events.GitHubOrganizationUpdated, - ExternalProjectID: params.ProjectSFID, - LfUsername: claUser.LFUsername, + UserID: claUser.UserID, + EventType: events.GitHubOrganizationUpdated, + ProjectSFID: params.ProjectSFID, + LfUsername: claUser.LFUsername, EventData: &events.GitHubOrganizationUpdatedEventData{ GitHubOrganizationName: params.OrgName, AutoEnabled: *params.Body.AutoEnabled, diff --git a/cla-backend-go/repositories/handlers.go b/cla-backend-go/repositories/handlers.go index 8bd5a6a67..365b7bfd4 100644 --- a/cla-backend-go/repositories/handlers.go +++ b/cla-backend-go/repositories/handlers.go @@ -53,12 +53,11 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv return github_repositories.NewAddProjectGithubRepositoryBadRequest().WithPayload(errorResponse(err)) } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryAdded, - CLAGroupID: utils.StringValue(params.GithubRepositoryInput.RepositoryProjectID), - ProjectID: params.ProjectSFID, - ExternalProjectID: params.ProjectSFID, - UserID: claUser.UserID, - LfUsername: claUser.LFUsername, + EventType: events.RepositoryAdded, + CLAGroupID: utils.StringValue(params.GithubRepositoryInput.RepositoryProjectID), + ProjectSFID: params.ProjectSFID, + UserID: claUser.UserID, + LfUsername: claUser.LFUsername, UserModel: &models.User{ Username: claUser.LFUsername, }, @@ -92,11 +91,10 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv return github_repositories.NewDeleteProjectGithubRepositoryBadRequest().WithPayload(errorResponse(err)) } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryDisabled, - ProjectID: params.ProjectSFID, - ExternalProjectID: params.ProjectSFID, - UserID: claUser.UserID, - LfUsername: claUser.LFUsername, + EventType: events.RepositoryDisabled, + ProjectSFID: params.ProjectSFID, + UserID: claUser.UserID, + LfUsername: claUser.LFUsername, EventData: &events.RepositoryDisabledEventData{ RepositoryName: ghRepo.RepositoryName, }, diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 837bcf477..a4b66037e 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -642,15 +642,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.AddEmailApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddEmailData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -662,15 +662,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.RemoveEmailApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveEmailData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -682,15 +682,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.AddDomainApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddDomainData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -702,15 +702,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.RemoveDomainApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveDomainData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -722,15 +722,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.AddGithubUsernameApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddGitHubUsernameData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -742,15 +742,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.RemoveGithubUsernameApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveGitHubUsernameData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -762,15 +762,15 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.AddGithubOrgApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddGitHubOrgData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, @@ -782,16 +782,16 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models for _, value := range approvalList.RemoveGithubOrgApprovalList { // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ClaApprovalListUpdated, - CLAGroupID: claGroupModel.ProjectID, - ProjectID: claGroupModel.ProjectExternalID, - ClaGroupModel: claGroupModel, - CompanyID: companyModel.CompanyID, - CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: userModel.UserID, - UserModel: userModel, - ExternalProjectID: claGroupModel.ProjectExternalID, + EventType: events.ClaApprovalListUpdated, + CLAGroupID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveGitHubOrgData{ UserName: userModel.LfUsername, UserEmail: userModel.LfEmail, diff --git a/cla-backend-go/swagger/common/event.yaml b/cla-backend-go/swagger/common/event.yaml index a39ab12a5..a529b0241 100644 --- a/cla-backend-go/swagger/common/event.yaml +++ b/cla-backend-go/swagger/common/event.yaml @@ -9,6 +9,7 @@ properties: EventType: type: string description: type of the event + UserID: description: id of the user who created this event $ref: './common/properties/internal-id.yaml' @@ -17,49 +18,59 @@ properties: LfUsername: type: string description: name of the user + EventCLAGroupID: description: the CLA Group ID $ref: './common/properties/internal-id.yaml' EventCLAGroupName: description: the CLA Group name $ref: './common/properties/cla-group-name.yaml' + EventCLAGroupNameLower: + description: the CLA Group name lowercase + $ref: './common/properties/cla-group-name.yaml' + EventProjectID: type: string description: id of the SFID project + EventProjectSFID: + description: the project ID associated with the project. This would be projectSFID if the CLA group have only one project otherwise it would be foundationSFID + $ref: './common/properties/external-id.yaml' + EventProjectSFName: + $ref: './common/properties/project-name.yaml' + description: name of project to display. This would be name of project if cla group have only one project otherwise it would be name of foundation EventProjectName: $ref: './common/properties/project-name.yaml' - EventCompanyName: - $ref: './common/properties/company-name.yaml' + description: name of project to display. This would be name of project if cla group have only one project otherwise it would be name of foundation + EventParentProjectSFID: + description: the parent project ID associated with the event + $ref: './common/properties/external-id.yaml' + EventParentProjectName: + description: the parent project name associated with the event + $ref: './common/properties/project-name.yaml' + EventCompanyID: type: string description: id of the organization/company + EventCompanySFID: + type: string + description: the external SFID associated with the company + EventCompanyName: + $ref: './common/properties/company-name.yaml' + EventTime: type: string description: time of the event. EventTimeEpoch: type: integer description: time of the event in epoch. + EventData: type: string description: data related to the event EventSummary: type: string description: data related to the event summary - EventProjectExternalID: - type: string - description: the external Project ID related to this event + ContainsPII: type: boolean description: flag to indicate if this record contains personal identifiable information - EventCompanySFID: - type: string - description: the external SFID associated with the company - EventFoundationSFID: - type: string - description: the external SFID associated with the foundation - EventProjectSFID: - type: string - description: the external SFID associated with the project. This would be projectSFID if the CLA group have only one project otherwise it would be foundationSFID - EventProjectSFName: - type: string - description: name of project to display. This would be name of project if cla group have only one project otherwise it would be name of foundation diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 3753b2435..d796c166b 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -399,12 +399,12 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companyID string // Log Event s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.AssignUserRoleScopeType, - LfUsername: lfxUser.Username, - ExternalProjectID: projectSFID, - CompanyModel: v1CompanyModel, - CompanyID: v1CompanyModel.CompanyID, - UserModel: &v1Models.User{LfUsername: lfxUser.Username, UserID: lfxUser.ID}, + EventType: events.AssignUserRoleScopeType, + LfUsername: lfxUser.Username, + ProjectSFID: projectSFID, + CompanyModel: v1CompanyModel, + CompanyID: v1CompanyModel.CompanyID, + UserModel: &v1Models.User{LfUsername: lfxUser.Username, UserID: lfxUser.ID}, EventData: &events.AssignRoleScopeData{ Role: "cla-manager-designee", Scope: fmt.Sprintf("%s|%s", projectSFID, v1CompanyModel.CompanyExternalID), @@ -732,10 +732,10 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool s.SendEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) // Make a note in the event log s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ContributorNotifyCompanyAdminType, - LfUsername: authUser.UserName, - ExternalProjectID: projectID, - CompanyID: v1CompanyModel.CompanyID, + EventType: events.ContributorNotifyCompanyAdminType, + LfUsername: authUser.UserName, + ProjectSFID: projectID, + CompanyID: v1CompanyModel.CompanyID, EventData: &events.ContributorNotifyCompanyAdminData{ AdminName: admin.Contact.Name, AdminEmail: admin.Contact.EmailAddress, @@ -783,10 +783,10 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debug("creating a contributor assigned CLA designee log event...") // Make a note in the event log s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ContributorAssignCLADesigneeType, - LfUsername: authUser.UserName, - ExternalProjectID: projectID, - CompanyID: v1CompanyModel.CompanyID, + EventType: events.ContributorAssignCLADesigneeType, + LfUsername: authUser.UserName, + ProjectSFID: projectID, + CompanyID: v1CompanyModel.CompanyID, EventData: &events.ContributorAssignCLADesignee{ DesigneeName: claManagerDesignee.LfUsername, DesigneeEmail: claManagerDesignee.Email.String(), @@ -800,10 +800,10 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debug("creating a contributor notify CLA designee log event...") // Make a note in the event log s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ContributorNotifyCLADesigneeType, - LfUsername: authUser.UserName, - ExternalProjectID: projectID, - CompanyID: v1CompanyModel.CompanyID, + EventType: events.ContributorNotifyCLADesigneeType, + LfUsername: authUser.UserName, + ProjectSFID: projectID, + CompanyID: v1CompanyModel.CompanyID, EventData: &events.ContributorNotifyCLADesignee{ DesigneeName: claManagerDesignee.LfUsername, DesigneeEmail: claManagerDesignee.Email.String(), diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index e1fcaf05d..beda500d3 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -737,15 +737,15 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje // Log Event s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.AssignUserRoleScopeType, - LfUsername: user.Username, - UserID: user.ID, - ExternalProjectID: projectID, - CompanyModel: v1CompanyModel, - ClaGroupModel: projectModel, - CLAGroupID: projectModel.ProjectID, - CLAGroupName: projectModel.ProjectName, - UserModel: &v1Models.User{LfUsername: user.Username, UserID: user.ID}, + EventType: events.AssignUserRoleScopeType, + LfUsername: user.Username, + UserID: user.ID, + ProjectSFID: projectID, + CompanyModel: v1CompanyModel, + ClaGroupModel: projectModel, + CLAGroupID: projectModel.ProjectID, + CLAGroupName: projectModel.ProjectName, + UserModel: &v1Models.User{LfUsername: user.Username, UserID: user.ID}, EventData: &events.AssignRoleScopeData{ Role: "contributor", Scope: fmt.Sprintf("%s|%s", projectID, companyID), diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index e571f17a1..a2754e384 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -441,19 +441,18 @@ func (s *service) addCLAManagerDesigneePermissions(ctx context.Context, claGroup log.WithFields(f).Debug(msg) // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ - ContainsPII: false, - EventCompanySFID: orgID, - EventData: msg, - EventProjectExternalID: projectSFID, - EventProjectID: claGroupID, - EventProjectName: projectName, - EventProjectSFID: projectSFID, - EventCompanyName: orgName, - EventSummary: msgSummary, - EventType: claEvents.AssignUserRoleScopeType, - LfUsername: "easycla system", - UserID: "easycla system", - UserName: "easycla system", + ContainsPII: false, + EventCompanySFID: orgID, + EventData: msg, + EventProjectSFID: projectSFID, + EventProjectID: claGroupID, + EventProjectName: projectName, + EventCompanyName: orgName, + EventSummary: msgSummary, + EventType: claEvents.AssignUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", }) if eventErr != nil { log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.AssignUserRoleScopeType, msg) @@ -599,19 +598,18 @@ func (s *service) addCLAManagerPermissions(ctx context.Context, claGroupID, proj log.WithFields(f).Debug(msg) // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ - ContainsPII: false, - EventCompanyName: companyModel.CompanyName, - EventCompanySFID: companySFID, - EventData: msg, - EventProjectExternalID: projectSFID, - EventProjectID: claGroupID, - EventProjectName: projectName, - EventProjectSFID: projectSFID, - EventSummary: msgSummary, - EventType: claEvents.AssignUserRoleScopeType, - LfUsername: "easycla system", - UserID: "easycla system", - UserName: "easycla system", + ContainsPII: false, + EventCompanyName: companyModel.CompanyName, + EventCompanySFID: companySFID, + EventData: msg, + EventProjectID: claGroupID, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventSummary: msgSummary, + EventType: claEvents.AssignUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", }) if eventErr != nil { log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.AssignUserRoleScopeType, msg) @@ -662,16 +660,15 @@ func (s *service) removeCLAPermissions(ctx context.Context, projectSFID string) // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ - ContainsPII: false, - EventData: msg, - EventProjectExternalID: projectSFID, - EventProjectName: projectName, - EventProjectSFID: projectSFID, - EventSummary: msg, - EventType: claEvents.RemoveUserRoleScopeType, - LfUsername: "easycla system", - UserID: "easycla system", - UserName: "easycla system", + ContainsPII: false, + EventData: msg, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventSummary: msg, + EventType: claEvents.RemoveUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", }) if eventErr != nil { log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.RemoveUserRoleScopeType, msg) @@ -727,17 +724,16 @@ func (s *service) removeCLAPermissionsByProjectOrganizationRole(ctx context.Cont // Log the event eventErr := s.eventsRepo.CreateEvent(&models.Event{ - ContainsPII: false, - EventCompanySFID: organizationSFID, - EventData: msg, - EventProjectExternalID: projectSFID, - EventProjectName: projectName, - EventProjectSFID: projectSFID, - EventSummary: msg, - EventType: claEvents.RemoveUserRoleScopeType, - LfUsername: "easycla system", - UserID: "easycla system", - UserName: "easycla system", + ContainsPII: false, + EventCompanySFID: organizationSFID, + EventData: msg, + EventProjectName: projectName, + EventProjectSFID: projectSFID, + EventSummary: msg, + EventType: claEvents.RemoveUserRoleScopeType, + LfUsername: "easycla system", + UserID: "easycla system", + UserName: "easycla system", }) if eventErr != nil { log.WithFields(f).WithError(eventErr).Warnf("unable to create event log entry for %s with msg: %s", claEvents.RemoveUserRoleScopeType, msg) diff --git a/cla-backend-go/v2/github_organizations/handlers.go b/cla-backend-go/v2/github_organizations/handlers.go index d3022a639..f7a776a2b 100644 --- a/cla-backend-go/v2/github_organizations/handlers.go +++ b/cla-backend-go/v2/github_organizations/handlers.go @@ -129,9 +129,9 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. // Log the event eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - LfUsername: authUser.UserName, - EventType: events.GitHubOrganizationAdded, - ExternalProjectID: params.ProjectSFID, + LfUsername: authUser.UserName, + EventType: events.GitHubOrganizationAdded, + ProjectSFID: params.ProjectSFID, EventData: &events.GitHubOrganizationAddedEventData{ GitHubOrganizationName: *params.Body.OrganizationName, }, @@ -174,9 +174,9 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - LfUsername: authUser.UserName, - EventType: events.GitHubOrganizationDeleted, - ExternalProjectID: params.ProjectSFID, + LfUsername: authUser.UserName, + EventType: events.GitHubOrganizationDeleted, + ProjectSFID: params.ProjectSFID, EventData: &events.GitHubOrganizationDeletedEventData{ GitHubOrganizationName: params.OrgName, }, @@ -228,9 +228,9 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. // Log the event eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - LfUsername: authUser.UserName, - EventType: events.GitHubOrganizationUpdated, - ExternalProjectID: params.ProjectSFID, + LfUsername: authUser.UserName, + EventType: events.GitHubOrganizationUpdated, + ProjectSFID: params.ProjectSFID, EventData: &events.GitHubOrganizationUpdatedEventData{ GitHubOrganizationName: params.OrgName, AutoEnabled: utils.BoolValue(params.Body.AutoEnabled), diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index eae267238..967de77a7 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -287,15 +287,12 @@ func (osc *Client) DeleteRolePermissions(ctx context.Context, organizationID, pr // Log Event... v1EventService.LogEvent(&events.LogEventArgs{ - EventType: events.ClaManagerRoleDeleted, - ProjectID: projectID, - ClaGroupModel: nil, - CompanyID: organizationID, - CompanyModel: nil, - LfUsername: authUser.UserName, - UserID: authUser.UserName, - UserModel: nil, - ExternalProjectID: projectID, + EventType: events.ClaManagerRoleDeleted, + ProjectID: projectID, + ProjectSFID: projectID, + CompanyID: organizationID, + LfUsername: authUser.UserName, + UserID: authUser.UserName, EventData: &events.ClaManagerRoleDeletedData{ Role: role, // cla-manager Scope: scope.ObjectTypeName, // project|organization diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 5bafd1a10..66766b0a2 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -128,10 +128,10 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. // Log the events for _, result := range results { eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryAdded, - ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), - ExternalProjectID: params.ProjectSFID, - LfUsername: authUser.UserName, + EventType: events.RepositoryAdded, + ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), + ProjectSFID: params.ProjectSFID, + LfUsername: authUser.UserName, ClaGroupModel: &v1Models.ClaGroup{ ProjectExternalID: params.ProjectSFID, ProjectID: utils.StringValue(params.GithubRepositoryInput.ClaGroupID), @@ -203,10 +203,10 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryDisabled, - ExternalProjectID: params.ProjectSFID, - ProjectID: ghRepo.RepositoryProjectID, - LfUsername: authUser.UserName, + EventType: events.RepositoryDisabled, + ProjectSFID: params.ProjectSFID, + ProjectID: ghRepo.RepositoryProjectID, + LfUsername: authUser.UserName, EventData: &events.RepositoryDisabledEventData{ RepositoryName: ghRepo.RepositoryName, }, @@ -339,10 +339,10 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. // We could extract the parameter values from the branch protection payload to determine if it was added/remove or simply updated // For now, let's just set the updated event log eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryBranchProtectionUpdated, - ExternalProjectID: params.ProjectSFID, - ProjectID: params.ProjectSFID, - LfUsername: authUser.UserName, + EventType: events.RepositoryBranchProtectionUpdated, + ProjectSFID: params.ProjectSFID, + ProjectID: params.ProjectSFID, + LfUsername: authUser.UserName, EventData: &events.RepositoryBranchProtectionUpdatedEventData{ RepositoryName: repoModel.RepositoryName, }, From ac3168866f0cd2245d7b4b84f0fab5cbf5d63d1a Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 8 Mar 2021 18:04:58 +0200 Subject: [PATCH 0145/1276] email templates refactor (#2753) Signed-off-by: makkalot --- cla-backend-go/approval_list/service.go | 110 +++--- cla-backend-go/cla_manager/handlers.go | 142 ++++---- cla-backend-go/cla_manager/service.go | 127 +++---- cla-backend-go/cmd/server.go | 7 +- .../emails/approval_list_templates.go | 93 +++--- .../emails/approval_list_templates_test.go | 42 ++- .../emails/cla_manager_templates.go | 161 ++++++--- .../emails/cla_manager_templates_test.go | 101 +++--- cla-backend-go/emails/params.go | 62 ++-- cla-backend-go/emails/prefill.go | 148 ++++++++ cla-backend-go/emails/uitls.go | 94 ------ .../emails/v2_cla_manager_templates.go | 148 +++----- .../tests/v2_cla_manager_templates_test.go | 108 +++--- cla-backend-go/v2/cla_manager/emails.go | 315 +++++++++++------- cla-backend-go/v2/cla_manager/handlers.go | 4 +- cla-backend-go/v2/cla_manager/service.go | 104 ++++-- 16 files changed, 991 insertions(+), 775 deletions(-) create mode 100644 cla-backend-go/emails/prefill.go delete mode 100644 cla-backend-go/emails/uitls.go diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 9ba532bac..7c307ed1b 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -55,6 +55,7 @@ type service struct { projectRepo project.ProjectRepository signatureRepo signatures.SignatureRepository projectsCLAGroupRepository projects_cla_groups.Repository + emailTemplateService emails.EmailTemplateService corpConsoleURL string httpClient *http.Client } @@ -160,7 +161,7 @@ func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user. log.Warnf("ApproveCclaWhitelistRequest - unable to lookup company by id: %s, error: %+v", companyID, err) return err } - claGroupModel, err := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) + _, err = s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { log.Warnf("ApproveCclaWhitelistRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) return err @@ -208,8 +209,12 @@ func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user. } // Send the email - s.sendRequestApprovedEmailToRecipient(ctx, s.projectService, s.projectsCLAGroupRepository, *claUser, companyModel, claGroupModel, - requestModel.UserName, requestModel.UserEmails[0], projectSFIDs) + s.sendRequestApprovedEmailToRecipient(ctx, + emails.CommonEmailParams{ + RecipientName: requestModel.UserName, + RecipientAddress: requestModel.UserEmails[0], + CompanyName: companyModel.CompanyName, + }, *claUser, projectSFIDs) return nil } @@ -257,7 +262,11 @@ func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claG } // Send the email - s.sendRequestRejectedEmailToRecipient(companyModel, claGroupModel, sig.Signatures[0], requestModel.UserName, requestModel.UserEmails[0]) + s.sendRequestRejectedEmailToRecipient(emails.CommonEmailParams{ + RecipientName: requestModel.UserName, + RecipientAddress: requestModel.UserEmails[0], + CompanyName: companyModel.CompanyName, + }, claGroupModel, sig.Signatures[0]) return nil } @@ -279,7 +288,17 @@ func (s service) sendRequestSentEmail(companyModel *models.Company, claGroupMode // CLA Manager Name/Email from a list, send this to this recipient (CLA Manager) - otherwise we will send to all // CLA Managers on the Signature ACL if recipientName != "" && recipientEmail != "" { - s.sendRequestEmailToRecipient(s.projectsCLAGroupRepository, companyModel, claGroupModel, contributorName, contributorEmail, recipientName, recipientEmail, message) + s.sendRequestEmailToRecipient(emails.RequestToAuthorizeTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: recipientName, + RecipientAddress: recipientEmail, + CompanyName: companyModel.CompanyName, + }, + ContributorName: contributorName, + ContributorEmail: contributorEmail, + OptionalMessage: message, + CompanyID: companyModel.CompanyID, + }, claGroupModel) return } @@ -300,32 +319,27 @@ func (s service) sendRequestSentEmail(companyModel *models.Company, claGroupMode log.Warnf("unable to send email to manager: %+v - no email on file...", manager) } else { // Send the email - s.sendRequestEmailToRecipient(s.projectsCLAGroupRepository, companyModel, claGroupModel, contributorName, contributorEmail, manager.Username, whichEmail, message) + s.sendRequestEmailToRecipient(emails.RequestToAuthorizeTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: manager.Username, + RecipientAddress: whichEmail, + CompanyName: companyModel.CompanyName, + }, + ContributorName: contributorName, + ContributorEmail: contributorEmail, + OptionalMessage: message, + }, claGroupModel) } } } // sendRequestEmailToRecipient generates and sends an email to the specified recipient -func (s service) sendRequestEmailToRecipient(projectClaGroupRepository projects_cla_groups.Repository, companyModel *models.Company, claGroupModel *models.ClaGroup, contributorName, contributorEmail, recipientName, recipientAddress, message string) { - companyName := companyModel.CompanyName +func (s service) sendRequestEmailToRecipient(emailParams emails.RequestToAuthorizeTemplateParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName - // subject string, body string, recipients []string - subject := fmt.Sprintf("EasyCLA: Request to Authorize %s for %s", contributorName, projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderRequestToAuthorizeTemplate(projectClaGroupRepository, s.projectService, claGroupModel.Version, claGroupModel.ProjectExternalID, - emails.RequestToAuthorizeTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - ContributorName: contributorName, - ContributorEmail: contributorEmail, - OptionalMessage: message, - CorporateConsoleURL: s.corpConsoleURL, - CompanyID: companyModel.CompanyID, - }) + subject := fmt.Sprintf("EasyCLA: Request to Authorize %s for %s", emailParams.ContributorName, projectName) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderRequestToAuthorizeTemplate(s.emailTemplateService, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("rendering email template : %s failed : %v", emails.RequestToAuthorizeTemplateName, err) return @@ -339,8 +353,7 @@ func (s service) sendRequestEmailToRecipient(projectClaGroupRepository projects_ } // sendRequestRejectedEmailToRecipient generates and sends an email to the specified recipient -func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Company, claGroupModel *models.ClaGroup, signature *models.Signature, recipientName, recipientAddress string) { - companyName := companyModel.CompanyName +func (s service) sendRequestRejectedEmailToRecipient(emailParams emails.CommonEmailParams, claGroupModel *models.ClaGroup, signature *models.Signature) { projectName := claGroupModel.ProjectName emailCLAManagerParams := []emails.ClaManagerInfoParams{} @@ -369,18 +382,12 @@ func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Compan // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Approval List Request Denied for Project %s", projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderTemplate(claGroupModel.Version, emails.ApprovalListRejectedTemplateName, - emails.ApprovalListRejectedTemplate, - emails.ApprovalListRejectedTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - CLAManagers: emailCLAManagerParams, - }, - }, - ) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderApprovalListRejectedTemplate( + s.emailTemplateService, claGroupModel.Version, claGroupModel.ProjectExternalID, emails.ApprovalListRejectedTemplateParams{ + CommonEmailParams: emailParams, + CLAManagers: emailCLAManagerParams, + }) if err != nil { log.Warnf("rendering email failed for : %s : %v", emails.ApprovalListRejectedTemplateName, err) return @@ -393,22 +400,20 @@ func (s service) sendRequestRejectedEmailToRecipient(companyModel *models.Compan } } -func (s service) sendRequestApprovedEmailToRecipient(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, claUser user.CLAUser, companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, projectSFIDs []string) { +func (s service) sendRequestApprovedEmailToRecipient(ctx context.Context, emailParams emails.CommonEmailParams, claUser user.CLAUser, projectSFIDs []string) { f := logrus.Fields{ "functionName": "sendRequestApprovedEmailToRecipient", utils.XREQUESTID: ctx.Value((utils.XREQUESTID)), - "claGroupName": claGroupModel.ProjectName, - "claGroupID": claGroupModel.ProjectID, - "companyName": companyModel.CompanyName, - "recipientName": recipientName, - "recipientAddress": recipientAddress, + "companyName": emailParams.CompanyName, + "recipientName": emailParams.RecipientName, + "recipientAddress": emailParams.RecipientAddress, } - companyName := companyModel.CompanyName + companyName := emailParams.CompanyName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Approved List Request Accepted for %s", companyName) - recipients := []string{recipientAddress} + recipients := []string{emailParams.RecipientAddress} approver := "" if claUser.LFUsername != "" { @@ -420,15 +425,10 @@ func (s service) sendRequestApprovedEmailToRecipient(ctx context.Context, projec } body, err := emails.RenderApprovalListTemplate( - repository, projectService, projectSFIDs, emails.ApprovalListApprovedTemplateParams{ - ApprovalTemplateParams: emails.ApprovalTemplateParams{ - RecipientName: recipientName, - CompanyName: companyName, - CLAGroupName: claGroupModel.ProjectName, - Approver: approver, - }, - }, - ) + s.emailTemplateService, projectSFIDs, emails.ApprovalListApprovedTemplateParams{ + CommonEmailParams: emailParams, + Approver: approver, + }) if err != nil { log.WithFields(f).Warnf("rendering email failed for : %s : %v", emails.ApprovalListApprovedTemplateName, err) return diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 0b1cda6f1..99e15ce8d 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -37,7 +37,7 @@ func isValidUser(claUser *user.CLAUser) bool { } // Configure is the API handler routine for the CLA manager routes -func Configure(api *operations.ClaAPI, service IService, companyService company.IService, projectService project.Service, usersService users.Service, sigService signatures.SignatureService, eventsService events.Service, corporateConsoleURL string) { // nolint +func Configure(api *operations.ClaAPI, service IService, companyService company.IService, projectService project.Service, usersService users.Service, sigService signatures.SignatureService, eventsService events.Service, emailSvc emails.EmailTemplateService) { // nolint api.ClaManagerCreateCLAManagerRequestHandler = cla_manager.CreateCLAManagerRequestHandlerFunc(func(params cla_manager.CreateCLAManagerRequestParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint @@ -187,9 +187,15 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. // Send email to each manager for _, manager := range claManagers { - sendRequestAccessEmailToCLAManagers(companyModel, claGroupModel, - params.Body.UserName, params.Body.UserEmail, - manager.Username, manager.LfEmail) + sendRequestAccessEmailToCLAManagers(emailSvc, emails.RequestAccessToCLAManagersTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: manager.Username, + RecipientAddress: manager.LfEmail, + CompanyName: companyModel.CompanyName, + }, + RequesterName: params.Body.UserName, + RequesterEmail: params.Body.UserEmail, + }, claGroupModel) } return cla_manager.NewCreateCLAManagerRequestOK().WithXRequestID(reqID).WithPayload(request) @@ -357,12 +363,25 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. // Notify CLA Managers - send email to each manager for _, manager := range claManagers { - sendRequestApprovedEmailToCLAManagers(companyModel, claGroupModel, request.UserName, request.UserEmail, - manager.Username, manager.LfEmail) + sendRequestApprovedEmailToCLAManagers(emailSvc, emails.RequestApprovedToCLAManagersTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: manager.Username, + RecipientAddress: manager.LfEmail, + CompanyName: companyModel.CompanyName, + }, + RequesterName: request.UserName, + RequesterEmail: request.UserEmail, + }, claGroupModel) } // Notify the requester - sendRequestApprovedEmailToRequester(companyModel, claGroupModel, request.UserName, request.UserEmail) + sendRequestApprovedEmailToRequester(emailSvc, emails.RequestApprovedToRequesterTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: request.UserName, + RecipientAddress: request.UserEmail, + CompanyName: companyModel.CompanyName, + }, + }, claGroupModel) return cla_manager.NewCreateCLAManagerRequestOK().WithXRequestID(reqID).WithPayload(request) }) @@ -459,12 +478,23 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. // Notify CLA Managers - send email to each manager for _, manager := range claManagers { - sendRequestDeniedEmailToCLAManagers(companyModel, claGroupModel, request.UserName, request.UserEmail, - manager.Username, manager.LfEmail) + sendRequestDeniedEmailToCLAManagers(emailSvc, emails.RequestDeniedToCLAManagersTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: manager.Username, + RecipientAddress: manager.LfEmail, + CompanyName: companyModel.CompanyName, + }, + RequesterName: request.UserName, + RequesterEmail: request.UserEmail, + }, claGroupModel) } // Notify the requester - sendRequestDeniedEmailToRequester(companyModel, claGroupModel, request.UserName, request.UserEmail) + sendRequestDeniedEmailToRequester(emailSvc, emails.CommonEmailParams{ + RecipientName: request.UserName, + RecipientAddress: request.UserEmail, + CompanyName: companyModel.CompanyName, + }, claGroupModel) return cla_manager.NewCreateCLAManagerRequestOK().WithPayload(request) }) @@ -882,24 +912,15 @@ func buildErrorMessageDeleteManager(errPrefix string, params cla_manager.DeleteC } // sendRequestAccessEmailToCLAManagers sends the request access email to the specified CLA Managers -func sendRequestAccessEmailToCLAManagers(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail, recipientName, recipientAddress string) { - companyName := companyModel.CompanyName +func sendRequestAccessEmailToCLAManagers(emailSvc emails.EmailTemplateService, emailParams emails.RequestAccessToCLAManagersTemplateParams, claGroupModel *models.ClaGroup) { + companyName := emailParams.CompanyName projectName := claGroupModel.ProjectName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: New CLA Manager Access Request for %s on %s", companyName, projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderTemplate(claGroupModel.Version, emails.RequestAccessToCLAManagersTemplateName, - emails.RequestAccessToCLAManagersTemplate, emails.RequestAccessToCLAManagersTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - RequesterName: requesterName, - RequesterEmail: requesterEmail, - CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), - }) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderRequestAccessToCLAManagersTemplate( + emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("rendering email template : %s failed : %v", emails.RequestAccessToCLAManagersTemplateName, err) return @@ -913,27 +934,13 @@ func sendRequestAccessEmailToCLAManagers(companyModel *models.Company, claGroupM } } -func sendRequestApprovedEmailToCLAManagers(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail, recipientName, recipientAddress string) { - companyName := companyModel.CompanyName +func sendRequestApprovedEmailToCLAManagers(emailSvc emails.EmailTemplateService, emailParams emails.RequestApprovedToCLAManagersTemplateParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Access Approval Notice for %s", projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderTemplate( - claGroupModel.Version, - emails.RequestApprovedToCLAManagersTemplateName, - emails.RequestApprovedToCLAManagersTemplate, - emails.RequestApprovedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - RequesterName: requesterName, - RequesterEmail: requesterEmail, - }) - + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderRequestApprovedToCLAManagersTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("rendering email template : %s failed : %v", emails.RequestApprovedToCLAManagersTemplateName, err) return @@ -946,22 +953,13 @@ func sendRequestApprovedEmailToCLAManagers(companyModel *models.Company, claGrou } } -func sendRequestApprovedEmailToRequester(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail string) { - companyName := companyModel.CompanyName +func sendRequestApprovedEmailToRequester(emailSvc emails.EmailTemplateService, emailParams emails.RequestApprovedToRequesterTemplateParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: New CLA Manager Access Approved for %s", projectName) - recipients := []string{requesterEmail} - body, err := emails.RenderTemplate(claGroupModel.Version, emails.RequestApprovedToRequesterTemplateName, - emails.RequestApprovedToRequesterTemplate, emails.RequestApprovedToRequesterTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: requesterName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), - }) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderRequestApprovedToRequesterTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("email template : %s failed rendering : %s", emails.RequestApprovedToRequesterTemplateName, err) return @@ -974,27 +972,13 @@ func sendRequestApprovedEmailToRequester(companyModel *models.Company, claGroupM } } -func sendRequestDeniedEmailToCLAManagers(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail, recipientName, recipientAddress string) { - companyName := companyModel.CompanyName +func sendRequestDeniedEmailToCLAManagers(emailSvc emails.EmailTemplateService, emailParams emails.RequestDeniedToCLAManagersTemplateParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Access Denied Notice for %s", projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderTemplate( - claGroupModel.Version, - emails.RequestDeniedToCLAManagersTemplateName, - emails.RequestDeniedToCLAManagersTemplate, - emails.RequestDeniedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - RequesterName: requesterName, - RequesterEmail: requesterEmail, - }, - ) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderRequestDeniedToCLAManagersTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("email template render : %s failed : %v", emails.RequestDeniedToCLAManagersTemplateName, err) @@ -1009,23 +993,15 @@ func sendRequestDeniedEmailToCLAManagers(companyModel *models.Company, claGroupM } } -func sendRequestDeniedEmailToRequester(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail string) { - companyName := companyModel.CompanyName +func sendRequestDeniedEmailToRequester(emailSvc emails.EmailTemplateService, emailParams emails.CommonEmailParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: New CLA Manager Access Denied for %s", projectName) - recipients := []string{requesterEmail} - body, err := emails.RenderTemplate(claGroupModel.Version, emails.RequestDeniedToRequesterTemplateName, - emails.RequestDeniedToRequesterTemplate, - emails.RequestDeniedToRequesterTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: requesterName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - }) - + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderRequestDeniedToRequesterTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emails.RequestDeniedToRequesterTemplateParams{ + CommonEmailParams: emailParams, + }) if err != nil { log.Warnf("email template rendering %s failed : %v", emails.RequestDeniedToRequesterTemplateName, err) return diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 3a1e8907e..c6a12d159 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -7,8 +7,6 @@ import ( "context" "fmt" - v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" - "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -49,6 +47,7 @@ type service struct { usersService users.Service sigService signatures.SignatureService eventsService events.Service + emailServie emails.EmailTemplateService corporateConsoleURL string } @@ -234,11 +233,22 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID // Notify CLA Managers - send email to each manager for _, manager := range claManagers { - sendClaManagerAddedEmailToCLAManagers(companyModel, claGroupModel, userModel.Username, userModel.LfEmail, - manager.Username, manager.LfEmail) + sendClaManagerAddedEmailToCLAManagers(s.emailServie, emails.ClaManagerAddedToCLAManagersTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: manager.Username, + RecipientAddress: manager.LfEmail, + CompanyName: companyModel.CompanyName, + }, + Name: userModel.Username, + Email: userModel.LfEmail, + }, claGroupModel) } // Notify the added user - sendClaManagerAddedEmailToUser(companyModel, claGroupModel, userModel.Username, userModel.LfEmail, projectSFName) + sendClaManagerAddedEmailToUser(s.emailServie, emails.CommonEmailParams{ + RecipientName: userModel.Username, + RecipientAddress: userModel.LfEmail, + CompanyName: companyModel.CompanyName, + }, claGroupModel) // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ @@ -335,12 +345,23 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou claManagers := sigModel.SignatureACL // Notify CLA Managers - send email to each manager for _, manager := range claManagers { - sendClaManagerDeleteEmailToCLAManagers(companyModel, claGroupModel, userModel.LfUsername, userModel.LfEmail, - manager.Username, manager.LfEmail) + sendClaManagerDeleteEmailToCLAManagers(s.emailServie, emails.ClaManagerDeletedToCLAManagersTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: manager.Username, + RecipientAddress: manager.LfEmail, + CompanyName: companyModel.CompanyName, + }, + Name: userModel.LfUsername, + Email: userModel.LfEmail, + }, claGroupModel) } // Notify the removed manager - s.sendRemovedClaManagerEmailToRecipient(s.projectClaRepository, companyModel, claGroupModel, userModel.LfUsername, userModel.LfEmail, claManagers) + sendRemovedClaManagerEmailToRecipient(s.emailServie, emails.CommonEmailParams{ + RecipientName: userModel.LfUsername, + RecipientAddress: userModel.LfEmail, + CompanyName: companyModel.CompanyName, + }, claGroupModel, claManagers) // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ @@ -367,32 +388,14 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou return updatedSignature, nil } -func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel *models.ClaGroup, requesterName, requesterEmail, projectSFName string) { - companyName := companyModel.CompanyName +func sendClaManagerAddedEmailToUser(emailSvc emails.EmailTemplateService, emailParams emails.CommonEmailParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName - templateName := emails.ClaManagerAddedEToUserTemplate - if claGroupModel.Version == "v2" { - templateName = emails.V2ClaManagerAddedEToUserTemplate - projectName = projectSFName - } - // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Added as CLA Manager for Project :%s", projectName) - recipients := []string{requesterEmail} - body, err := emails.RenderTemplate( - claGroupModel.Version, - emails.ClaManagerAddedEToUserTemplateName, - templateName, - emails.ClaManagerAddedEToUserTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: requesterName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - CLAGroupName: claGroupModel.ProjectName, - }, - CorporateURL: utils.GetCorporateURL(claGroupModel.Version == utils.V2), - }, - ) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderClaManagerAddedEToUserTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emails.ClaManagerAddedEToUserTemplateParams{ + CommonEmailParams: emailParams, + }) if err != nil { log.Warnf("email template render : %s failed : %v", emails.ClaManagerAddedEToUserTemplateName, err) return @@ -406,36 +409,13 @@ func sendClaManagerAddedEmailToUser(companyModel *models.Company, claGroupModel } } -func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGroupModel *models.ClaGroup, name, email, recipientName, recipientAddress string) { - companyName := companyModel.CompanyName +func sendClaManagerAddedEmailToCLAManagers(emailSvc emails.EmailTemplateService, emailParams emails.ClaManagerAddedToCLAManagersTemplateParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName - // we want the Project Name in the email not the cla group name - ps := v2ProjectService.GetClient() - projectSF, projectErr := ps.GetProject(claGroupModel.ProjectExternalID) - if projectErr != nil { - msg := fmt.Sprintf("Project service lookup error for SFID: %s, error : %+v", - claGroupModel.ProjectExternalID, projectErr) - log.Warn(msg) - return - } - // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Added Notice for %s", projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderTemplate(claGroupModel.Version, emails.ClaManagerAddedToCLAManagersTemplateName, - emails.ClaManagerAddedToCLAManagersTemplate, - emails.ClaManagerAddedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - CLAGroupName: projectName, - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectSF.Name}, - CompanyName: companyName, - }, - Name: name, - Email: email, - }) - + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderClaManagerAddedToCLAManagersTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("email template render : %s failed : %v", emails.ClaManagerAddedToCLAManagersTemplate, err) return @@ -450,8 +430,7 @@ func sendClaManagerAddedEmailToCLAManagers(companyModel *models.Company, claGrou } // sendRequestRejectedEmailToRecipient generates and sends an email to the specified recipient -func (s *service) sendRemovedClaManagerEmailToRecipient(projectsClaGroupRepository projects_cla_groups.Repository, companyModel *models.Company, claGroupModel *models.ClaGroup, recipientName, recipientAddress string, claManagers []models.User) { - companyName := companyModel.CompanyName +func sendRemovedClaManagerEmailToRecipient(emailSvc emails.EmailTemplateService, emailParams emails.CommonEmailParams, claGroupModel *models.ClaGroup, claManagers []models.User) { projectName := claGroupModel.ProjectName emailCLAManagerParams := []emails.ClaManagerInfoParams{} @@ -498,15 +477,15 @@ func (s *service) sendRemovedClaManagerEmailToRecipient(projectsClaGroupReposito // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Removed as CLA Manager for Project %s", projectName) - recipients := []string{recipientAddress} + recipients := []string{emailParams.RecipientAddress} body, err := emails.RenderRemovedCLAManagerTemplate( - projectsClaGroupRepository, - s.projectService, + emailSvc, claGroupModel.Version, - recipientName, - companyName, claGroupModel.ProjectExternalID, - emailCLAManagerParams) + emails.RemovedCLAManagerTemplateParams{ + CommonEmailParams: emailParams, + CLAManagers: emailCLAManagerParams, + }) if err != nil { log.Warnf("rendering the email content failed for : %s", emails.RemovedCLAManagerTemplateName) @@ -521,25 +500,13 @@ func (s *service) sendRemovedClaManagerEmailToRecipient(projectsClaGroupReposito } } -func sendClaManagerDeleteEmailToCLAManagers(companyModel *models.Company, claGroupModel *models.ClaGroup, name, email, recipientName, recipientAddress string) { - companyName := companyModel.CompanyName +func sendClaManagerDeleteEmailToCLAManagers(emailSvc emails.EmailTemplateService, emailParams emails.ClaManagerDeletedToCLAManagersTemplateParams, claGroupModel *models.ClaGroup) { projectName := claGroupModel.ProjectName // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: CLA Manager Removed Notice for %s", projectName) - recipients := []string{recipientAddress} - body, err := emails.RenderTemplate(claGroupModel.Version, - emails.ClaManagerDeletedToCLAManagersTemplateName, - emails.ClaManagerDeletedToCLAManagersTemplate, - emails.ClaManagerDeletedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, - CompanyName: companyName, - }, - Name: name, - Email: email, - }) + recipients := []string{emailParams.RecipientAddress} + body, err := emails.RenderClaManagerDeletedToCLAManagersTemplate(emailSvc, claGroupModel.Version, claGroupModel.ProjectExternalID, emailParams) if err != nil { log.Warnf("email template render : %s failed : %v", emails.ClaManagerDeletedToCLAManagersTemplateName, err) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 0f2d13ce6..6fb227762 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" + "github.com/communitybridge/easycla/cla-backend-go/emails" + "github.com/communitybridge/easycla/cla-backend-go/v2/dynamo_events" v2GithubActivity "github.com/communitybridge/easycla/cla-backend-go/v2/github_activity" @@ -260,6 +262,7 @@ func server(localMode bool) http.Handler { healthService := health.New(Version, Commit, Branch, BuildDate) templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) v1ProjectService := project.NewService(projectRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) + emailTemplateService := emails.NewEmailTemplateService(projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleURL, configFile.CorporateConsoleV2URL) v2ProjectService := v2Project.NewService(v1ProjectService, projectRepo, projectClaGroupRepo) v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleURL, userRepo, usersService) v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, projectRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) @@ -269,7 +272,7 @@ func server(localMode bool) http.Handler { v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) - v2ClaManagerService := v2ClaManager.NewService(v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) + v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) @@ -323,7 +326,7 @@ func server(localMode bool) http.Handler { gerrits.Configure(api, gerritService, v1ProjectService, eventsService) v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, projectClaGroupRepo) v2Company.Configure(v2API, v2CompanyService, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleURL) - cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) + cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService) v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, configFile.CorporateConsoleV2URL, projectClaGroupRepo, userRepo) sign.Configure(v2API, v2SignService) cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, projectClaGroupRepo, eventsService) diff --git a/cla-backend-go/emails/approval_list_templates.go b/cla-backend-go/emails/approval_list_templates.go index faaf50642..344633917 100644 --- a/cla-backend-go/emails/approval_list_templates.go +++ b/cla-backend-go/emails/approval_list_templates.go @@ -4,16 +4,14 @@ package emails import ( - "strings" - - "github.com/communitybridge/easycla/cla-backend-go/project" - "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" ) // ApprovalListRejectedTemplateParams is email params for ApprovalListRejectedTemplate type ApprovalListRejectedTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams + CLAManagers []ClaManagerInfoParams } const ( @@ -34,9 +32,26 @@ If you have further questions about this denial, please contact one of the exist ` ) +// RenderApprovalListRejectedTemplate renders RequestToAuthorizeTemplate +func RenderApprovalListRejectedTemplate(svc EmailTemplateService, claGroupVersion string, projectSFID string, params ApprovalListRejectedTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupVersion, projectSFID) + if err != nil { + return "", err + } + + // assign the prefilled struct + params.CLAGroupTemplateParams = claGroupParams + return RenderTemplate(claGroupVersion, ApprovalListRejectedTemplateName, ApprovalListRejectedTemplate, + params, + ) + +} + // ApprovalListApprovedTemplateParams is email params for Approval type ApprovalListApprovedTemplateParams struct { - ApprovalTemplateParams + CommonEmailParams + CLAGroupTemplateParams + Approver string } const ( @@ -47,19 +62,34 @@ const (

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the CLA Group {{.CLAGroupName}}.

    You have been added to the Approval list of {{.CompanyName}} for {{.CLAGroupName}} by CLA Manager {{.Approver}}. -

    This means that you are authorized to contribute to the any of the following project(s) associated with the CLA Group {{.CLAGroupName}}: {{.GetProjects}}

    +

    This means that you are authorized to contribute to the any of the following project(s) associated with the CLA Group {{.CLAGroupName}}: {{.GetProjectsOrProject}}

    If you had previously submitted a pull request to any any the above project(s) that had failed, you can now go back to it and follow the link to verify with your organization.

    ` ) +// RenderApprovalListTemplate renders RenderApprovalListTemplate +func RenderApprovalListTemplate(svc EmailTemplateService, projectSFIDs []string, params ApprovalListApprovedTemplateParams) (string, error) { + // prefill the projects data + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFIDs[0]) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, + ApprovalListApprovedTemplate, params) +} + // RequestToAuthorizeTemplateParams is email params for RequestToAuthorizeTemplate type RequestToAuthorizeTemplateParams struct { - CLAManagerTemplateParams - ContributorName string - ContributorEmail string - OptionalMessage string - CorporateConsoleURL string - CompanyID string + CommonEmailParams + // This field is prefilled most of the time with EmailService + CLAGroupTemplateParams + CLAManagers []ClaManagerInfoParams + ContributorName string + ContributorEmail string + OptionalMessage string + CompanyID string } const ( @@ -77,7 +107,7 @@ const (

    {{.OptionalMessage}}


    {{end}}

    If you want to add them to the Approved List, please -log into the EasyCLA Corporate +log into the EasyCLA Corporate Console, where you can approve this user's request by selecting the 'Manage Approved List' and adding the contributor's email, the contributor's entire email domain, their GitHub ID or the entire GitHub Organization for the repository. This will permit them to begin contributing to {{.Project.ExternalProjectName}} on behalf of {{.CompanyName}}.

    @@ -86,41 +116,16 @@ repository. This will permit them to begin contributing to {{.Project.ExternalPr ) // RenderRequestToAuthorizeTemplate renders RequestToAuthorizeTemplate -func RenderRequestToAuthorizeTemplate(repository projects_cla_groups.Repository, projectService project.Service, claGroupVersion string, projecSFID string, params RequestToAuthorizeTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projecSFID, ¶ms.CLAManagerTemplateParams, nil); err != nil { +func RenderRequestToAuthorizeTemplate(svc EmailTemplateService, claGroupVersion string, projectSFID string, params RequestToAuthorizeTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupVersion, projectSFID) + if err != nil { return "", err } + // assign the prefilled struct + params.CLAGroupTemplateParams = claGroupParams return RenderTemplate(claGroupVersion, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, params, ) } - -// GetProjects returns the single Project or comma separated projects if more than one -func (p ApprovalListApprovedTemplateParams) GetProjects() string { - if len(p.Projects) == 1 { - return p.Projects[0].ExternalProjectName - } - - var projectNames []string - for _, p := range p.Projects { - projectNames = append(projectNames, p.ExternalProjectName) - } - - return strings.Join(projectNames, ", ") -} - -// RenderApprovalListTemplate renders RenderApprovalListTemplate -func RenderApprovalListTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params ApprovalListApprovedTemplateParams) (string, error) { - // prefill the projects data - projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, "") - if err != nil { - return "", err - } - - params.Projects = projects - - return RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, - ApprovalListApprovedTemplate, params) -} diff --git a/cla-backend-go/emails/approval_list_templates_test.go b/cla-backend-go/emails/approval_list_templates_test.go index f7c2524a7..e00f215d3 100644 --- a/cla-backend-go/emails/approval_list_templates_test.go +++ b/cla-backend-go/emails/approval_list_templates_test.go @@ -12,13 +12,15 @@ import ( func TestApprovalListRejectedTemplate(t *testing.T) { params := ApprovalListRejectedTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProject"}, CompanyName: "JohnsCompany", - CLAManagers: []ClaManagerInfoParams{ - {LfUsername: "LFUserName", Email: "LFEmail"}, - }, + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProject"}}, + }, + CLAManagers: []ClaManagerInfoParams{ + {LfUsername: "LFUserName", Email: "LFEmail"}, }, } @@ -33,16 +35,18 @@ func TestApprovalListRejectedTemplate(t *testing.T) { func TestApprovalListApprovedTemplate(t *testing.T) { params := ApprovalListApprovedTemplateParams{ - ApprovalTemplateParams: ApprovalTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "Recipient", - CLAGroupName: "CLAGroupFoo", CompanyName: "CompanyFoo", - Approver: "LFUsername", + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + CLAGroupName: "CLAGroupFoo", Projects: []CLAProjectParams{ {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, }, }, + Approver: "LFUsername", } result, err := RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, ApprovalListApprovedTemplate, params) @@ -56,19 +60,21 @@ func TestApprovalListApprovedTemplate(t *testing.T) { func TestRequestToAuthorizeTemplate(t *testing.T) { params := RequestToAuthorizeTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", - CLAManagers: []ClaManagerInfoParams{ - {LfUsername: "LFUserName", Email: "LFEmail"}, - }, }, - ContributorName: "ContributorNameValue", - ContributorEmail: "ContributorEmailValue", - CorporateConsoleURL: "CorporateConsoleURLValue", - CompanyID: "CompanyIDValue", + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + CorporateConsole: "CorporateConsoleURLValue", + }, + CLAManagers: []ClaManagerInfoParams{ + {LfUsername: "LFUserName", Email: "LFEmail"}, + }, + ContributorName: "ContributorNameValue", + ContributorEmail: "ContributorEmailValue", + CompanyID: "CompanyIDValue", } result, err := RenderTemplate(utils.V1, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go index fddde4530..364b83068 100644 --- a/cla-backend-go/emails/cla_manager_templates.go +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -3,14 +3,11 @@ package emails -import ( - "github.com/communitybridge/easycla/cla-backend-go/project" - "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" -) - // RemovedCLAManagerTemplateParams is email params for RemovedCLAManagerTemplate type RemovedCLAManagerTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams + CLAManagers []ClaManagerInfoParams } const ( @@ -32,27 +29,22 @@ const ( ) // RenderRemovedCLAManagerTemplate renders the RemovedCLAManagerTemplate -func RenderRemovedCLAManagerTemplate(repository projects_cla_groups.Repository, projectService project.Service, claGroupModelVersion, recipientName, companyName, projectSFID string, claManagers []ClaManagerInfoParams) (string, error) { - params := CLAManagerTemplateParams{ - RecipientName: recipientName, - CompanyName: companyName, - CLAManagers: claManagers, - } - - err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms, nil) +func RenderRemovedCLAManagerTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params RemovedCLAManagerTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) if err != nil { return "", err } + params.CLAGroupTemplateParams = claGroupParams - return RenderTemplate(claGroupModelVersion, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, RemovedCLAManagerTemplateParams{params}) + return RenderTemplate(claGroupModelVersion, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, params) } // RequestAccessToCLAManagersTemplateParams is email params for RequestAccessToCLAManagersTemplate type RequestAccessToCLAManagersTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams RequesterName string RequesterEmail string - CorporateURL string } const ( @@ -67,15 +59,27 @@ list of employees allowed to contribute to {{.Project.ExternalProjectName}} on b your company’s CLA Managers for {{.Project.ExternalProjectName}}.

    {{.RequesterName}} ({{.RequesterEmail}}) has requested to be added as another CLA Manager from {{.CompanyName}} for {{.Project.ExternalProjectName}}. This would permit them to maintain the lists of approved contributors and CLA Managers as well.

    -

    If you want to permit this, please log into the EasyCLA Corporate Console, +

    If you want to permit this, please log into the EasyCLA Corporate Console, select your company, then select the {{.Project.ExternalProjectName}} project. From the CLA Manager requests, you can approve this user as an additional CLA Manager.

    ` ) +// RenderRequestAccessToCLAManagersTemplate renders the RemovedCLAManagerTemplate +func RenderRequestAccessToCLAManagersTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params RequestAccessToCLAManagersTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, RequestAccessToCLAManagersTemplateName, RequestAccessToCLAManagersTemplate, params) +} + // RequestApprovedToCLAManagersTemplateParams is email params for RequestApprovedToCLAManagersTemplate type RequestApprovedToCLAManagersTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams RequesterName string RequesterEmail string } @@ -96,10 +100,21 @@ list of company’s CLA Managers for {{.Project.ExternalProjectName}}.

    ` ) +// RenderRequestApprovedToCLAManagersTemplate renders the RemovedCLAManagerTemplate +func RenderRequestApprovedToCLAManagersTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params RequestApprovedToCLAManagersTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, RequestApprovedToCLAManagersTemplateName, RequestApprovedToCLAManagersTemplate, params) +} + // RequestApprovedToRequesterTemplateParams email template params for RequestApprovedToRequesterTemplate type RequestApprovedToRequesterTemplateParams struct { - CLAManagerTemplateParams - CorporateURL string + CommonEmailParams + CLAGroupTemplateParams } const ( @@ -112,14 +127,26 @@ const (

    You have now been approved as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}. This means that you can now maintain the list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your company’s CLA Managers for {{.Project.ExternalProjectName}}.

    -

    To get started, please log into the EasyCLA Corporate Console, and select your +

    To get started, please log into the EasyCLA Corporate Console, and select your company and then the project {{.Project.ExternalProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

    ` ) +// RenderRequestApprovedToRequesterTemplate renders the RemovedCLAManagerTemplate +func RenderRequestApprovedToRequesterTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params RequestApprovedToRequesterTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, RequestApprovedToRequesterTemplateName, RequestApprovedToRequesterTemplate, params) +} + // RequestDeniedToCLAManagersTemplateParams is email params for RequestDeniedToCLAManagersTemplate type RequestDeniedToCLAManagersTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams RequesterName string RequesterEmail string } @@ -139,9 +166,21 @@ be able to maintain the list of employees allowed to contribute to {{.Project.Ex ` ) +// RenderRequestDeniedToCLAManagersTemplate renders the RemovedCLAManagerTemplate +func RenderRequestDeniedToCLAManagersTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params RequestDeniedToCLAManagersTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, RequestDeniedToCLAManagersTemplateName, RequestDeniedToCLAManagersTemplate, params) +} + // RequestDeniedToRequesterTemplateParams is email params for RequestDeniedToRequesterTemplate type RequestDeniedToRequesterTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams } const ( @@ -156,40 +195,53 @@ list of employees allowed to contribute to {{.Project.ExternalProjectName}} on b ` ) -// ClaManagerAddedEToUserTemplateParams is email params for ClaManagerAddedEToUserTemplate +// RenderRequestDeniedToRequesterTemplate renders the RemovedCLAManagerTemplate +func RenderRequestDeniedToRequesterTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params RequestDeniedToRequesterTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, RequestDeniedToRequesterTemplateName, RequestDeniedToRequesterTemplate, params) +} + +// ClaManagerAddedEToUserTemplateParams is email params type ClaManagerAddedEToUserTemplateParams struct { - CLAManagerTemplateParams - CorporateURL string + CommonEmailParams + CLAGroupTemplateParams } const ( - // ClaManagerAddedEToUserTemplateName is email template name for ClaManagerAddedEToUserTemplate - ClaManagerAddedEToUserTemplateName = "ClaManagerAddedEToUserTemplate" - // ClaManagerAddedEToUserTemplate is email template for + // ClaManagerAddedEToUserTemplateName is template name of ClaManagerAddedEToUserTemplate + ClaManagerAddedEToUserTemplateName = "V2ClaManagerAddedEToUserTemplate" + //ClaManagerAddedEToUserTemplate email template for cla manager v2 ClaManagerAddedEToUserTemplate = `

    Hello {{.RecipientName}},

    -

    This is a notification email from EasyCLA regarding the project {{.CLAGroupName}}.

    -

    You have been added as a CLA Manager from {{.CompanyName}} for the project {{.CLAGroupName}}. This means that you can now maintain the -list of employees allowed to contribute to {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your -company’s CLA Managers for {{.CLAGroupName}}.

    -

    To get started, please log into the EasyCLA Corporate Console, and select your -company and then the project {{.CLAGroupName}}. From here you will be able to edit the list of approved employees and CLA Managers.

    -` - //V2ClaManagerAddedEToUserTemplate email template for cla manager v2 - V2ClaManagerAddedEToUserTemplate = ` -

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}} and CLA Group {{.CLAGroupName}}.

    You have been added as a CLA Manager for the organization {{.CompanyName}} and the project {{.Project.ExternalProjectName}}. This means that you can now maintain the list of employees allowed to contribute to the project {{.Project.ExternalProjectName}} on behalf of your company, as well as view and manage the list of your company’s CLA Managers for the CLA Group {{.CLAGroupName}}.

    -

    To get started, please log into the EasyCLA Corporate Console, and select your +

    To get started, please log into the EasyCLA Corporate Console, and select your company and then the project {{.Project.ExternalProjectName}}. From here you will be able to edit the list of approved employees and CLA Managers.

    ` ) +// RenderClaManagerAddedEToUserTemplate renders the RemovedCLAManagerTemplate +func RenderClaManagerAddedEToUserTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params ClaManagerAddedEToUserTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, ClaManagerAddedEToUserTemplateName, ClaManagerAddedEToUserTemplate, params) +} + // ClaManagerAddedToCLAManagersTemplateParams is email params for ClaManagerAddedToCLAManagersTemplate type ClaManagerAddedToCLAManagersTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams Name string Email string } @@ -210,9 +262,21 @@ list of company’s CLA Managers for {{.Project.ExternalProjectName}}.

    ` ) +// RenderClaManagerAddedToCLAManagersTemplate renders the RemovedCLAManagerTemplate +func RenderClaManagerAddedToCLAManagersTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params ClaManagerAddedToCLAManagersTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, ClaManagerAddedToCLAManagersTemplateName, ClaManagerAddedToCLAManagersTemplate, params) +} + // ClaManagerDeletedToCLAManagersTemplateParams is template params for ClaManagerDeletedToCLAManagersTemplate type ClaManagerDeletedToCLAManagersTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams Name string Email string } @@ -227,3 +291,14 @@ const (

    {{.Name}} ({{.Email}}) has been removed as a CLA Manager from {{.CompanyName}} for the project {{.Project.ExternalProjectName}}.

    ` ) + +// RenderClaManagerDeletedToCLAManagersTemplate renders the RemovedCLAManagerTemplate +func RenderClaManagerDeletedToCLAManagersTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params ClaManagerDeletedToCLAManagersTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) + if err != nil { + return "", err + } + params.CLAGroupTemplateParams = claGroupParams + + return RenderTemplate(claGroupModelVersion, ClaManagerDeletedToCLAManagersTemplateName, ClaManagerDeletedToCLAManagersTemplate, params) +} diff --git a/cla-backend-go/emails/cla_manager_templates_test.go b/cla-backend-go/emails/cla_manager_templates_test.go index c23001ee0..f401af7dc 100644 --- a/cla-backend-go/emails/cla_manager_templates_test.go +++ b/cla-backend-go/emails/cla_manager_templates_test.go @@ -12,14 +12,17 @@ import ( func TestRemovedCLAManagerTemplate(t *testing.T) { params := RemovedCLAManagerTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", - CLAManagers: []ClaManagerInfoParams{ - {LfUsername: "LFUserName", Email: "LFEmail"}, - }, + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{ + {ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + }, + CLAManagers: []ClaManagerInfoParams{ + {LfUsername: "LFUserName", Email: "LFEmail"}, }, } @@ -33,12 +36,12 @@ func TestRemovedCLAManagerTemplate(t *testing.T) { // even if the foundation is set we should show the project name // because 0 child projects under the claGroup - params.CLAManagerTemplateParams.Project.FoundationName = "CNCF" + params.CLAGroupTemplateParams.Projects[0].FoundationName = "CNCF" result, err = RenderTemplate(utils.V1, RemovedCLAManagerTemplateName, RemovedCLAManagerTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the project JohnsProject") + assert.Contains(t, result, "for the project JohnsProject") // then we increase the child project count so we should get the FoundationName instead of project name params.ChildProjectCount = 2 @@ -51,15 +54,19 @@ func TestRemovedCLAManagerTemplate(t *testing.T) { func TestRequestAccessToCLAManagersTemplate(t *testing.T) { params := RequestAccessToCLAManagersTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{ + {ExternalProjectName: "JohnsProjectExternal"}, + }, + CLAGroupName: "JohnsProject", + CorporateConsole: "http://CorporateURL.com", + }, RequesterName: "RequesterName", RequesterEmail: "RequesterEmail", - CorporateURL: "http://CorporateURL.com", } result, err := RenderTemplate(utils.V1, RequestAccessToCLAManagersTemplateName, RequestAccessToCLAManagersTemplate, @@ -79,12 +86,16 @@ func TestRequestAccessToCLAManagersTemplate(t *testing.T) { func TestRequestApprovedToCLAManagersTemplate(t *testing.T) { params := RequestApprovedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{ + {ExternalProjectName: "JohnsProjectExternal"}, + }, + CLAGroupName: "JohnsProject", + }, RequesterName: "RequesterName", RequesterEmail: "RequesterEmail", } @@ -102,13 +113,15 @@ func TestRequestApprovedToCLAManagersTemplate(t *testing.T) { func TestRequestApprovedToRequesterTemplateParams(t *testing.T) { params := RequestApprovedToRequesterTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, - CorporateURL: "http://CorporateURL.com", + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + CorporateConsole: "http://CorporateURL.com", + }, } result, err := RenderTemplate(utils.V1, RequestApprovedToRequesterTemplateName, RequestApprovedToRequesterTemplate, @@ -125,12 +138,14 @@ func TestRequestApprovedToRequesterTemplateParams(t *testing.T) { func TestRequestDeniedToCLAManagersTemplate(t *testing.T) { params := RequestDeniedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + }, RequesterName: "RequesterName", RequesterEmail: "RequesterEmail", } @@ -147,12 +162,14 @@ func TestRequestDeniedToCLAManagersTemplate(t *testing.T) { func TestRequestDeniedToRequesterTemplate(t *testing.T) { params := RequestDeniedToRequesterTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + }, } result, err := RenderTemplate(utils.V1, RequestDeniedToRequesterTemplateName, RequestDeniedToRequesterTemplate, @@ -166,35 +183,39 @@ func TestRequestDeniedToRequesterTemplate(t *testing.T) { func TestClaManagerAddedEToUserTemplate(t *testing.T) { params := ClaManagerAddedEToUserTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, - CorporateURL: "http://CorporateURL.com", + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + CorporateConsole: "http://CorporateURL.com", + }, } result, err := RenderTemplate(utils.V1, ClaManagerAddedEToUserTemplateName, ClaManagerAddedEToUserTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello JohnsClaManager") - assert.Contains(t, result, "regarding the project JohnsProject") - assert.Contains(t, result, "CLA Manager from JohnsCompany for the project JohnsProject") - assert.Contains(t, result, "allowed to contribute to JohnsProject") - assert.Contains(t, result, "CLA Managers for JohnsProject") + assert.Contains(t, result, "regarding the project JohnsProjectExternal") + assert.Contains(t, result, "CLA Manager for the organization JohnsCompany and the project JohnsProjectExternal") + assert.Contains(t, result, "allowed to contribute to the project JohnsProjectExternal") + assert.Contains(t, result, "CLA Managers for the CLA Group JohnsProject") assert.Contains(t, result, "") assert.Contains(t, result, "and then the project JohnsProject") } func TestClaManagerAddedToCLAManagersTemplate(t *testing.T) { params := ClaManagerAddedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + }, Name: "John", Email: "john@example.com", } @@ -213,12 +234,14 @@ func TestClaManagerAddedToCLAManagersTemplate(t *testing.T) { func TestClaManagerDeletedToCLAManagersTemplate(t *testing.T) { params := ClaManagerDeletedToCLAManagersTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ + CommonEmailParams: CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: CLAProjectParams{ExternalProjectName: "JohnsProjectExternal"}, - CLAGroupName: "JohnsProject", CompanyName: "JohnsCompany", }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, + CLAGroupName: "JohnsProject", + }, Name: "John", Email: "john@example.com", } diff --git a/cla-backend-go/emails/params.go b/cla-backend-go/emails/params.go index fca576309..510003874 100644 --- a/cla-backend-go/emails/params.go +++ b/cla-backend-go/emails/params.go @@ -8,10 +8,18 @@ import ( "html/template" "net/url" "path" + "strings" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) +// CommonEmailParams are part of almost every email it's sent from the system +type CommonEmailParams struct { + RecipientName string + RecipientAddress string + CompanyName string +} + // ClaManagerInfoParams represents the CLAManagerInfo used inside of the Email Templates type ClaManagerInfoParams struct { LfUsername string @@ -56,39 +64,49 @@ func (p CLAProjectParams) GetProjectFullURL() template.HTML { return template.HTML(fmt.Sprintf(fullURLHtml, projectConsolePathURL, p.ExternalProjectName)) } -// CLAManagerTemplateParams includes the params for the CLAManagerTemplateParams -type CLAManagerTemplateParams struct { - RecipientName string - CompanyName string - CLAGroupName string - Project CLAProjectParams - CLAManagers []ClaManagerInfoParams +// CLAGroupTemplateParams includes the params for the CLAGroupTemplateParams +type CLAGroupTemplateParams struct { + CorporateConsole string + CLAGroupName string // ChildProjectCount indicates how many childProjects are under this CLAGroup // this is important for some of the email rendering knowing if claGroup has // multiple children ChildProjectCount int -} - -// ApprovalTemplateParams details approval fields for contributor -type ApprovalTemplateParams struct { - RecipientName string - CompanyName string - CLAGroupName string - Approver string - Projects []CLAProjectParams + Projects []CLAProjectParams } // GetProjectNameOrFoundation returns if the foundationName is set it gets back // the foundation Name otherwise the ProjectName is returned -func (claParams CLAManagerTemplateParams) GetProjectNameOrFoundation() string { - if claParams.ChildProjectCount == 0 { - return claParams.Project.ExternalProjectName +func (claParams CLAGroupTemplateParams) GetProjectNameOrFoundation() string { + project := claParams.Projects[0] + if claParams.ChildProjectCount == 1 { + return claParams.Projects[0].ExternalProjectName } // if multiple return the foundation if present - if claParams.Project.FoundationName != "" { - return claParams.Project.FoundationName + if project.FoundationName != "" { + return project.FoundationName } //default to project name if nothing works - return claParams.Project.ExternalProjectName + return project.ExternalProjectName +} + +// Project is used generally in v1 templates because the matching there was 1:1 +// it will returns the first element from the projects list +func (claParams CLAGroupTemplateParams) Project() CLAProjectParams { + return claParams.Projects[0] +} + +// GetProjectsOrProject gets the first if single or all of them comma separated +func (claParams CLAGroupTemplateParams) GetProjectsOrProject() string { + if len(claParams.Projects) == 1 { + return claParams.Projects[0].ExternalProjectName + } + + var projectNames []string + for _, p := range claParams.Projects { + projectNames = append(projectNames, p.ExternalProjectName) + } + + return strings.Join(projectNames, ", ") } diff --git a/cla-backend-go/emails/prefill.go b/cla-backend-go/emails/prefill.go new file mode 100644 index 000000000..df2f26f22 --- /dev/null +++ b/cla-backend-go/emails/prefill.go @@ -0,0 +1,148 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "context" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" +) + +// EmailTemplateService has utility functions needed to pre-fill the email params +type EmailTemplateService interface { + PrefillV2CLAProjectParams(projectSFIDs []string) ([]CLAProjectParams, error) + GetCLAGroupTemplateParamsFromProjectSFID(claGroupVersion, projectSFID string) (CLAGroupTemplateParams, error) +} + +type emailTemplateServiceProvider struct { + claGroupRepository project.ProjectRepository + repository projects_cla_groups.Repository + projectService project.Service + corporateConsoleV1 string + corporateConsoleV2 string +} + +// NewEmailTemplateService creates a new instance of email template service +func NewEmailTemplateService(repository projects_cla_groups.Repository, projectService project.Service, corporateConsoleV1, corporateConsoleV2 string) EmailTemplateService { + return &emailTemplateServiceProvider{ + repository: repository, + projectService: projectService, + corporateConsoleV1: corporateConsoleV1, + corporateConsoleV2: corporateConsoleV2, + } +} + +// PrefillV2CLAProjectParams for each supplied projectSFIDs gets the claGroup info + checks if the project is signed at +// foundation level which is important for email rendering +func (s *emailTemplateServiceProvider) PrefillV2CLAProjectParams(projectSFIDs []string) ([]CLAProjectParams, error) { + if len(projectSFIDs) == 0 { + return nil, nil + } + + var claProjectParams []CLAProjectParams + // keeping a cache so we can safe some of the remote svc calls + signedAtFoundationLevelCache := map[string]bool{} + for _, pSFID := range projectSFIDs { + projectCLAGroup, err := s.repository.GetClaGroupIDForProject(pSFID) + if err != nil { + return nil, fmt.Errorf("fetching project : %s failed: %v", pSFID, err) + } + + params := CLAProjectParams{ + ExternalProjectName: projectCLAGroup.ProjectName, + ProjectSFID: pSFID, + FoundationName: projectCLAGroup.FoundationName, + FoundationSFID: projectCLAGroup.FoundationSFID, + CorporateConsole: s.corporateConsoleV2, + } + + signedResult, err := s.projectService.SignedAtFoundationLevel(context.Background(), projectCLAGroup.FoundationSFID) + if err != nil { + return nil, fmt.Errorf("fetching the SignedAtFoundationLevel for foundation : %s failed : %v", projectCLAGroup.FoundationSFID, err) + } + params.SignedAtFoundationLevel = signedResult + signedAtFoundationLevelCache[projectCLAGroup.FoundationSFID] = signedResult + + claProjectParams = append(claProjectParams, params) + } + + return claProjectParams, nil +} + +// GetCLAGroupTemplateParamsFromProjectSFID creates CLAGroupTemplateParams from projectSFID +func (s *emailTemplateServiceProvider) GetCLAGroupTemplateParamsFromProjectSFID(claGroupVersion, projectSFID string) (CLAGroupTemplateParams, error) { + if utils.V2 == claGroupVersion { + return s.getV2CLAGroupTemplateParamsFromProjectSFID(projectSFID) + } + return s.getV1CLAGroupTemplateParamsFromProjectSFID(projectSFID) +} + +func (s *emailTemplateServiceProvider) getV2CLAGroupTemplateParamsFromProjectSFID(projectSFID string) (CLAGroupTemplateParams, error) { + projectCLAGroup, err := s.repository.GetClaGroupIDForProject(projectSFID) + if err != nil { + return CLAGroupTemplateParams{}, err + } + + params := &CLAGroupTemplateParams{} + params.CLAGroupName = projectCLAGroup.ClaGroupName + params.CorporateConsole = s.corporateConsoleV2 + + projects, err := s.repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) + if err != nil { + return CLAGroupTemplateParams{}, fmt.Errorf("getProjectsIdsForClaGroup failed : %w", err) + } + + params.ChildProjectCount = len(projects) + var projectSFIDs []string + for _, p := range projects { + projectSFIDs = append(projectSFIDs, p.ProjectSFID) + } + + projectParams, err := s.PrefillV2CLAProjectParams(projectSFIDs) + if err != nil { + return CLAGroupTemplateParams{}, fmt.Errorf("prefilling cla project params failed : %v", err) + } + params.Projects = projectParams + return *params, nil +} + +func (s *emailTemplateServiceProvider) getV1CLAGroupTemplateParamsFromProjectSFID(projectSFID string) (CLAGroupTemplateParams, error) { + claGroup, err := s.claGroupRepository.GetClaGroupByProjectSFID(context.Background(), projectSFID, true) + if err != nil { + return CLAGroupTemplateParams{}, err + } + + ps := v2ProjectService.GetClient() + projectSF, projectErr := ps.GetProject(projectSFID) + if projectErr != nil { + return CLAGroupTemplateParams{}, fmt.Errorf("project service lookup error for SFID: %s, error : %+v", projectSFID, projectErr) + } + + signedResult, err := s.projectService.SignedAtFoundationLevel(context.Background(), claGroup.FoundationSFID) + if err != nil { + return CLAGroupTemplateParams{}, fmt.Errorf("fetching the SignedAtFoundationLevel for foundation : %s failed : %v", claGroup.FoundationSFID, err) + } + + return CLAGroupTemplateParams{ + CorporateConsole: s.corporateConsoleV1, + CLAGroupName: claGroup.ProjectName, + ChildProjectCount: 1, + Projects: []CLAProjectParams{ + { + ExternalProjectName: projectSF.Name, + ProjectSFID: projectSFID, + FoundationName: projectSF.Foundation.Name, + FoundationSFID: projectSF.Foundation.ID, + SignedAtFoundationLevel: signedResult, + CorporateConsole: s.corporateConsoleV1, + }, + }, + }, nil + +} diff --git a/cla-backend-go/emails/uitls.go b/cla-backend-go/emails/uitls.go deleted file mode 100644 index 2a3f909cf..000000000 --- a/cla-backend-go/emails/uitls.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package emails - -import ( - "context" - "errors" - "fmt" - - log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/communitybridge/easycla/cla-backend-go/project" - "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" -) - -// PrefillCLAManagerTemplateParamsFromClaGroup fetches data from projects_cla_groups.Repository to prefill some of the fields -// of CLAManagerTemplateParams, like childCount and FoundationName and etc -func PrefillCLAManagerTemplateParamsFromClaGroup(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params *CLAManagerTemplateParams, corporateConsole *string) error { - projectCLAGroup, err := repository.GetClaGroupIDForProject(projectSFID) - if err != nil { - if errors.Is(err, projects_cla_groups.ErrProjectNotAssociatedWithClaGroup) { - log.Warnf("no cla group was found for externalProjectID : %s skipping the prefill", projectSFID) - return nil - } - return err - } - ctx := context.Background() - - //Check if signed at foundationLevel - signedAtFoundationLevel, err := projectService.SignedAtFoundationLevel(ctx, projectCLAGroup.FoundationSFID) - if err != nil { - return err - } - - params.CLAGroupName = projectCLAGroup.ClaGroupName - params.Project = CLAProjectParams{ - ExternalProjectName: projectCLAGroup.ProjectName, - ProjectSFID: projectSFID, - FoundationName: projectCLAGroup.FoundationName, - FoundationSFID: projectCLAGroup.FoundationSFID, - SignedAtFoundationLevel: signedAtFoundationLevel, - } - if corporateConsole != nil { - params.Project.CorporateConsole = *corporateConsole - } - projects, err := repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) - if err != nil { - return err - } - - params.ChildProjectCount = len(projects) - return nil -} - -// PrefillCLAProjectParams for each supplied projectSFIDs gets the claGroup info + checks if the project is signed at -// foundation level which is important for email rendering -func PrefillCLAProjectParams(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, corporateConsole string) ([]CLAProjectParams, error) { - if len(projectSFIDs) == 0 { - return nil, nil - } - - var claProjectParams []CLAProjectParams - // keeping a cache so we can safe some of the remote svc calls - signedAtFoundationLevelCache := map[string]bool{} - for _, pSFID := range projectSFIDs { - projectCLAGroup, err := repository.GetClaGroupIDForProject(pSFID) - if err != nil { - return nil, fmt.Errorf("fetching project : %s failed: %v", pSFID, err) - } - - params := CLAProjectParams{ - ExternalProjectName: projectCLAGroup.ProjectName, - ProjectSFID: pSFID, - FoundationName: projectCLAGroup.FoundationName, - FoundationSFID: projectCLAGroup.FoundationSFID, - CorporateConsole: corporateConsole, - } - signed, found := signedAtFoundationLevelCache[projectCLAGroup.FoundationSFID] - if found { - params.SignedAtFoundationLevel = signed - } - - signedResult, err := projectService.SignedAtFoundationLevel(context.Background(), projectCLAGroup.FoundationSFID) - if err != nil { - return nil, fmt.Errorf("fetching the SignedAtFoundationLevel for foundation : %s failed : %v", projectCLAGroup.FoundationSFID, err) - } - params.SignedAtFoundationLevel = signedResult - signedAtFoundationLevelCache[projectCLAGroup.FoundationSFID] = signedResult - - claProjectParams = append(claProjectParams, params) - } - - return claProjectParams, nil -} diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 39a137f47..3dc314f75 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -4,11 +4,6 @@ package emails import ( - "strings" - - "github.com/communitybridge/easycla/cla-backend-go/project" - - "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" ) @@ -22,25 +17,10 @@ type Contributor struct { // V2ContributorApprovalRequestTemplateParams is email template params for V2ContributorApprovalRequestTemplate type V2ContributorApprovalRequestTemplateParams struct { - CLAManagerTemplateParams - SigningEntityName string - UserDetails string - CorporateConsoleV2URL string - Projects []CLAProjectParams -} - -// GetProjectsOrProject returns the single Project or comma separated projects if more than one -func (p V2ContributorApprovalRequestTemplateParams) GetProjectsOrProject() string { - if len(p.Projects) == 1 { - return p.Projects[0].ExternalProjectName - } - - var projectNames []string - for _, p := range p.Projects { - projectNames = append(projectNames, p.ExternalProjectName) - } - - return strings.Join(projectNames, ", ") + CommonEmailParams + CLAGroupTemplateParams + SigningEntityName string + UserDetails string } const ( @@ -52,30 +32,29 @@ const (

    This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

    The following contributor would like to submit a contribution to the projects(s): {{.GetProjectsOrProject}} and is requesting to be added to the approval list as a contributor for your organization:

    {{.UserDetails}}

    -

    Approval can be done at {{.CorporateConsoleV2URL}}. Visit any of the project(s):{{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}} and add the contributor to the approved list.

    +

    Approval can be done at {{.CorporateConsole}}. Visit any of the project(s):{{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}} and add the contributor to the approved list.

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

    ` ) // RenderV2ContributorApprovalRequestTemplate renders V2ContributorApprovalRequestTemplate -func RenderV2ContributorApprovalRequestTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ContributorApprovalRequestTemplateParams) (string, error) { +func RenderV2ContributorApprovalRequestTemplate(svc EmailTemplateService, projectSFIDs []string, params V2ContributorApprovalRequestTemplateParams) (string, error) { - projectParams, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsoleV2URL) + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFIDs[0]) if err != nil { return "", err } - - params.Projects = projectParams + params.CLAGroupTemplateParams = claGroupParams return RenderTemplate(utils.V2, V2ContributorApprovalRequestTemplateName, V2ContributorApprovalRequestTemplate, params) } // V2OrgAdminTemplateParams is email params for V2OrgAdminTemplate type V2OrgAdminTemplateParams struct { - CLAManagerTemplateParams - SenderName string - SenderEmail string - CorporateConsole string + CommonEmailParams + CLAGroupTemplateParams + SenderName string + SenderEmail string } const ( @@ -97,27 +76,20 @@ Either you or someone whom to designate from your company can login to this port ) // RenderV2OrgAdminTemplate renders V2OrgAdminTemplate -func RenderV2OrgAdminTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params V2OrgAdminTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms.CLAManagerTemplateParams, nil); err != nil { - return "", err - } - - projectParams, err := PrefillCLAProjectParams(repository, projectService, []string{projectSFID}, params.CorporateConsole) +func RenderV2OrgAdminTemplate(svc EmailTemplateService, projectSFID string, params V2OrgAdminTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFID) if err != nil { return "", err } - - params.Project = projectParams[0] - + params.CLAGroupTemplateParams = claGroupParams return RenderTemplate(utils.V2, V2OrgAdminTemplateName, V2OrgAdminTemplate, params) } // V2ContributorToOrgAdminTemplateParams is email template params for V2ContributorToOrgAdminTemplate type V2ContributorToOrgAdminTemplateParams struct { - CLAManagerTemplateParams - Projects []CLAProjectParams - UserDetails string - CorporateConsole string + CommonEmailParams + CLAGroupTemplateParams + UserDetails string } const ( @@ -135,14 +107,13 @@ const ( ) // RenderV2ContributorToOrgAdminTemplate renders V2ContributorToOrgAdminTemplate -func RenderV2ContributorToOrgAdminTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ContributorToOrgAdminTemplateParams) (string, error) { +func RenderV2ContributorToOrgAdminTemplate(svc EmailTemplateService, projectSFIDs []string, params V2ContributorToOrgAdminTemplateParams) (string, error) { // prefill the projects data - projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsole) + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFIDs[0]) if err != nil { return "", err } - - params.Projects = projects + params.CLAGroupTemplateParams = claGroupParams return RenderTemplate(utils.V2, V2ContributorToOrgAdminTemplateName, V2ContributorToOrgAdminTemplate, params) @@ -150,10 +121,10 @@ func RenderV2ContributorToOrgAdminTemplate(repository projects_cla_groups.Reposi // V2CLAManagerDesigneeCorporateTemplateParams is email params for V2CLAManagerDesigneeCorporateTemplate type V2CLAManagerDesigneeCorporateTemplateParams struct { - CLAManagerTemplateParams - SenderName string - SenderEmail string - CorporateConsole string + CommonEmailParams + CLAGroupTemplateParams + SenderName string + SenderEmail string } const ( @@ -175,43 +146,21 @@ Either you or someone whom you designate from your company can login to this por ) // RenderV2CLAManagerDesigneeCorporateTemplate renders V2CLAManagerDesigneeCorporateTemplate -func RenderV2CLAManagerDesigneeCorporateTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFID string, params V2CLAManagerDesigneeCorporateTemplateParams) (string, error) { - if err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms.CLAManagerTemplateParams, nil); err != nil { - return "", err - } - - projects, err := PrefillCLAProjectParams(repository, projectService, []string{projectSFID}, params.CorporateConsole) +func RenderV2CLAManagerDesigneeCorporateTemplate(emailSvc EmailTemplateService, projectSFID string, params V2CLAManagerDesigneeCorporateTemplateParams) (string, error) { + claGroupParams, err := emailSvc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFID) if err != nil { return "", err } - - // assing the prefilled project - params.Project = projects[0] + params.CLAGroupTemplateParams = claGroupParams return RenderTemplate(utils.V2, V2CLAManagerDesigneeCorporateTemplateName, V2CLAManagerDesigneeCorporateTemplate, params) } // V2ToCLAManagerDesigneeTemplateParams is email params for V2ToCLAManagerDesigneeTemplate type V2ToCLAManagerDesigneeTemplateParams struct { - RecipientName string - Projects []CLAProjectParams - Contributor Contributor - CorporateConsole string - CompanyName string -} - -// GetProjectsOrProject returns the single Project or comma separated projects if more than one -func (p V2ToCLAManagerDesigneeTemplateParams) GetProjectsOrProject() string { - if len(p.Projects) == 1 { - return p.Projects[0].ExternalProjectName - } - - var projectNames []string - for _, p := range p.Projects { - projectNames = append(projectNames, p.ExternalProjectName) - } - - return strings.Join(projectNames, ", ") + CommonEmailParams + CLAGroupTemplateParams + Contributor Contributor } const ( @@ -233,27 +182,17 @@ const ( ) // RenderV2ToCLAManagerDesigneeTemplate renders V2ToCLAManagerDesigneeTemplate -func RenderV2ToCLAManagerDesigneeTemplate(repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string, params V2ToCLAManagerDesigneeTemplateParams, template string, templateName string) (string, error) { - // prefill the projects data - projects, err := PrefillCLAProjectParams(repository, projectService, projectSFIDs, params.CorporateConsole) +func RenderV2ToCLAManagerDesigneeTemplate(svc EmailTemplateService, projectSFIDs []string, params V2ToCLAManagerDesigneeTemplateParams, template string, templateName string) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFIDs[0]) if err != nil { return "", err } - - params.Projects = projects + params.CLAGroupTemplateParams = claGroupParams return RenderTemplate(utils.V2, templateName, template, params) } -// V2DesigneeToUserWithNoLFIDTemplateParams is email params for V2DesigneeToUserWithNoLFIDTemplate -type V2DesigneeToUserWithNoLFIDTemplateParams struct { - CLAManagerTemplateParams - RequesterUserName string - RequesterEmail string - CorporateConsole string -} - const ( // V2DesigneeToUserWithNoLFIDTemplateName is email template name for V2DesigneeToUserWithNoLFIDTemplate V2DesigneeToUserWithNoLFIDTemplateName = "V2DesigneeToUserWithNoLFIDTemplateName" @@ -277,7 +216,8 @@ The requester has stated that you would be the initial CLA Manager for this CCLA // V2CLAManagerToUserWithNoLFIDTemplateParams is email params for V2CLAManagerToUserWithNoLFIDTemplate type V2CLAManagerToUserWithNoLFIDTemplateParams struct { - CLAManagerTemplateParams + CommonEmailParams + CLAGroupTemplateParams RequesterUserName string RequesterEmail string Projects []CLAProjectParams @@ -303,22 +243,12 @@ const ( ) // RenderV2CLAManagerToUserWithNoLFIDTemplate renders V2CLAManagerToUserWithNoLFIDTemplate -func RenderV2CLAManagerToUserWithNoLFIDTemplate(repository projects_cla_groups.Repository, projectService project.Service, recipientName, projectName, projectSFID, requesterName, requesterEmail, corporateConsole string) (string, error) { - params := V2CLAManagerToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: CLAManagerTemplateParams{ - RecipientName: recipientName, - Project: CLAProjectParams{ - ExternalProjectName: projectName, - }, - }, - RequesterUserName: requesterName, - RequesterEmail: requesterEmail, - } - - err := PrefillCLAManagerTemplateParamsFromClaGroup(repository, projectService, projectSFID, ¶ms.CLAManagerTemplateParams, &corporateConsole) +func RenderV2CLAManagerToUserWithNoLFIDTemplate(svc EmailTemplateService, projectSFID string, params V2CLAManagerToUserWithNoLFIDTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFID) if err != nil { return "", err } + params.CLAGroupTemplateParams = claGroupParams body, err := RenderTemplate(utils.V2, V2CLAManagerToUserWithNoLFIDTemplateName, V2CLAManagerToUserWithNoLFIDTemplate, diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index d72c4869f..21c655640 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -14,17 +14,18 @@ import ( func TestV2ContributorApprovalRequestTemplate(t *testing.T) { params := emails.V2ContributorApprovalRequestTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProject"}, CompanyName: "JohnsCompany", }, - Projects: []emails.CLAProjectParams{ - {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, - {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + Projects: []emails.CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, + CorporateConsole: "http://CorporateConsoleV2URL.com", }, - UserDetails: "UserDetailsValue", - CorporateConsoleV2URL: "http://CorporateConsoleV2URL.com", + UserDetails: "UserDetailsValue", } result, err := emails.RenderTemplate(utils.V1, emails.V2ContributorApprovalRequestTemplateName, emails.V2ContributorApprovalRequestTemplate, @@ -50,20 +51,22 @@ func TestV2ContributorApprovalRequestTemplate(t *testing.T) { func TestV2OrgAdminTemplate(t *testing.T) { params := emails.V2OrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + CompanyName: "JohnsCompany", RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ + }, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + Projects: []emails.CLAProjectParams{{ ExternalProjectName: "JohnsProject", ProjectSFID: "ProjectSFIDValue", FoundationSFID: "FoundationSFIDValue", CorporateConsole: "http://CorporateConsole.com", - }, - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + }}, + CLAGroupName: "JohnsCLAGroupName", + CorporateConsole: "http://CorporateConsole.com", }, - SenderName: "SenderNameValue", - SenderEmail: "SenderEmailValue", - CorporateConsole: "http://CorporateConsole.com", + SenderName: "SenderNameValue", + SenderEmail: "SenderEmailValue", } result, err := emails.RenderTemplate(utils.V1, emails.V2OrgAdminTemplateName, emails.V2OrgAdminTemplate, @@ -80,18 +83,20 @@ func TestV2OrgAdminTemplate(t *testing.T) { func TestV2ContributorToOrgAdminTemplate(t *testing.T) { params := emails.V2ContributorToOrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProject"}, - CLAGroupName: "JohnsCLAGroupName", CompanyName: "JohnsCompany", }, - Projects: []emails.CLAProjectParams{ - {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, - {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + Projects: []emails.CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, + CLAGroupName: "JohnsCLAGroupName", + CorporateConsole: "http://CorporateConsole.com", }, - UserDetails: "UserDetailsValue", - CorporateConsole: "http://CorporateConsole.com", + + UserDetails: "UserDetailsValue", } result, err := emails.RenderTemplate(utils.V1, emails.V2ContributorToOrgAdminTemplateName, emails.V2ContributorToOrgAdminTemplate, @@ -107,20 +112,22 @@ func TestV2ContributorToOrgAdminTemplate(t *testing.T) { func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { params := emails.V2CLAManagerDesigneeCorporateTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ + CompanyName: "JohnsCompany", + }, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + CLAGroupName: "JohnsCLAGroupName", + Projects: []emails.CLAProjectParams{{ ExternalProjectName: "JohnsProject", FoundationSFID: "FoundationSFIDValue", ProjectSFID: "ProjectSFIDValue", CorporateConsole: "http://CorporateConsole.com", - }, - CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", + }}, + CorporateConsole: "http://CorporateConsole.com", }, - SenderName: "SenderNameValue", - SenderEmail: "SenderEmailValue", - CorporateConsole: "http://CorporateConsole.com", + SenderName: "SenderNameValue", + SenderEmail: "SenderEmailValue", } result, err := emails.RenderTemplate(utils.V1, emails.V2CLAManagerDesigneeCorporateTemplateName, emails.V2CLAManagerDesigneeCorporateTemplate, @@ -137,10 +144,15 @@ func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { params := emails.V2ToCLAManagerDesigneeTemplateParams{ - RecipientName: "JohnsClaManager", - Projects: []emails.CLAProjectParams{ - {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, - {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: "JohnsClaManager", + }, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + Projects: []emails.CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "http://CorporateConsole.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "http://CorporateConsole.com"}, + }, + CorporateConsole: "http://CorporateConsole.com", }, Contributor: emails.Contributor{ Email: "ContributorEmailValue", @@ -148,7 +160,6 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { EmailLabel: utils.EmailLabel, UsernameLabel: utils.UserLabel, }, - CorporateConsole: "http://CorporateConsole.com", } result, err := emails.RenderTemplate(utils.V1, emails.V2ToCLAManagerDesigneeTemplateName, emails.V2ToCLAManagerDesigneeTemplate, @@ -177,18 +188,23 @@ func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { params := emails.V2ToCLAManagerDesigneeTemplateParams{ - RecipientName: "JohnsClaManager", - Projects: []emails.CLAProjectParams{ - {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "https://corporate.dev.lfcla.com"}, - {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "https://corporate.dev.lfcla.com"}, + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: "JohnsClaManager", }, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + Projects: []emails.CLAProjectParams{ + {ExternalProjectName: "Project1", ProjectSFID: "ProjectSFID1", FoundationSFID: "FoundationSFID1", CorporateConsole: "https://corporate.dev.lfcla.com"}, + {ExternalProjectName: "Project2", ProjectSFID: "ProjectSFID2", FoundationSFID: "FoundationSFID2", CorporateConsole: "https://corporate.dev.lfcla.com"}, + }, + CorporateConsole: "https://corporate.dev.lfcla.com", + }, + Contributor: emails.Contributor{ Email: "ContributorEmail", Username: "ContributorUsername", EmailLabel: utils.EmailLabel, UsernameLabel: utils.UserLabel, }, - CorporateConsole: "https://corporate.dev.lfcla.com", } result, err := emails.RenderTemplate(utils.V1, emails.V2DesigneeToUserWithNoLFIDTemplateName, emails.V2DesigneeToUserWithNoLFIDTemplate, @@ -212,16 +228,18 @@ func TestV2DesigneeToUserWithNoLFIDTemplate(t *testing.T) { func TestV2CLAManagerToUserWithNoLFIDTemplate(t *testing.T) { params := emails.V2CLAManagerToUserWithNoLFIDTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ RecipientName: "JohnsClaManager", - Project: emails.CLAProjectParams{ExternalProjectName: "JohnsProjectExternal", + CompanyName: "JohnsCompany", + }, + CLAGroupTemplateParams: emails.CLAGroupTemplateParams{ + Projects: []emails.CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal", CorporateConsole: "http://CorporateConsole.com", SignedAtFoundationLevel: false, ProjectSFID: "ProjectSFID", FoundationSFID: "FoundationSFID", - }, + }}, CLAGroupName: "JohnsCLAGroupName", - CompanyName: "JohnsCompany", }, RequesterUserName: "RequesterUserNameValue", RequesterEmail: "RequesterEmailValue", diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index 01158dc65..557e0fa20 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -8,13 +8,12 @@ import ( "fmt" "strings" + v2AcsService "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" + "github.com/communitybridge/easycla/cla-backend-go/emails" v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/communitybridge/easycla/cla-backend-go/project" - "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" - v2AcsService "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" "github.com/sirupsen/logrus" ) @@ -28,8 +27,79 @@ type EmailToCLAManagerModel struct { CorporateConsoleURL string } +// ToCLAManagerDesigneeCorporateModel data model for sending emails +type ToCLAManagerDesigneeCorporateModel struct { + companyName string + projectSFID string + projectName string + designeeEmail string + designeeName string + senderEmail string + senderName string +} + +// ToCLAManagerDesigneeModel data model for sending emails +type ToCLAManagerDesigneeModel struct { + designeeName string + designeeEmail string + companyName string + projectNames []string + projectSFIDs []string + contributorModel emails.Contributor + contributorEmail string + contributorName string +} + +// DesigneeEmailToUserWithNoLFIDModel data model for sending emails +type DesigneeEmailToUserWithNoLFIDModel struct { + userWithNoLFIDName string + userWithNoLFIDEmail string + requesterUsername string + requesterEmail string + projectNames []string + projectSFIDs []string + foundationSFID string + role string + companyName string + organizationID string +} + +// EmailToUserWithNoLFIDModel data model for sending emails +type EmailToUserWithNoLFIDModel struct { + projectName string + requesterUsername string + requesterEmail string + userWithNoLFIDName string + userWithNoLFIDEmail string + organizationID string + companyName string + projectID string + role string +} + +// EmailToOrgAdminModel data model for sending emails +type EmailToOrgAdminModel struct { + adminEmail string + adminName string + companyName string + projectName string + projectSFID string + senderName string + senderEmail string +} + +// ContributorEmailToOrgAdminModel data model for sending emails +type ContributorEmailToOrgAdminModel struct { + adminEmail string + adminName string + companyName string + projectSFIDs []string + contributor *v1Models.User + userDetails string +} + // SendEmailToCLAManager handles sending an email to the specified CLA Manager -func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel, repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string) { +func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel, projectSFIDs []string) { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -47,14 +117,13 @@ func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAMa subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(input.Contributor)) recipients := []string{input.CLAManagerEmail} - body, err := emails.RenderV2ContributorApprovalRequestTemplate(repository, projectService, projectSFIDs, emails.V2ContributorApprovalRequestTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ + body, err := emails.RenderV2ContributorApprovalRequestTemplate(s.emailSvc, projectSFIDs, emails.V2ContributorApprovalRequestTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.CLAManagerName, CompanyName: input.CompanyName, }, - SigningEntityName: input.CompanyName, - UserDetails: getFormattedUserDetails(input.Contributor), - CorporateConsoleV2URL: input.CorporateConsoleURL, + SigningEntityName: input.CompanyName, + UserDetails: getFormattedUserDetails(input.Contributor), }) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering email template: %s", emails.V2ContributorApprovalRequestTemplateName) @@ -71,31 +140,28 @@ func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAMa } // SendEmailToOrgAdmin sends an email to the organization admin -func (s *service) SendEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) { +func (s *service) SendEmailToOrgAdmin(ctx context.Context, input EmailToOrgAdminModel) { f := logrus.Fields{ - "functionName": "cla_manager.service.SendEmailToOrgAdmin", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "adminEmail": adminEmail, - "adminName": adminName, - "companyName": companyName, - "projectName": projectName, - "projectSFID": projectSFID, - "senderName": senderName, - "senderEmail": senderEmail, - "corporateConsoleURL": corporateConsole, + "functionName": "cla_manager.service.SendEmailToOrgAdmin", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "adminEmail": input.adminEmail, + "adminName": input.adminName, + "companyName": input.companyName, + "projectName": input.projectName, + "projectSFID": input.projectSFID, + "senderName": input.senderName, + "senderEmail": input.senderEmail, } - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) - recipients := []string{adminEmail} - body, err := emails.RenderV2OrgAdminTemplate(repository, projectService, projectSFID, emails.V2OrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: adminName, - CompanyName: companyName, - Project: emails.CLAProjectParams{ExternalProjectName: projectName}, + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", input.companyName) + recipients := []string{input.adminEmail} + body, err := emails.RenderV2OrgAdminTemplate(s.emailSvc, input.projectSFID, emails.V2OrgAdminTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: input.adminName, + CompanyName: input.companyName, }, - SenderName: senderName, - SenderEmail: senderEmail, - CorporateConsole: corporateConsole, + SenderName: input.senderName, + SenderEmail: input.senderEmail, }) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering email template : %s failed : %v", emails.V2OrgAdminTemplateName, err) @@ -109,31 +175,29 @@ func (s *service) SendEmailToOrgAdmin(ctx context.Context, repository projects_c } } -func (s *service) ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) { +func (s *service) ContributorEmailToOrgAdmin(ctx context.Context, input ContributorEmailToOrgAdminModel) { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToOrgAdmin", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "adminEmail": adminEmail, - "adminName": adminName, - "companyName": companyName, - "contributorName": contributor.Username, - "contributorGitHubID": contributor.GithubID, - "contributorGitHubUsername": contributor.GithubUsername, - "contributorLFUsername": contributor.LfUsername, - "contributorLFEmail": contributor.LfEmail, - "contributorEmails": strings.Join(contributor.Emails, ","), - "corporateConsoleURL": corporateConsole, + "adminEmail": input.adminEmail, + "adminName": input.adminName, + "companyName": input.companyName, + "contributorName": input.contributor.Username, + "contributorGitHubID": input.contributor.GithubID, + "contributorGitHubUsername": input.contributor.GithubUsername, + "contributorLFUsername": input.contributor.LfUsername, + "contributorLFEmail": input.contributor.LfEmail, + "contributorEmails": strings.Join(input.contributor.Emails, ","), } - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", companyName, getBestUserName(contributor)) - recipients := []string{adminEmail} - body, err := emails.RenderV2ContributorToOrgAdminTemplate(repository, projectService, projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: adminName, - CompanyName: companyName, + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", input.companyName, getBestUserName(input.contributor)) + recipients := []string{input.adminEmail} + body, err := emails.RenderV2ContributorToOrgAdminTemplate(s.emailSvc, input.projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: input.adminName, + CompanyName: input.companyName, }, - UserDetails: getFormattedUserDetails(contributor), - CorporateConsole: corporateConsole, + UserDetails: input.userDetails, }) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering template : %s failed : %v", emails.V2ContributorToOrgAdminTemplateName, err) @@ -147,29 +211,27 @@ func (s *service) ContributorEmailToOrgAdmin(ctx context.Context, repository pro } } -func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) { +func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, input ToCLAManagerDesigneeCorporateModel) { f := logrus.Fields{ - "functionName": "cla_manager.service.SendEmailToCLAManagerDesigneeCorporate", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "corporateConsole": corporateConsole, - "companyName": companyName, - "projectName": projectName, - "designeeEmail": designeeEmail, - "designeeName": designeeName, - "senderEmail": senderEmail, - "senderName": senderName, + "functionName": "cla_manager.service.SendEmailToCLAManagerDesigneeCorporate", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyName": input.companyName, + "projectName": input.projectName, + "designeeEmail": input.designeeEmail, + "designeeName": input.designeeName, + "senderEmail": input.senderEmail, + "senderName": input.senderName, } - subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", companyName) - recipients := []string{designeeEmail} - body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(repository, projectService, projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ - CLAManagerTemplateParams: emails.CLAManagerTemplateParams{ - RecipientName: designeeName, - CompanyName: companyName, + subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", input.companyName) + recipients := []string{input.designeeEmail} + body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(s.emailSvc, input.projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: input.designeeName, + CompanyName: input.companyName, }, - SenderName: senderName, - SenderEmail: senderEmail, - CorporateConsole: corporateConsole, + SenderName: input.senderName, + SenderEmail: input.senderEmail, }) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering template : %s : failed: %v", emails.V2CLAManagerDesigneeCorporateTemplateName, err) @@ -183,27 +245,33 @@ func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, re } } -func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorModel emails.Contributor) { +func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, input ToCLAManagerDesigneeModel) { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToCLAManagerDesignee", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "corporateConsole": corporateConsole, - "companyName": companyName, - "projectNames": strings.Join(projectNames, ","), - "designeeEmail": designeeEmail, - "designeeName": designeeName, - "contributorEmail": contributorModel.Email, - "contributorName": contributorModel.Username, + "companyName": input.companyName, + "projectNames": strings.Join(input.projectNames, ","), + "designeeEmail": input.designeeEmail, + "designeeName": input.designeeName, + "contributorEmail": input.contributorEmail, + "contributorName": input.contributorName, } subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", - companyName, contributorModel.Email) - recipients := []string{designeeEmail} - body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, + input.companyName, input.contributorEmail) + recipients := []string{input.designeeEmail} + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(s.emailSvc, input.projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ - RecipientName: designeeName, - Contributor: contributorModel, - CorporateConsole: corporateConsole, + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: input.designeeName, + CompanyName: input.companyName, + }, + Contributor: emails.Contributor{ + Email: input.contributorEmail, + Username: input.contributorName, + EmailLabel: "", + UsernameLabel: "", + }, }, emails.V2ToCLAManagerDesigneeTemplate, emails.V2ToCLAManagerDesigneeTemplateName) if err != nil { @@ -218,27 +286,31 @@ func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, repository } } -func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectSFIDs []string, foundationSFID, role string, corporateConsoleV2URL string, contributorModel emails.Contributor) error { +func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, input DesigneeEmailToUserWithNoLFIDModel) error { f := logrus.Fields{ - "functionName": "cla_manager.service.SendDesigneeEmailToUserWithNoLFID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "userWithNoLFIDName": userWithNoLFIDName, - "userWithNoLFIDEmail": userWithNoLFIDEmail, - "organizationID": organizationID, - "projectNames": strings.Join(projectNames, ","), - "role": role, - "corporateConsoleV2URL": corporateConsoleV2URL, - "requesterUsername": contributorModel.Username, - "requesterEmail": contributorModel.Email, + "functionName": "cla_manager.service.SendDesigneeEmailToUserWithNoLFID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "userWithNoLFIDName": input.userWithNoLFIDName, + "userWithNoLFIDEmail": input.userWithNoLFIDEmail, + "organizationID": input.organizationID, + "projectNames": strings.Join(input.projectNames, ","), + "role": input.role, + "requesterUsername": input.requesterUsername, + "requesterEmail": input.requesterEmail, } subject := "EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager" - body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(repository, projectService, projectSFIDs, + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(s.emailSvc, input.projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ - RecipientName: userWithNoLFIDName, - Contributor: contributorModel, - CorporateConsole: corporateConsoleV2URL, + Contributor: emails.Contributor{ + Email: input.requesterEmail, + Username: input.requesterUsername, + }, + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: input.userWithNoLFIDName, + CompanyName: input.companyName, + }, }, emails.V2DesigneeToUserWithNoLFIDTemplate, emails.V2DesigneeToUserWithNoLFIDTemplateName) if err != nil { @@ -250,16 +322,16 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project log.WithFields(f).Debug("sending user invite request...") // Parse the provided user's name - userFirstName, userLastName := utils.GetFirstAndLastName(userWithNoLFIDName) + userFirstName, userLastName := utils.GetFirstAndLastName(input.userWithNoLFIDName) return acsClient.SendUserInvite(ctx, &v2AcsService.SendUserInviteInput{ InviteUserFirstName: userFirstName, InviteUserLastName: userLastName, - InviteUserEmail: userWithNoLFIDEmail, - RoleName: role, + InviteUserEmail: input.userWithNoLFIDEmail, + RoleName: input.role, Scope: utils.ProjectOrgScope, - ProjectSFID: foundationSFID, - OrganizationSFID: organizationID, + ProjectSFID: input.foundationSFID, + OrganizationSFID: input.organizationID, InviteType: "userinvite", Subject: subject, EmailContent: body, @@ -268,23 +340,30 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, project } // sendEmailToUserWithNoLFID helper function to send email to a given user with no LFID -func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role, corporateConsole string) error { +func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, input EmailToUserWithNoLFIDModel) error { f := logrus.Fields{ "functionName": "cla_manager.service.SendEmailToUserWithNoLFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectName": projectName, - "requesterUsername": requesterUsername, - "requesterEmail": requesterEmail, - "userWithNoLFIDName": userWithNoLFIDName, - "userWithNoLFIDEmail": userWithNoLFIDEmail, - "organizationID": organizationID, - "projectID": utils.StringValue(projectID), - "role": role, + "projectName": input.projectName, + "requesterUsername": input.requesterUsername, + "requesterEmail": input.requesterEmail, + "userWithNoLFIDName": input.userWithNoLFIDName, + "userWithNoLFIDEmail": input.userWithNoLFIDEmail, + "organizationID": input.organizationID, + "projectID": input.projectID, + "role": input.role, } // subject string, body string, recipients []string - subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", role) - body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(repository, projectService, userWithNoLFIDName, projectName, *projectID, requesterUsername, requesterEmail, corporateConsole) + subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", input.role) + body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(s.emailSvc, input.projectID, emails.V2CLAManagerToUserWithNoLFIDTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: input.userWithNoLFIDName, + CompanyName: input.companyName, + }, + RequesterUserName: input.requesterUsername, + RequesterEmail: input.requesterEmail, + }) if err != nil { log.WithFields(f).WithError(err).Warnf("rendering email : %s failed : %v", emails.V2CLAManagerToUserWithNoLFIDTemplateName, err) @@ -293,18 +372,18 @@ func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, repository proj acsClient := v2AcsService.GetClient() // Parse the provided user's name - userFirstName, userLastName := utils.GetFirstAndLastName(userWithNoLFIDName) + userFirstName, userLastName := utils.GetFirstAndLastName(input.userWithNoLFIDName) log.WithFields(f).Debug("sending user invite request...") //return acsClient.SendUserInvite(ctx, &userWithNoLFIDEmail, role, utils.ProjectOrgScope, projectID, organizationID, "userinvite", &subject, &body, automate) return acsClient.SendUserInvite(ctx, &v2AcsService.SendUserInviteInput{ InviteUserFirstName: userFirstName, InviteUserLastName: userLastName, - InviteUserEmail: userWithNoLFIDEmail, - RoleName: role, + InviteUserEmail: input.userWithNoLFIDEmail, + RoleName: input.role, Scope: utils.ProjectOrgScope, - ProjectSFID: *projectID, - OrganizationSFID: organizationID, + ProjectSFID: input.projectID, + OrganizationSFID: input.organizationID, InviteType: "userinvite", Subject: subject, EmailContent: body, diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 61be52814..87db4a8f6 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -248,7 +248,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C return cla_manager.NewInviteCompanyAdminBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, userErr)) } - claManagerDesignees, err := service.InviteCompanyAdmin(ctx, params.Body.ContactAdmin, params.Body.CompanyID, *params.Body.ClaGroupID, params.Body.UserEmail.String(), params.Body.Name, &user, LfxPortalURL, CorporateConsoleV2URL) + claManagerDesignees, err := service.InviteCompanyAdmin(ctx, params.Body.ContactAdmin, params.Body.CompanyID, *params.Body.ClaGroupID, params.Body.UserEmail.String(), params.Body.Name, &user) if err != nil { statusCode := buildErrorStatusCode(err) @@ -328,7 +328,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C } claManagerDesignee, err := service.CreateCLAManagerRequest(ctx, params.Body.ContactAdmin, v1CompanyModel.CompanyID, params.ProjectSFID, params.Body.UserEmail.String(), - *params.Body.FullName, authUser, LfxPortalURL, CorporateConsoleV2URL) + *params.Body.FullName, authUser) if err != nil { statusCode := buildErrorStatusCode(err) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index d796c166b..d57d84b74 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -69,6 +69,7 @@ const ( ) type service struct { + emailSvc emails.EmailTemplateService companyService company.IService projectService project.Service repositoriesService repositories.Service @@ -83,29 +84,30 @@ type service struct { type Service interface { CreateCLAManager(ctx context.Context, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse - InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, lFxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) + InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) - CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL, CorporateConsole string) (*models.ClaManagerDesignee, error) + CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User) (*models.ClaManagerDesignee, error) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *models.NotifyClaManagerList, CorporateConsoleV2URL string) error CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error) ProjectCompanySignedOrNot(ctx context.Context, signedAtFoundation bool, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, companyModel *v1Models.Company) error IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error) // Email Functions - SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel, repository projects_cla_groups.Repository, projectService project.Service, projectSFIDs []string) - SendEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectName, projectSFID string, senderEmail string, senderName string, corporateConsole string) - ContributorEmailToOrgAdmin(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, adminEmail string, adminName string, companyName string, projectSFIDs []string, contributor *v1Models.User, corporateConsole string) - SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectSFID string, projectName string, designeeEmail string, designeeName string, senderEmail string, senderName string) - SendEmailToCLAManagerDesignee(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, corporateConsole string, companyName string, projectNames, projectSFIDs []string, designeeEmail string, designeeName string, contributorModel emails.Contributor) - SendDesigneeEmailToUserWithNoLFID(ctx context.Context, projectService project.Service, repository projects_cla_groups.Repository, userWithNoLFIDName, userWithNoLFIDEmail, organizationName, organizationID string, projectNames, projectIDs []string, foundationSFID, role string, corporateConsoleV2URL string, contributorModel emails.Contributor) error - SendEmailToUserWithNoLFID(ctx context.Context, repository projects_cla_groups.Repository, projectService project.Service, projectName, requesterUsername, requesterEmail, userWithNoLFIDName, userWithNoLFIDEmail, organizationID string, projectID *string, role, corporateConsole string) error + SendEmailToCLAManager(ctx context.Context, input *EmailToCLAManagerModel, projectSFIDs []string) + SendEmailToOrgAdmin(ctx context.Context, input EmailToOrgAdminModel) + ContributorEmailToOrgAdmin(ctx context.Context, input ContributorEmailToOrgAdminModel) + SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, input ToCLAManagerDesigneeCorporateModel) + SendEmailToCLAManagerDesignee(ctx context.Context, input ToCLAManagerDesigneeModel) + SendDesigneeEmailToUserWithNoLFID(ctx context.Context, input DesigneeEmailToUserWithNoLFIDModel) error + SendEmailToUserWithNoLFID(ctx context.Context, input EmailToUserWithNoLFIDModel) error } // NewService returns instance of CLA Manager service -func NewService(compService company.IService, projService project.Service, mgrService v1ClaManager.IService, claUserService easyCLAUser.Service, +func NewService(emailSvc emails.EmailTemplateService, compService company.IService, projService project.Service, mgrService v1ClaManager.IService, claUserService easyCLAUser.Service, repoService repositories.Service, v2CompService v2Company.Service, evService events.Service, projectCGroupRepo projects_cla_groups.Repository) Service { return &service{ + emailSvc: emailSvc, companyService: compService, projectService: projService, repositoriesService: repoService, @@ -648,7 +650,7 @@ func (s *service) CreateCLAManagerDesigneeByGroup(ctx context.Context, params cl } // CreateCLAManagerRequest service method -func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User, LfxPortalURL, corporateConsole string) (*models.ClaManagerDesignee, error) { +func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User) (*models.ClaManagerDesignee, error) { f := logrus.Fields{ "functionName": "cla_manager.service.CreateCLAManagerRequest", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -729,7 +731,16 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.Warn(msg) return nil, adminErr } - s.SendEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, v1CompanyModel.CompanyName, projectSF.Name, projectSF.ID, authUser.Email, authUser.UserName, LfxPortalURL) + s.SendEmailToOrgAdmin(ctx, + EmailToOrgAdminModel{ + adminEmail: userService.GetPrimaryEmail(adminUser), + adminName: admin.Contact.Name, + companyName: v1CompanyModel.CompanyName, + projectName: projectSF.Name, + projectSFID: projectSF.ID, + senderName: authUser.UserName, + senderEmail: authUser.Email, + }) // Make a note in the event log s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ContributorNotifyCompanyAdminType, @@ -756,7 +767,17 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool msg := fmt.Sprintf("User: %s does not have an LF Login", userEmail) log.WithFields(f).Warn(msg) // Send email - sendEmailErr := s.SendEmailToUserWithNoLFID(ctx, s.projectCGRepo, s.projectService, projectSF.Name, authUser.UserName, authUser.Email, fullName, userEmail, v1CompanyModel.CompanyExternalID, &projectSF.ID, utils.CLADesigneeRole, corporateConsole) + sendEmailErr := s.SendEmailToUserWithNoLFID(ctx, EmailToUserWithNoLFIDModel{ + projectName: projectSF.Name, + requesterUsername: authUser.UserName, + requesterEmail: authUser.Email, + userWithNoLFIDName: fullName, + userWithNoLFIDEmail: userEmail, + organizationID: v1CompanyModel.CompanyExternalID, + companyName: v1CompanyModel.CompanyName, + projectID: projectSF.ID, + role: utils.CLADesigneeRole, + }) if sendEmailErr != nil { log.WithFields(f).Warnf("Error sending email: %+v", sendEmailErr) return nil, sendEmailErr @@ -795,7 +816,15 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool log.WithFields(f).Debugf("sending Email to CLA Manager Designee email: %s ", userEmail) designeeName := fmt.Sprintf("%s %s", lfxUser.FirstName, lfxUser.LastName) - s.SendEmailToCLAManagerDesigneeCorporate(ctx, s.projectCGRepo, s.projectService, corporateConsole, v1CompanyModel.CompanyName, projectSF.ID, projectSF.Name, userEmail, designeeName, authUser.Email, authUser.UserName) + s.SendEmailToCLAManagerDesigneeCorporate(ctx, ToCLAManagerDesigneeCorporateModel{ + companyName: v1CompanyModel.CompanyName, + projectSFID: projectSF.ID, + projectName: projectSF.Name, + designeeEmail: userEmail, + designeeName: designeeName, + senderEmail: authUser.Email, + senderName: authUser.UserName, + }) log.WithFields(f).Debug("creating a contributor notify CLA designee log event...") // Make a note in the event log @@ -840,7 +869,7 @@ func (s *service) ValidateInviteCompanyAdminCheck(ctx context.Context, f logrus. return nil } -func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User, LfxPortalURL, CorporateConsoleV2URL string) ([]*models.ClaManagerDesignee, error) { //nolint +func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User) ([]*models.ClaManagerDesignee, error) { //nolint f := logrus.Fields{ "functionName": "cla_manager.service.InviteCompanyAdmin", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -966,7 +995,14 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com return nil, adminErr } - s.ContributorEmailToOrgAdmin(ctx, s.projectCGRepo, s.projectService, userService.GetPrimaryEmail(adminUser), admin.Contact.Name, organization.Name, projectSFIDs, userModel, LfxPortalURL) + s.ContributorEmailToOrgAdmin(ctx, ContributorEmailToOrgAdminModel{ + adminEmail: userService.GetPrimaryEmail(adminUser), + adminName: admin.Contact.Name, + companyName: organization.Name, + projectSFIDs: projectSFIDs, + contributor: userModel, + userDetails: getFormattedUserDetails(userModel), + }) designeeScope := models.ClaManagerDesignee{ Email: strfmt.Email(admin.Contact.EmailAddress), Name: admin.Contact.Name, @@ -998,7 +1034,18 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com contributorModel = getContributorPublicEmail(contributor) } - sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, s.projectService, s.projectCGRepo, name, userEmail, organization.Name, organization.ID, projectSFs, projectSFIDs, foundationSFID, "cla-manager-designee", LfxPortalURL, contributorModel) + sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, DesigneeEmailToUserWithNoLFIDModel{ + userWithNoLFIDName: name, + userWithNoLFIDEmail: userEmail, + requesterUsername: contributorModel.Email, + requesterEmail: contributorModel.Email, + projectNames: projectSFs, + projectSFIDs: projectSFIDs, + foundationSFID: foundationSFID, + role: "cla-manager-designee", + companyName: organization.Name, + organizationID: organization.ID, + }) if sendErr != nil { msg := fmt.Sprintf("Problem sending email to user: %s , error: %+v", userEmail, sendErr) log.Warn(msg) @@ -1040,13 +1087,28 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com contributorModel.Username = contributor.LFUsername contributorModel.EmailLabel = utils.EmailLabel contributorModel.UsernameLabel = utils.UserLabel - + s.SendEmailToCLAManagerDesignee(ctx, ToCLAManagerDesigneeModel{ + designeeName: user.Name, + designeeEmail: userEmail, + companyName: organization.Name, + projectNames: projectSFs, + projectSFIDs: projectSFIDs, + contributorEmail: contributor.LFEmail, + contributorName: contributor.LFUsername, + contributorModel: contributorModel, + }) } else { contributorModel = getContributorPublicEmail(contributor) + s.SendEmailToCLAManagerDesignee(ctx, ToCLAManagerDesigneeModel{ + designeeName: user.Name, + designeeEmail: userEmail, + companyName: organization.Name, + projectNames: projectSFs, + projectSFIDs: projectSFIDs, + contributorModel: contributorModel, + }) } - s.SendEmailToCLAManagerDesignee(ctx, s.projectCGRepo, s.projectService, CorporateConsoleV2URL, organization.Name, projectSFs, projectSFIDs, userEmail, user.Name, contributorModel) - log.Debugf("CLA Manager designee created : %+v", designeeScopes) return designeeScopes, nil @@ -1150,7 +1212,7 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode CLAManagerEmail: claManager.Email.String(), CompanyName: notifyCLAManagers.CompanyName, CorporateConsoleURL: CorporateConsoleV2URL, - }, s.projectCGRepo, s.projectService, projectSFIDs) + }, projectSFIDs) } return nil From 846f6bec2f32efc60dee7eeadd7d7ec289274e1d Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 8 Mar 2021 16:12:14 -0800 Subject: [PATCH 0146/1276] Added/Updated Python Event Table Attributes (#2754) - Updated table attributes/columns - Updated create event logic to lookup missing values, when possible - Updated unit tests Signed-off-by: David Deal --- cla-backend/cla/controllers/github.py | 6 +- cla-backend/cla/controllers/project.py | 17 +-- cla-backend/cla/controllers/signature.py | 9 +- cla-backend/cla/controllers/user.py | 4 +- cla-backend/cla/models/docusign_models.py | 14 +- cla-backend/cla/models/dynamo_models.py | 121 +++++++++++++----- cla-backend/cla/models/model_utils.py | 24 ++++ cla-backend/cla/tests/unit/test_event.py | 6 +- .../cla/tests/unit/test_project_event.py | 69 ++++++---- .../cla/tests/unit/test_signature_event.py | 31 ++++- cla-backend/cla/tests/unit/test_user_event.py | 13 +- cla-backend/cla/utils.py | 11 +- 12 files changed, 226 insertions(+), 99 deletions(-) create mode 100644 cla-backend/cla/models/model_utils.py diff --git a/cla-backend/cla/controllers/github.py b/cla-backend/cla/controllers/github.py index a2a3bff32..992259bed 100644 --- a/cla-backend/cla/controllers/github.py +++ b/cla-backend/cla/controllers/github.py @@ -480,8 +480,7 @@ def handle_installation_repositories_added_event(action: str, body: dict): 'to the CLA configuration. GitHub organization was set to auto-enable.') Event.create_event( event_type=EventType.RepositoryAdded, - event_project_id=cla_group_id, - event_project_name=project_model.get_project_name(), + event_cla_group_id=cla_group_id, event_company_id=None, event_data=msg, event_summary=msg, @@ -544,8 +543,7 @@ def handle_installation_repositories_removed_event(action: str, body: dict): # Log the event Event.create_event( event_type=EventType.RepositoryDisable, - event_project_id=repo.get_repository_project_id(), - event_project_name=project_model.get_project_name(), + event_cla_group_id=repo.get_repository_project_id(), event_company_id=None, event_data=msg, event_summary=msg, diff --git a/cla-backend/cla/controllers/project.py b/cla-backend/cla/controllers/project.py index 45c512728..269768ddc 100644 --- a/cla-backend/cla/controllers/project.py +++ b/cla-backend/cla/controllers/project.py @@ -224,7 +224,8 @@ def create_project(project_external_id, project_name, project_icla_enabled, proj event_data = 'Project-{} created'.format(project_name) Event.create_event( event_type=EventType.CreateProject, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), + event_project_id=project_external_id, event_data=event_data, event_summary=event_data, contains_pii=False, @@ -277,7 +278,7 @@ def update_project(project_id, project_name=None, project_icla_enabled=None, event_data = f'Project- {project_id} Updates: ' + updated_string Event.create_event( event_type=EventType.UpdateProject, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), event_data=event_data, event_summary=event_data, contains_pii=False, @@ -304,7 +305,7 @@ def delete_project(project_id, username=None): event_data = 'Project-{} deleted'.format(project.get_project_name()) Event.create_event( event_type=EventType.DeleteProject, - event_project_id=project_id, + event_cla_group_id=project_id, event_data=event_data, event_summary=event_data, contains_pii=False, @@ -476,7 +477,7 @@ def post_project_document(project_id, event_data = 'Created new document for Project-{} '.format(project.get_project_name()) Event.create_event( event_type=EventType.CreateProjectDocument, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), event_data=event_data, event_summary=event_data, contains_pii=False, @@ -554,7 +555,7 @@ def post_project_document_template(project_id, project.get_project_name(), template_name) Event.create_event( event_type=EventType.CreateProjectDocumentTemplate, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), event_data=event_data, event_summary=event_data, contains_pii=False, @@ -598,7 +599,7 @@ def delete_project_document(project_id, document_type, major_version, minor_vers Event.create_event( event_data=event_data, event_summary=event_data, - event_project_id=project_id, + event_cla_group_id=project_id, event_type=EventType.DeleteProjectDocument, contains_pii=False, ) @@ -876,7 +877,7 @@ def add_project_manager(username, project_id, lfid): event_type=EventType.AddProjectManager, event_data=event_data, event_summary=event_data, - event_project_id=project_id, + event_cla_group_id=project_id, contains_pii=True, ) @@ -928,7 +929,7 @@ def remove_project_manager(username, project_id, lfid): event_type=EventType.RemoveProjectManager, event_data=event_data, event_summary=event_data, - event_project_id=project_id, + event_cla_group_id=project_id, contains_pii=True, ) diff --git a/cla-backend/cla/controllers/signature.py b/cla-backend/cla/controllers/signature.py index 74d2446e8..bd6e61e9f 100644 --- a/cla-backend/cla/controllers/signature.py +++ b/cla-backend/cla/controllers/signature.py @@ -136,7 +136,7 @@ def create_signature(signature_project_id, # pylint: disable=too-many-arguments event_data=event_data, event_summary=event_data, event_type=EventType.CreateSignature, - event_project_id=signature_project_id, + event_cla_group_id=str(signature_project_id), contains_pii=False, ) @@ -303,6 +303,7 @@ def update_signature(signature_id, # pylint: disable=too-many-arguments,too-man event_data=event_data, event_summary=event_data, event_type=EventType.UpdateSignature, + event_cla_group_id=signature.get_signature_project_id(), contains_pii=True, ) @@ -390,6 +391,7 @@ def notify_whitelist_change(auth_user, old_signature: Signature, new_signature: event_type=EventType.NotifyWLChange, event_company_name=company_name, event_project_name=project_name, + event_cla_group_id=new_signature.get_signature_project_id(), contains_pii=True, ) @@ -688,8 +690,10 @@ def delete_signature(signature_id): :type signature_id: UUID """ signature = Signature() + cla_group_id = '' try: # Try to load the signature to delete. signature.load(str(signature_id)) + cla_group_id = signature.get_signature_project_id() except DoesNotExist as err: # Should we bother sending back an error? return {'errors': {'signature_id': str(err)}} @@ -698,6 +702,7 @@ def delete_signature(signature_id): Event.create_event( event_data=event_data, event_summary=event_data, + event_cla_group_id=cla_group_id, event_type=EventType.DeleteSignature, contains_pii=False, ) @@ -976,6 +981,7 @@ def add_cla_manager(auth_user: AuthUser, signature_id: str, lfid: str): event_data = f'{lfid} added as cla manager to Signature ACL for {signature.get_signature_id()}' Event.create_event( event_data=event_data, + event_cla_group_id=signature.get_signature_project_id(), event_summary=event_data, event_type=EventType.AddCLAManager, contains_pii=True, @@ -1041,6 +1047,7 @@ def remove_cla_manager(username, signature_id, lfid): event_data=event_data, event_summary=event_data, event_type=EventType.RemoveCLAManager, + event_cla_group_id=project.get_project_id(), contains_pii=True, ) diff --git a/cla-backend/cla/controllers/user.py b/cla-backend/cla/controllers/user.py index 0a7d6278f..6b38b68f8 100644 --- a/cla-backend/cla/controllers/user.py +++ b/cla-backend/cla/controllers/user.py @@ -193,7 +193,7 @@ def request_company_whitelist(user_id: str, company_id: str, user_name: str, use f'as {user_name} <{user_email}>') Event.create_event( event_user_id=user_id, - event_project_id=project_id, + event_cla_group_id=project_id, event_company_id=company_id, event_type=EventType.RequestCompanyWL, event_data=event_data, @@ -288,6 +288,7 @@ def invite_cla_manager(contributor_id, contributor_name, contributor_email, cla_ event_data=log_msg, event_summary=log_msg, event_type=EventType.InviteAdmin, + event_cla_group_id=project.get_project_id(), contains_pii=True, ) @@ -331,6 +332,7 @@ def request_company_ccla(user_id, user_email, company_id, project_id): event_type=EventType.RequestCCLA, event_user_id=user_id, event_company_id=company_id, + event_cla_group_id=project.get_project_id(), contains_pii=False, ) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 23f0480ab..bea387f4d 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -597,7 +597,7 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic event_type=EventType.UserAssociatedWithCompany, event_company_id=actual_company_id, event_company_name=company.get_company_name(), - event_project_id=project_id, + event_cla_group_id=project_id, event_project_name=project.get_project_name(), event_user_id=user.get_user_id(), event_user_name=user.get_user_name() if user else None, @@ -706,7 +706,7 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url Event.create_event( event_type=EventType.EmployeeSignatureCreated, event_company_id=company_id, - event_project_id=project_id, + event_cla_group_id=project_id, event_user_id=user_id, event_user_name=user.get_user_name() if user else None, event_data=event_data, @@ -808,7 +808,7 @@ def request_employee_signature_gerrit(self, project_id, company_id, user_id, ret Event.create_event( event_type=EventType.EmployeeSignatureCreated, event_company_id=company_id, - event_project_id=project_id, + event_cla_group_id=project_id, event_user_id=user_id, event_user_name=user.get_user_name() if user else None, event_data=event_data, @@ -1473,7 +1473,7 @@ def signed_individual_callback(self, content, installation_id, github_repository f'project {project.get_project_name()} with project ID: {project.get_project_id()}.') Event.create_event( event_type=EventType.IndividualSignatureSigned, - event_project_id=signature.get_signature_project_id(), + event_cla_group_id=signature.get_signature_project_id(), event_company_id=None, event_user_id=signature.get_signature_reference_id(), event_user_name=user.get_user_name() if user else None, @@ -1527,7 +1527,7 @@ def signed_individual_callback_gerrit(self, content, user_id): f'project {project.get_project_name()} with project ID: {project.get_project_id()}.') Event.create_event( event_type=EventType.IndividualSignatureSigned, - event_project_id=signature.get_signature_project_id(), + event_cla_group_id=signature.get_signature_project_id(), event_company_id=None, event_user_id=user.get_user_id(), event_user_name=user.get_user_name(), @@ -1679,7 +1679,7 @@ def signed_corporate_callback(self, content, project_id, company_id): f'the project ID: {project.get_project_id()}.') Event.create_event( event_type=EventType.IndividualSignatureSigned, - event_project_id=project_id, + event_cla_group_id=project_id, event_company_id=None, event_user_id=user.get_user_id(), event_user_name=user.get_user_name(), @@ -1699,7 +1699,7 @@ def signed_corporate_callback(self, content, project_id, company_id): f'by {signature.get_signatory_name()}.') Event.create_event( event_type=EventType.CompanySignatureSigned, - event_project_id=project_id, + event_cla_group_id=project_id, event_company_id=company.get_company_id(), event_user_id=user.get_user_id(), event_user_name=signature.get_signatory_name(), diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 564b8e981..6c16e0b67 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -32,6 +32,8 @@ from cla.models import model_interfaces, key_value_store_interface, DoesNotExist from cla.models.event_types import EventType from cla.models.model_interfaces import User, Signature, ProjectCLAGroup, Repository, Gerrit +from cla.project_service import ProjectService +from cla.models.model_utils import is_uuidv4 stage = os.environ.get("STAGE", "") cla_logo_url = os.environ.get("CLA_BUCKET_LOGO_URL", "") @@ -4148,9 +4150,11 @@ class Meta: event_cla_group_name_lower = UnicodeAttribute(null=True) event_project_id = UnicodeAttribute(null=True) + event_project_sfid = UnicodeAttribute(null=True) event_project_name = UnicodeAttribute(null=True) event_project_name_lower = UnicodeAttribute(null=True) - event_project_external_id = UnicodeAttribute(null=True) + event_parent_project_sfid = UnicodeAttribute(null=True) + event_parent_project_name = UnicodeAttribute(null=True) event_company_id = UnicodeAttribute(null=True) event_company_sfid = UnicodeAttribute(null=True) @@ -4239,8 +4243,10 @@ def __str__(self): f"event cla group name:{self.model.event_cla_group_name}, " f"event project id:{self.model.event_project_id}, " + f"event project sfid: {self.model.event_project_sfid}," f"event project name: {self.model.event_project_name}, " - f"event project external id: {self.model.event_project_external_id}," + f"event parent project sfid:{self.model.event_parent_project_sfid}, " + f"event parent project name: {self.model.event_parent_project_name}, " f"event company id: {self.model.event_company_id}, " f"event company sfid: {self.model.event_company_sfid}, " @@ -4299,8 +4305,8 @@ def get_event_cla_group_name_lower(self): def get_event_project_id(self): return self.model.event_project_id - def get_event_project_external_id(self): - return self.model.event_project_external_id + def get_event_project_sfid(self): + return self.model.event_project_sfid def get_event_project_name(self): return self.model.event_project_name @@ -4308,6 +4314,12 @@ def get_event_project_name(self): def get_event_project_name_lower(self): return self.model.event_project_name_lower + def get_event_parent_project_sfid(self): + return self.model.event_parent_project_sfid + + def get_event_parent_project_name(self): + return self.model.event_parent_project_name + def get_event_type(self): return self.model.event_type @@ -4359,65 +4371,103 @@ def all_limit(self, limit: Optional[int] = None, last_evaluated_key: Optional[st ret.append(evt) return ret, result_iterator.last_evaluated_key, result_iterator.total_count - def set_event_data(self, event_data): + def set_event_data(self, event_data: str): self.model.event_data = event_data - def set_event_summary(self, event_summary): + def set_event_summary(self, event_summary: str): self.model.event_summary = event_summary - def set_event_id(self, event_id): + def set_event_id(self, event_id: str): self.model.event_id = event_id - def set_event_company_id(self, company_id): + def set_event_company_id(self, company_id: str): self.model.event_company_id = company_id - def set_event_company_sfid(self, company_sfid): + def set_event_company_sfid(self, company_sfid: str): self.model.event_company_sfid = company_sfid - def set_event_company_name(self, company_name): + def set_event_company_name(self, company_name: str): self.model.event_company_name = company_name if company_name: self.model.event_company_name_lower = company_name.lower() - def set_event_user_id(self, user_id): + def set_event_user_id(self, user_id: str): self.model.event_user_id = user_id - def set_event_cla_group_id(self, event_cla_group_id): + def set_event_cla_group_id(self, event_cla_group_id: str): self.model.event_cla_group_id = event_cla_group_id - def set_event_cla_group_name(self, event_cla_group_name): + def set_event_cla_group_name(self, event_cla_group_name: str): self.model.event_cla_group_name = event_cla_group_name if event_cla_group_name: self.model.event_cla_group_name_lower = event_cla_group_name.lower() - def set_event_project_id(self, event_project_id): + def set_event_project_id(self, event_project_id: str): self.model.event_project_id = event_project_id - def set_event_project_external_id(self, event_project_external_id): - self.model.event_project_external_id = event_project_external_id + def set_event_project_sfid(self, event_project_sfid: str): + self.model.event_project_sfid = event_project_sfid - def set_event_project_name(self, event_project_name): + def set_event_project_name(self, event_project_name: str): self.model.event_project_name = event_project_name if event_project_name: self.model.event_project_name_lower = event_project_name.lower() - def set_event_type(self, event_type): + def set_event_parent_project_sfid(self, event_parent_project_sfid: str): + self.model.event_parent_project_sfid = event_parent_project_sfid + + def set_event_parent_project_name(self, event_parent_project_name: str): + self.model.event_parent_project_name = event_parent_project_name + + def set_event_type(self, event_type: str): self.model.event_type = event_type - def set_event_user_name(self, event_user_name): + def set_event_user_name(self, event_user_name: str): self.model.event_user_name = event_user_name self.model.event_user_name_lower = event_user_name.lower() - def set_event_date_and_contains_pii(self, contains_pii=False): + def set_event_date_and_contains_pii(self, contains_pii: bool = False): dateDDMMYYYY = datetime.date.today().strftime("%d-%m-%Y") self.model.contains_pii = contains_pii self.model.event_date = dateDDMMYYYY self.model.event_date_and_contains_pii = '{}#{}'.format(dateDDMMYYYY, str(contains_pii).lower()) def set_company_id_external_project_id(self): - if self.model.event_project_external_id is not None and self.model.event_company_id is not None: + if self.model.event_project_sfid is not None and self.model.event_company_id is not None: self.model.company_id_external_project_id = (f'{self.model.event_company_id}' - f'#{self.model.event_project_external_id}') + f'#{self.model.event_project_sfid}') + + @staticmethod + def set_cla_group_details(event, cla_group_id: str): + try: + project = Project() + project.load(str(cla_group_id)) + event.set_event_cla_group_name(project.get_project_name()) + event.set_event_project_sfid(project.get_project_external_id()) + Event.set_project_details(event, project.get_project_external_id()) + except Exception as err: + cla.log.warning(f'unable to set CLA Group name due to the following error: {err}') + + @staticmethod + def set_project_details(event, event_project_id: str): + try: + sf_project = ProjectService.get_project_by_id(event_project_id) + if sf_project is not None: + event.set_event_project_name(sf_project.get("Name")) + # Does this project have a parent? + if sf_project.get("Parent") is not None: + # Load the parent to get the name + Event.set_project_parent_details(event, sf_project.get("Parent")) + except Exception as err: + cla.log.warning(f'unable to set project name and parent ID/name ' + f'due to the following error: {err}') + + @staticmethod + def set_project_parent_details(event, event_parent_project_id: str): + sf_project = ProjectService.get_project_by_id(event_parent_project_id) + if sf_project is not None: + event.set_event_parent_project_sfid(sf_project.get("ID")) + event.set_event_parent_project_name(sf_project.get("Name")) def search_events(self, **kwargs): """ @@ -4464,6 +4514,7 @@ def search_events(self, **kwargs): def create_event( cls, event_type: Optional[EventType] = None, + event_cla_group_id: Optional[str] = None, event_project_id: Optional[str] = None, event_company_id: Optional[str] = None, event_project_name: Optional[str] = None, @@ -4482,6 +4533,8 @@ def create_event( :type event_type: EventType :param event_project_id: The project associated with event :type event_project_id: string + :param event_cla_group_id: The CLA Group ID associated with event + :type event_cla_group_id: string :param event_project_name: The project name associated with event :type event_project_name: string :param event_company_id: The company associated with event @@ -4507,16 +4560,17 @@ def create_event( event_project_name = "undefined" if event_company_name is None: event_company_name = "undefined" - if event_project_id: - try: - project = Project() - project.load(str(event_project_id)) - event_project_name = project.get_project_name() - event_project_external_id = project.get_project_external_id() - event.set_event_project_id(event_project_id) - event.set_event_project_external_id(event_project_external_id) - except DoesNotExist as err: - return {"errors": {"event_project_id": str(err)}} + + # Handle case where teh event_project_id == CLA Group ID or SalesForce ID + if event_project_id and is_uuidv4(event_project_id): # cla group id in the project_id field + Event.set_cla_group_details(event, event_project_id) + elif event_project_id and not is_uuidv4(event_project_id): # external SFID + Event.set_project_details(event, event_project_id) + + # if the caller has given us a CLA Group ID + if event_cla_group_id is not None: # cla_group_id + Event.set_cla_group_details(event, event_cla_group_id) + if event_company_id: try: company = Company() @@ -4543,12 +4597,11 @@ def create_event( event.set_event_id(str(uuid.uuid4())) if event_type: event.set_event_type(event_type.name) - event.set_event_project_name(event_project_name) + event.set_event_project_name(event_project_name) # potentially overrides the SF Name event.set_event_summary(event_summary) event.set_event_company_name(event_company_name) event.set_event_data(event_data) event.set_event_date_and_contains_pii(contains_pii) - event.set_company_id_external_project_id() if not dry_run: event.save() return {"data": event.to_dict()} diff --git a/cla-backend/cla/models/model_utils.py b/cla-backend/cla/models/model_utils.py new file mode 100644 index 000000000..c3d1c6a69 --- /dev/null +++ b/cla-backend/cla/models/model_utils.py @@ -0,0 +1,24 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +""" +Utility functions for the models +""" +from uuid import UUID + + +def is_uuidv4(uuid_string: str) -> bool: + """ + Helper function for determining if the specified string is a UUID v4 value. + :param uuid_string: the string representing a UUID + :return: True if the specified string is a UUID v4 value, False otherwise + """ + try: + UUID(uuid_string, version=4) + return True + except TypeError: + # If it's a value error, then the string is not a valid UUID. + return False + except ValueError: + # If it's a value error, then the string is not a valid UUID. + return False diff --git a/cla-backend/cla/tests/unit/test_event.py b/cla-backend/cla/tests/unit/test_event.py index efc16696b..65bd1c41f 100644 --- a/cla-backend/cla/tests/unit/test_event.py +++ b/cla-backend/cla/tests/unit/test_event.py @@ -56,7 +56,7 @@ def test_event_project_id(project): event_data=event_data, event_summary=event_data, event_type=event_types.EventType.DeleteProject, - event_project_id=project.get_project_id() + event_cla_group_id=project.get_project_id() ) assert 'data' in response @@ -109,14 +109,14 @@ def test_event_time_epoch(mock_event): def test_company_id_external_project_id(mock_event): - mock_event.set_event_project_external_id("external_id") + mock_event.set_event_project_sfid("external_id") mock_event.set_event_company_id("company_id") mock_event.set_company_id_external_project_id() assert mock_event.get_company_id_external_project_id() == "company_id#external_id" def test_company_id_external_project_id_empty_test1(mock_event): - mock_event.set_event_project_external_id("external_id") + mock_event.set_event_project_sfid("external_id") mock_event.set_company_id_external_project_id() assert mock_event.get_company_id_external_project_id() == None diff --git a/cla-backend/cla/tests/unit/test_project_event.py b/cla-backend/cla/tests/unit/test_project_event.py index 82f88b30d..a296d29bb 100644 --- a/cla-backend/cla/tests/unit/test_project_event.py +++ b/cla-backend/cla/tests/unit/test_project_event.py @@ -1,21 +1,17 @@ # Copyright The Linux Foundation and each contributor to CommunityBridge. # SPDX-License-Identifier: MIT -import os -from unittest.mock import MagicMock, Mock, patch - -import pytest -from falcon import HTTP_200 -from pynamodb.tests.deep_eq import deep_eq +from unittest.mock import Mock, patch import cla from cla.auth import AuthUser from cla.controllers import project as project_controller -from cla.models.dynamo_models import Project, User, Document, UserPermissions, Event +from cla.models.dynamo_models import Project, User, Document, UserPermissions from cla.models.event_types import EventType PATCH_METHOD = "pynamodb.connection.Connection._make_api_call" + @patch('cla.controllers.project.Event.create_event') def test_event_delete_project(mock_event, project): """ Test Delete Project event """ @@ -33,12 +29,13 @@ def test_event_delete_project(mock_event, project): # Check whether audit event service is invoked mock_event.assert_called_with( event_type=expected_event_type, - event_project_id=project_id, + event_cla_group_id=project_id, event_data=expected_event_data, event_summary=expected_event_data, contains_pii=False, ) + @patch('cla.controllers.project.Event.create_event') def test_event_create_project(mock_event): """ Test Create Project event """ @@ -70,12 +67,14 @@ def test_event_create_project(mock_event): # Test for audit event mock_event.assert_called_with( event_type=event_type, - event_project_id=project_id, + event_cla_group_id=project_id, + event_project_id=project_external_id, event_data=expected_event_data, event_summary=expected_event_data, contains_pii=False, ) + @patch('cla.controllers.project.Event.create_event') def test_event_update_project(mock_event, project): """ Test Update Project event """ @@ -101,10 +100,11 @@ def test_event_update_project(mock_event, project): event_type=event_type, event_data=expected_event_data, event_summary=expected_event_data, - event_project_id=project_id, + event_cla_group_id=project_id, contains_pii=False, ) + @patch('cla.controllers.project.Event.create_event') def test_create_project_document(mock_event, project): """ Test create project Document event """ @@ -139,9 +139,14 @@ def test_create_project_document(mock_event, project): new_major_version=False, ) mock_event.assert_called_with( - event_type=event_type, event_project_id=project_id, event_data=event_data, event_summary=event_data, contains_pii=False, + event_type=event_type, + event_cla_group_id=project_id, + event_data=event_data, + event_summary=event_data, + contains_pii=False, ) + @patch('cla.controllers.project.Event.create_event') def test_create_project_document_template(mock_event, project): """ Test creating project document with existing template event """ @@ -178,9 +183,14 @@ def test_create_project_document_template(mock_event, project): ) mock_event.assert_called_with( - event_type=event_type, event_project_id=project_id, event_data=event_data, event_summary=event_data, contains_pii=False, + event_type=event_type, + event_cla_group_id=project_id, + event_data=event_data, + event_summary=event_data, + contains_pii=False, ) + @patch('cla.controllers.project.Event.create_event') def test_delete_project_document(mock_event): """ Test event for deleting document from the specified project """ @@ -193,8 +203,8 @@ def test_delete_project_document(mock_event): major_version = "v1" minor_version = "v1" event_data = ( - f'Project {project.get_project_name()} with {document_type} :' - +f'document type , minor version : {minor_version}, major version : {major_version} deleted' + f'Project {project.get_project_name()} with {document_type} :' + + f'document type , minor version : {minor_version}, major version : {major_version} deleted' ) Project.load = Mock() @@ -208,21 +218,26 @@ def test_delete_project_document(mock_event): ) mock_event.assert_called_with( - event_type=event_type, event_project_id=project_id, event_data=event_data, event_summary=event_data, contains_pii = False, + event_type=event_type, + event_cla_group_id=project_id, + event_data=event_data, + event_summary=event_data, + contains_pii=False, ) + @patch('cla.controllers.project.Event.create_event') def test_project_add_permission_existing_user(mock_event, project): """ Test adding permissions to project event """ auth_claims = { 'auth0_username_claim': 'http:/localhost/foo', 'email': 'foo@gmail.com', - 'sub' : 'bar', - 'name' : 'name' + 'sub': 'bar', + 'name': 'name' } username = 'harry' auth_user = AuthUser(auth_claims) - auth_user.username='ddeal' + auth_user.username = 'ddeal' event_type = EventType.AddPermission project_sfdc_id = 'project_sfdc_id' @@ -253,12 +268,12 @@ def test_project_remove_permission(mock_event): auth_claims = { 'auth0_username_claim': 'http:/localhost/foo', 'email': 'foo@gmail.com', - 'sub' : 'bar', - 'name' : 'name' + 'sub': 'bar', + 'name': 'name' } username = 'harry' auth_user = AuthUser(auth_claims) - auth_user.username='ddeal' + auth_user.username = 'ddeal' event_type = EventType.RemovePermission project_sfdc_id = 'project_sfdc_id' @@ -282,6 +297,7 @@ def test_project_remove_permission(mock_event): contains_pii=True, ) + @patch('cla.controllers.project.Event.create_event') def test_add_project_manager(mock_event, project): """ Tests event logging where LFID is added to the project ACL """ @@ -302,22 +318,23 @@ def test_add_project_manager(mock_event, project): project.get_project_id(), lfid ) - event_data = '{} added {} to project {}'.format(username,lfid,project.get_project_name()) + event_data = '{} added {} to project {}'.format(username, lfid, project.get_project_name()) mock_event.assert_called_with( event_type=event_type, event_data=event_data, event_summary=event_data, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), contains_pii=True, ) + @patch('cla.controllers.project.Event.create_event') def test_remove_project_manager(mock_event, project): """ Test event logging where lfid is removed from the project acl """ event_type = EventType.RemoveProjectManager Project.load = Mock() - Project.get_project_acl = Mock(return_value=('foo','bar')) + Project.get_project_acl = Mock(return_value=('foo', 'bar')) Project.remove_project_acl = Mock() Project.save = Mock() @@ -331,6 +348,6 @@ def test_remove_project_manager(mock_event, project): event_type=event_type, event_data=event_data, event_summary=event_data, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), contains_pii=True, - ) \ No newline at end of file + ) diff --git a/cla-backend/cla/tests/unit/test_signature_event.py b/cla-backend/cla/tests/unit/test_signature_event.py index 9c5a428ee..f0905436c 100644 --- a/cla-backend/cla/tests/unit/test_signature_event.py +++ b/cla-backend/cla/tests/unit/test_signature_event.py @@ -1,28 +1,38 @@ - # Copyright The Linux Foundation and each contributor to CommunityBridge. +# Copyright The Linux Foundation and each contributor to CommunityBridge. # SPDX-License-Identifier: MIT from unittest.mock import patch, Mock import pytest -from cla.models.dynamo_models import Signature,Project,Company, Document +from cla.models.dynamo_models import Signature, Project, Company from cla.controllers import signature as signature_controller from cla.controllers import company from cla.models.event_types import EventType from cla.auth import AuthUser +from unittest.mock import patch, Mock + +import pytest +from cla.auth import AuthUser +from cla.controllers import company +from cla.controllers import signature as signature_controller +from cla.models.dynamo_models import Signature, Project, Company +from cla.models.event_types import EventType @pytest.fixture() def create_event_signature(): signature_controller.create_event = Mock() + @pytest.fixture() def auth_user(): with patch.object(AuthUser, "__init__", lambda self: None): user = AuthUser() yield user + @patch('cla.controllers.signature.Event.create_event') def test_create_signature(mock_event, create_event_signature, project): """ Test create signature event """ @@ -36,20 +46,22 @@ def test_create_signature(mock_event, create_event_signature, project): event_type = EventType.CreateSignature signature_id = 'new_signature_id' Signature.get_signature_id = Mock(return_value=signature_id) + Signature.get_signature_project_id = Mock(return_value=project.get_project_id()) project_id = project.get_project_id() project = project.get_project_name() event_data = f'Signature added. Signature_id - {signature_id} for Project - {project}' signature_controller.create_signature( - project_id,'signature_reference_id','signature_reference_type' + project_id, 'signature_reference_id', 'signature_reference_type' ) mock_event.assert_called_once_with( event_data=event_data, event_summary=event_data, event_type=event_type, - event_project_id=project_id, + event_cla_group_id=project_id, contains_pii=False, ) + @patch('cla.controllers.signature.Event.create_event') def test_update_signature(mock_event, auth_user, create_event_signature, signature_instance): """ Test update signature """ @@ -68,10 +80,12 @@ def test_update_signature(mock_event, auth_user, create_event_signature, signatu mock_event.assert_called_once_with( event_data=event_data, event_summary=event_data, + event_cla_group_id=signature_instance.get_signature_project_id(), event_type=event_type, contains_pii=True, ) + @patch('cla.controllers.signature.Event.create_event') def test_delete_signature(mock_event, create_event_signature, signature_instance): """ Test delete signature """ @@ -83,10 +97,12 @@ def test_delete_signature(mock_event, create_event_signature, signature_instance mock_event.assert_called_once_with( event_data=event_data, event_summary=event_data, + event_cla_group_id=signature_instance.get_signature_project_id(), event_type=event_type, contains_pii=False, ) + @patch('cla.controllers.signature.Event.create_event') def test_add_cla_manager(mock_event, auth_user, signature_instance, create_event_signature): """ Test add cla manager event """ @@ -113,10 +129,12 @@ def test_add_cla_manager(mock_event, auth_user, signature_instance, create_event mock_event.assert_called_once_with( event_data=event_data, event_summary=event_data, + event_cla_group_id=signature_instance.get_signature_project_id(), event_type=EventType.AddCLAManager, contains_pii=True, ) + @patch('cla.controllers.signature.Event.create_event') def test_remove_cla_manager(mock_event, signature_instance, create_event_signature): """ Test remove cla_manager """ @@ -128,7 +146,7 @@ def test_remove_cla_manager(mock_event, signature_instance, create_event_signatu event_type = EventType.RemoveCLAManager lfid = 'nachwera' subject = 'Removed CLA Manager' - body = 'Removed %s' %lfid + body = 'Removed %s' % lfid recipients = ['foo@gmail.com'] signature_controller.remove_cla_manager_email_content = Mock(return_value=(subject, body, recipients)) signature_controller.get_email_service = Mock() @@ -140,7 +158,6 @@ def test_remove_cla_manager(mock_event, signature_instance, create_event_signatu event_data=event_data, event_summary=event_data, event_type=event_type, + event_cla_group_id=signature_instance.get_signature_project_id(), contains_pii=True, ) - - diff --git a/cla-backend/cla/tests/unit/test_user_event.py b/cla-backend/cla/tests/unit/test_user_event.py index 09d37531d..e935e877d 100644 --- a/cla-backend/cla/tests/unit/test_user_event.py +++ b/cla-backend/cla/tests/unit/test_user_event.py @@ -17,7 +17,7 @@ def create_event_user(): user_controller.create_event = Mock() -class TestRequestCompanyWhitelist: +class TestRequestCompanyApprovalList: def setup(self) -> None: self.old_load = User.load @@ -43,7 +43,7 @@ def teardown(self) -> None: Project.load = self.project_load Project.get_project_name = self.get_project_name - def test_request_company_whitelist(self, create_event_user, project, company, user): + def test_request_company_approval_list(self, create_event_user, project, company, user): """ Test user requesting to be added to the Approved List event """ with patch('cla.controllers.user.Event.create_event') as mock_event: event_type = EventType.RequestCompanyWL @@ -55,6 +55,7 @@ def test_request_company_whitelist(self, create_event_user, project, company, us Company.get_company_name = Mock(return_value=company.get_company_name()) Project.load = Mock() Project.get_project_name = Mock(return_value=project.get_project_name()) + Project.get_project_id = Mock(return_value=project.get_project_id()) user_controller.get_email_service = Mock() user_controller.send = Mock() user_controller.request_company_whitelist( @@ -75,7 +76,7 @@ def test_request_company_whitelist(self, create_event_user, project, company, us mock_event.assert_called_once_with( event_user_id=user.get_user_id(), - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), event_company_id=company.get_company_id(), event_type=event_type, event_data=event_data, @@ -113,6 +114,7 @@ def test_invite_cla_manager(self, mock_event, create_event_user, user): cla_manager_name = "admin" cla_manager_email = "foo@admin.com" project_name = "foo_project" + project_id = "foo_project_id" company_name = "Test Company" event_data = (f'sent email to CLA Manager: {cla_manager_name} with email {cla_manager_email} ' f'for project {project_name} and company {company_name} ' @@ -125,8 +127,9 @@ def test_invite_cla_manager(self, mock_event, create_event_user, user): event_user_id=contributor_id, event_project_name=project_name, event_data=event_data, - event_type=EventType.InviteAdmin, event_summary=event_data, + event_type=EventType.InviteAdmin, + event_cla_group_id=project_id, contains_pii=True, ) @@ -158,6 +161,7 @@ def test_request_company_ccla(self, mock_event, create_event_user, user, project Company.load = Mock() Project.load = Mock() Project.get_project_name = Mock(return_value=project.get_project_name()) + Project.get_project_id = Mock(return_value=project.get_project_id()) manager = User(lf_username="harold", user_email="foo@gmail.com") Company.get_managers = Mock(return_value=[manager, ]) event_data = f"Sent email to sign ccla for {project.get_project_name()}" @@ -171,5 +175,6 @@ def test_request_company_ccla(self, mock_event, create_event_user, user, project event_type=EventType.RequestCCLA, event_user_id=user.get_user_id(), event_company_id=company.get_company_id(), + event_cla_group_id=project.get_project_id(), contains_pii=False, ) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 9a4826325..1f9a9a0d4 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -10,18 +10,17 @@ import os import urllib.parse import urllib.parse as urlparse -from urllib.parse import urlencode from datetime import datetime from typing import List, Optional +from urllib.parse import urlencode import falcon import requests from hug.middleware import SessionMiddleware -from hug.middleware import LogMiddleware -from cla.middleware import CLALogMiddleware from requests_oauthlib import OAuth2Session import cla +from cla.middleware import CLALogMiddleware from cla.models import DoesNotExist from cla.models.dynamo_models import User, Signature, Repository, \ Company, Project, Document, \ @@ -40,10 +39,12 @@ def get_cla_path(): cla_root_dir = os.path.dirname(cla_folder_dir) return cla_root_dir + def get_log_middleware(): """Prepare the hug middleware to manage logging. """ return CLALogMiddleware(logger=cla.log) + def get_session_middleware(): """Prepares the hug middleware to manage key-value session data.""" store = get_key_value_store_service() @@ -710,7 +711,7 @@ def user_signed_project_signature(user: User, project: Project) -> bool: f'and company {company.get_company_name()}') Event.create_event( event_type=EventType.EmployeeSignatureDisapproved, - event_project_id=project.get_project_id(), + event_cla_group_id=project.get_project_id(), event_company_id=company.get_company_id(), event_user_id=user.get_user_id(), event_data=event_data, @@ -1550,6 +1551,7 @@ def get_email_help_content(show_v2_help_link: bool) -> str: def get_email_sign_off_content() -> str: return '

    Thanks,

    EasyCLA Support Team

    ' + def get_corporate_url(project_version: str) -> str: """ helper method that returns appropriate corporate link based on EasyCLA version @@ -1558,6 +1560,7 @@ def get_corporate_url(project_version: str) -> str: """ return CORPORATE_V2_BASE if project_version == 'v2' else CORPORATE_BASE + def append_email_help_sign_off_content(body: str, project_version: str) -> str: """ helper method which appends the help and sign off content to the body of the email From 68b22e453dc78008c61d258cf4b88ef24b1e3d7a Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 8 Mar 2021 18:39:47 -0800 Subject: [PATCH 0147/1276] Added Additional Event Query Logging (#2755) Signed-off-by: David Deal --- cla-backend-go/events/repository.go | 48 ++++++++++++++++++++++++++++ cla-backend-go/v2/events/handlers.go | 1 + 2 files changed, 49 insertions(+) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 7fd1aafae..0b79bbb0b 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -484,10 +484,21 @@ func buildNextKey(indexName string, event *models.Event) (string, error) { // GetCompanyFoundationEvents returns the list of events for foundation and company func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "events.repository.GetCompanyFoundationEvents", + "companySFID": companySFID, + "companyID": companyID, + "foundationSFID": foundationSFID, + "nextKey": utils.StringValue(nextKey), + "paramPageSize": utils.Int64Value(paramPageSize), + "loadAll": all, + } key := fmt.Sprintf("%s#%s", companySFID, foundationSFID) + log.WithFields(f).Debugf("adding key condition of 'company_sfid_foundation_sfid = %s'", key) keyCondition := expression.Key("company_sfid_foundation_sfid").Equal(expression.Value(key)) var filter expression.ConditionBuilder if companyID != "" { + log.WithFields(f).Debugf("adding filter condition of 'event_company_id = %s'", companyID) filter = expression.Name("event_company_id").Equal(expression.Value(companyID)) } return repo.queryEventsTable(CompanySFIDFoundationSFIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) @@ -495,10 +506,21 @@ func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, found // GetCompanyClaGroupEvents returns the list of events for cla group and the company func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "events.repository.GetCompanyClaGroupEvents", + "companySFID": companySFID, + "companyID": companyID, + "claGroupID": claGroupID, + "nextKey": utils.StringValue(nextKey), + "paramPageSize": utils.Int64Value(paramPageSize), + "loadAll": all, + } key := fmt.Sprintf("%s#%s", companySFID, claGroupID) + log.WithFields(f).Debugf("adding key condition of 'company_sfid_project_id = %s'", key) keyCondition := expression.Key("company_sfid_project_id").Equal(expression.Value(key)) var filter expression.ConditionBuilder if companyID != "" { + log.WithFields(f).Debugf("adding filter condition of 'event_company_id = %s'", companyID) filter = expression.Name("event_company_id").Equal(expression.Value(companyID)) } return repo.queryEventsTable(CompanySFIDProjectIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) @@ -506,6 +528,14 @@ func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGrou // GetCompanyEvents returns the list of events for given company id and event types func (repo *repository) GetCompanyEvents(companyID, eventType string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "events.repository.GetCompanyEvents", + "companyID": companyID, + "nextKey": utils.StringValue(nextKey), + "paramPageSize": utils.Int64Value(paramPageSize), + "loadAll": all, + } + log.WithFields(f).Debugf("adding key condition of 'company_id = %s'", companyID) keyCondition := expression.Key("company_id").Equal(expression.Value(companyID)).And( expression.Key("event_type").Equal(expression.Value(eventType))) @@ -514,12 +544,30 @@ func (repo *repository) GetCompanyEvents(companyID, eventType string, nextKey *s // GetFoundationEvents returns the list of foundation events func (repo *repository) GetFoundationEvents(foundationSFID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "events.repository.GetFoundationEvents", + "foundationSFID": foundationSFID, + "nextKey": utils.StringValue(nextKey), + "paramPageSize": utils.Int64Value(paramPageSize), + "loadAll": all, + "searchTerm": utils.StringValue(searchTerm), + } + log.WithFields(f).Debugf("adding key condition of 'event_parent_project_sfid = %s'", foundationSFID) keyCondition := expression.Key("event_parent_project_sfid").Equal(expression.Value(foundationSFID)) return repo.queryEventsTable(EventFoundationSFIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } // GetClaGroupEvents returns the list of cla-group events func (repo *repository) GetClaGroupEvents(claGroupID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "events.repository.GetClaGroupEvents", + "claGroupID": claGroupID, + "nextKey": utils.StringValue(nextKey), + "paramPageSize": utils.Int64Value(paramPageSize), + "loadAll": all, + "searchTerm": utils.StringValue(searchTerm), + } + log.WithFields(f).Debugf("adding key condition of 'event_cla_group_id = %s'", claGroupID) keyCondition := expression.Key("event_cla_group_id").Equal(expression.Value(claGroupID)) return repo.queryEventsTable(EventCLAGroupIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index 5d45ad2bb..0803f5934 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -126,6 +126,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe return events.NewGetRecentEventsForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } + log.WithFields(f).Debug("querying foundation events...") result, err := service.GetFoundationEvents(params.FoundationSFID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents), params.SearchTerm) if err != nil { msg := "problem fetching foundation events" From b4c9e88dca0d2af7fb5f2877f524d05b2fae8f10 Mon Sep 17 00:00:00 2001 From: makkalot Date: Tue, 9 Mar 2021 12:42:48 +0200 Subject: [PATCH 0148/1276] fixing missing contributor models and missing emailTemplateSvc initializations Signed-off-by: makkalot --- cla-backend-go/cla_manager/service.go | 13 +++--- cla-backend-go/cmd/server.go | 2 +- .../emails/cla_manager_templates.go | 2 +- cla-backend-go/v2/cla_manager/emails.go | 41 +++++++----------- cla-backend-go/v2/cla_manager/service.go | 43 +++++++++---------- 5 files changed, 44 insertions(+), 57 deletions(-) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index c6a12d159..f8e177443 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -47,12 +47,12 @@ type service struct { usersService users.Service sigService signatures.SignatureService eventsService events.Service - emailServie emails.EmailTemplateService + emailTemplateService emails.EmailTemplateService corporateConsoleURL string } // NewService creates a new service object -func NewService(repo IRepository, projectClaRepository projects_cla_groups.Repository, companyService company.IService, projectService project.Service, usersService users.Service, sigService signatures.SignatureService, eventsService events.Service, corporateConsoleURL string) IService { +func NewService(repo IRepository, projectClaRepository projects_cla_groups.Repository, companyService company.IService, projectService project.Service, usersService users.Service, sigService signatures.SignatureService, eventsService events.Service, emailTemplateService emails.EmailTemplateService, corporateConsoleURL string) IService { return service{ repo: repo, projectClaRepository: projectClaRepository, @@ -61,6 +61,7 @@ func NewService(repo IRepository, projectClaRepository projects_cla_groups.Repos usersService: usersService, sigService: sigService, eventsService: eventsService, + emailTemplateService: emailTemplateService, corporateConsoleURL: corporateConsoleURL, } } @@ -233,7 +234,7 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID // Notify CLA Managers - send email to each manager for _, manager := range claManagers { - sendClaManagerAddedEmailToCLAManagers(s.emailServie, emails.ClaManagerAddedToCLAManagersTemplateParams{ + sendClaManagerAddedEmailToCLAManagers(s.emailTemplateService, emails.ClaManagerAddedToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, RecipientAddress: manager.LfEmail, @@ -244,7 +245,7 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID }, claGroupModel) } // Notify the added user - sendClaManagerAddedEmailToUser(s.emailServie, emails.CommonEmailParams{ + sendClaManagerAddedEmailToUser(s.emailTemplateService, emails.CommonEmailParams{ RecipientName: userModel.Username, RecipientAddress: userModel.LfEmail, CompanyName: companyModel.CompanyName, @@ -345,7 +346,7 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou claManagers := sigModel.SignatureACL // Notify CLA Managers - send email to each manager for _, manager := range claManagers { - sendClaManagerDeleteEmailToCLAManagers(s.emailServie, emails.ClaManagerDeletedToCLAManagersTemplateParams{ + sendClaManagerDeleteEmailToCLAManagers(s.emailTemplateService, emails.ClaManagerDeletedToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, RecipientAddress: manager.LfEmail, @@ -357,7 +358,7 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou } // Notify the removed manager - sendRemovedClaManagerEmailToRecipient(s.emailServie, emails.CommonEmailParams{ + sendRemovedClaManagerEmailToRecipient(s.emailTemplateService, emails.CommonEmailParams{ RecipientName: userModel.LfUsername, RecipientAddress: userModel.LfEmail, CompanyName: companyModel.CompanyName, diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 6fb227762..37714a739 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -269,7 +269,7 @@ func server(localMode bool) http.Handler { v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, projectRepo, projectClaGroupRepo, v1CompanyService) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo) - v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, configFile.CorporateConsoleURL) + v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleURL) v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) diff --git a/cla-backend-go/emails/cla_manager_templates.go b/cla-backend-go/emails/cla_manager_templates.go index 364b83068..4c7b85e45 100644 --- a/cla-backend-go/emails/cla_manager_templates.go +++ b/cla-backend-go/emails/cla_manager_templates.go @@ -262,7 +262,7 @@ list of company’s CLA Managers for {{.Project.ExternalProjectName}}.

    ` ) -// RenderClaManagerAddedToCLAManagersTemplate renders the RemovedCLAManagerTemplate +// RenderClaManagerAddedToCLAManagersTemplate renders the ClaManagerAddedToCLAManagersTemplate func RenderClaManagerAddedToCLAManagersTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params ClaManagerAddedToCLAManagersTemplateParams) (string, error) { claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID) if err != nil { diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index 557e0fa20..b053fc1a6 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -46,16 +46,13 @@ type ToCLAManagerDesigneeModel struct { projectNames []string projectSFIDs []string contributorModel emails.Contributor - contributorEmail string - contributorName string } // DesigneeEmailToUserWithNoLFIDModel data model for sending emails type DesigneeEmailToUserWithNoLFIDModel struct { userWithNoLFIDName string userWithNoLFIDEmail string - requesterUsername string - requesterEmail string + contributorModel emails.Contributor projectNames []string projectSFIDs []string foundationSFID string @@ -117,7 +114,7 @@ func (s *service) SendEmailToCLAManager(ctx context.Context, input *EmailToCLAMa subject := fmt.Sprintf("EasyCLA: Approval Request for contributor: %s", getBestUserName(input.Contributor)) recipients := []string{input.CLAManagerEmail} - body, err := emails.RenderV2ContributorApprovalRequestTemplate(s.emailSvc, projectSFIDs, emails.V2ContributorApprovalRequestTemplateParams{ + body, err := emails.RenderV2ContributorApprovalRequestTemplate(s.emailTemplateService, projectSFIDs, emails.V2ContributorApprovalRequestTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.CLAManagerName, CompanyName: input.CompanyName, @@ -155,7 +152,7 @@ func (s *service) SendEmailToOrgAdmin(ctx context.Context, input EmailToOrgAdmin subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", input.companyName) recipients := []string{input.adminEmail} - body, err := emails.RenderV2OrgAdminTemplate(s.emailSvc, input.projectSFID, emails.V2OrgAdminTemplateParams{ + body, err := emails.RenderV2OrgAdminTemplate(s.emailTemplateService, input.projectSFID, emails.V2OrgAdminTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.adminName, CompanyName: input.companyName, @@ -192,7 +189,7 @@ func (s *service) ContributorEmailToOrgAdmin(ctx context.Context, input Contribu subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", input.companyName, getBestUserName(input.contributor)) recipients := []string{input.adminEmail} - body, err := emails.RenderV2ContributorToOrgAdminTemplate(s.emailSvc, input.projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ + body, err := emails.RenderV2ContributorToOrgAdminTemplate(s.emailTemplateService, input.projectSFIDs, emails.V2ContributorToOrgAdminTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.adminName, CompanyName: input.companyName, @@ -225,7 +222,7 @@ func (s *service) SendEmailToCLAManagerDesigneeCorporate(ctx context.Context, in subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA ", input.companyName) recipients := []string{input.designeeEmail} - body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(s.emailSvc, input.projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ + body, err := emails.RenderV2CLAManagerDesigneeCorporateTemplate(s.emailTemplateService, input.projectSFID, emails.V2CLAManagerDesigneeCorporateTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.designeeName, CompanyName: input.companyName, @@ -253,25 +250,20 @@ func (s *service) SendEmailToCLAManagerDesignee(ctx context.Context, input ToCLA "projectNames": strings.Join(input.projectNames, ","), "designeeEmail": input.designeeEmail, "designeeName": input.designeeName, - "contributorEmail": input.contributorEmail, - "contributorName": input.contributorName, + "contributorEmail": input.contributorModel.Email, + "contributorName": input.contributorModel.Username, } subject := fmt.Sprintf("EasyCLA: Invitation to Sign the %s Corporate CLA and add to approved list %s ", - input.companyName, input.contributorEmail) + input.companyName, input.contributorModel.Email) recipients := []string{input.designeeEmail} - body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(s.emailSvc, input.projectSFIDs, + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(s.emailTemplateService, input.projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.designeeName, CompanyName: input.companyName, }, - Contributor: emails.Contributor{ - Email: input.contributorEmail, - Username: input.contributorName, - EmailLabel: "", - UsernameLabel: "", - }, + Contributor: input.contributorModel, }, emails.V2ToCLAManagerDesigneeTemplate, emails.V2ToCLAManagerDesigneeTemplateName) if err != nil { @@ -295,22 +287,19 @@ func (s *service) SendDesigneeEmailToUserWithNoLFID(ctx context.Context, input D "organizationID": input.organizationID, "projectNames": strings.Join(input.projectNames, ","), "role": input.role, - "requesterUsername": input.requesterUsername, - "requesterEmail": input.requesterEmail, + "requesterUsername": input.contributorModel.Username, + "requesterEmail": input.contributorModel.Email, } subject := "EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager" - body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(s.emailSvc, input.projectSFIDs, + body, err := emails.RenderV2ToCLAManagerDesigneeTemplate(s.emailTemplateService, input.projectSFIDs, emails.V2ToCLAManagerDesigneeTemplateParams{ - Contributor: emails.Contributor{ - Email: input.requesterEmail, - Username: input.requesterUsername, - }, CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.userWithNoLFIDName, CompanyName: input.companyName, }, + Contributor: input.contributorModel, }, emails.V2DesigneeToUserWithNoLFIDTemplate, emails.V2DesigneeToUserWithNoLFIDTemplateName) if err != nil { @@ -356,7 +345,7 @@ func (s *service) SendEmailToUserWithNoLFID(ctx context.Context, input EmailToUs // subject string, body string, recipients []string subject := fmt.Sprintf("EasyCLA: Invitation to create LF Login and complete process of becoming CLA Manager with %s role", input.role) - body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(s.emailSvc, input.projectID, emails.V2CLAManagerToUserWithNoLFIDTemplateParams{ + body, err := emails.RenderV2CLAManagerToUserWithNoLFIDTemplate(s.emailTemplateService, input.projectID, emails.V2CLAManagerToUserWithNoLFIDTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: input.userWithNoLFIDName, CompanyName: input.companyName, diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index d57d84b74..0c2d297da 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -69,15 +69,15 @@ const ( ) type service struct { - emailSvc emails.EmailTemplateService - companyService company.IService - projectService project.Service - repositoriesService repositories.Service - managerService v1ClaManager.IService - easyCLAUserService easyCLAUser.Service - v2CompanyService v2Company.Service - eventService events.Service - projectCGRepo projects_cla_groups.Repository + emailTemplateService emails.EmailTemplateService + companyService company.IService + projectService project.Service + repositoriesService repositories.Service + managerService v1ClaManager.IService + easyCLAUserService easyCLAUser.Service + v2CompanyService v2Company.Service + eventService events.Service + projectCGRepo projects_cla_groups.Repository } // Service interface @@ -103,19 +103,19 @@ type Service interface { } // NewService returns instance of CLA Manager service -func NewService(emailSvc emails.EmailTemplateService, compService company.IService, projService project.Service, mgrService v1ClaManager.IService, claUserService easyCLAUser.Service, +func NewService(emailTemplateService emails.EmailTemplateService, compService company.IService, projService project.Service, mgrService v1ClaManager.IService, claUserService easyCLAUser.Service, repoService repositories.Service, v2CompService v2Company.Service, evService events.Service, projectCGroupRepo projects_cla_groups.Repository) Service { return &service{ - emailSvc: emailSvc, - companyService: compService, - projectService: projService, - repositoriesService: repoService, - managerService: mgrService, - easyCLAUserService: claUserService, - v2CompanyService: v2CompService, - eventService: evService, - projectCGRepo: projectCGroupRepo, + emailTemplateService: emailTemplateService, + companyService: compService, + projectService: projService, + repositoriesService: repoService, + managerService: mgrService, + easyCLAUserService: claUserService, + v2CompanyService: v2CompService, + eventService: evService, + projectCGRepo: projectCGroupRepo, } } @@ -1037,8 +1037,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com sendErr := s.SendDesigneeEmailToUserWithNoLFID(ctx, DesigneeEmailToUserWithNoLFIDModel{ userWithNoLFIDName: name, userWithNoLFIDEmail: userEmail, - requesterUsername: contributorModel.Email, - requesterEmail: contributorModel.Email, + contributorModel: contributorModel, projectNames: projectSFs, projectSFIDs: projectSFIDs, foundationSFID: foundationSFID, @@ -1093,8 +1092,6 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com companyName: organization.Name, projectNames: projectSFs, projectSFIDs: projectSFIDs, - contributorEmail: contributor.LFEmail, - contributorName: contributor.LFUsername, contributorModel: contributorModel, }) } else { From 9d7b1f84b0da492a59b272cf4aa419ac33d8ed4f Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 9 Mar 2021 14:05:15 -0800 Subject: [PATCH 0149/1276] Resolved 2716 Enroll/Unenroll Event Logs (#2765) - Resolved issue when setting event log fields - fixed CLA Group ID/Name, Project ID/Name, Parent ID/Name (including lower-case issue) - Resolved unit tests/mocks Signed-off-by: David Deal --- .../cmd/dynamo_events_lambda/main.go | 4 ++ .../org_service/org_service.go | 4 ++ cla-backend-go/cmd/server.go | 2 + cla-backend-go/events/event_data.go | 14 ++---- cla-backend-go/events/mockrepo.go | 46 +++++++++++++++++++ cla-backend-go/events/repository.go | 4 +- cla-backend-go/events/service.go | 40 ++++++++++------ cla-backend-go/tests/events_test.go | 2 +- cla-backend-go/v2/cla_groups/helpers.go | 43 ++++++++--------- 9 files changed, 112 insertions(+), 47 deletions(-) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 2e9c30e74..fbc6f20f7 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -110,12 +110,16 @@ func init() { users.UserRepository company.IRepository project.ProjectRepository + projects_cla_groups.Repository } + eventsService := claevents.NewService(eventsRepo, combinedRepo{ usersRepo, companyRepo, projectRepo, + projectClaGroupRepo, }) + usersService := users.NewService(usersRepo, eventsService) companyService := company.NewService(companyRepo, configFile.CorporateConsoleURL, userRepo, usersService) v2CompanyService := v2Company.NewService(companyService, signaturesRepo, projectRepo, usersRepo, companyRepo, projectClaGroupRepo, eventsService) diff --git a/cla-backend-go/cmd/functional_tests/org_service/org_service.go b/cla-backend-go/cmd/functional_tests/org_service/org_service.go index d05685fe3..d2d03e558 100644 --- a/cla-backend-go/cmd/functional_tests/org_service/org_service.go +++ b/cla-backend-go/cmd/functional_tests/org_service/org_service.go @@ -57,7 +57,9 @@ func (t *TestBehaviour) RunIsUserHaveRoleScope() { users.UserRepository company.IRepository project.ProjectRepository + projects_cla_groups.Repository } + eventsRepo := events.NewRepository(awsSession, stage) usersRepo := users.NewRepository(awsSession, stage) companyRepo := company.NewRepository(awsSession, stage) @@ -70,7 +72,9 @@ func (t *TestBehaviour) RunIsUserHaveRoleScope() { usersRepo, companyRepo, projectRepo, + projectClaGroupRepo, }) + organization_service.InitClient(configFile.APIGatewayURL, eventsService) acs_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) acsClient := acs_service.GetClient() diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 37714a739..53946431c 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -124,6 +124,7 @@ type combinedRepo struct { users.UserRepository v1Company.IRepository project.ProjectRepository + projects_cla_groups.Repository } // server function called by environment specific server functions @@ -248,6 +249,7 @@ func server(localMode bool) http.Handler { usersRepo, v1CompanyRepo, projectRepo, + projectClaGroupRepo, }) // Initialize the external platform services - these are external APIs that diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index ae3ddecb3..d6d54d7b8 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -359,13 +359,13 @@ type ClaManagerRoleDeletedData struct { // GetEventDetailsString . . . func (ed *CLAGroupEnrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("%s (%s/%s) enabled the the project %s (%s) from the CLA Group %s (%s).", - args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.ClaGroupModel.ProjectName, args.ClaGroupModel.ProjectID), false + args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false } // GetEventDetailsString . . . func (ed *CLAGroupUnenrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("%s (%s/%s) unenrolled the the project %s (%s) from the CLA Group %s (%s).", - args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.ClaGroupModel.ProjectName, args.ClaGroupModel.ProjectID), false + args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false } // GetEventDetailsString . . . @@ -754,13 +754,13 @@ func (ed *ClaManagerRoleDeletedData) GetEventDetailsString(args *LogEventArgs) ( // GetEventDetailsString . . . func (ed *CLAGroupEnrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("The user %s enabled the the project %s from the CLA Group %s.", - args.UserName, args.ProjectName, args.ClaGroupModel.ProjectName), false + args.UserName, args.ProjectName, args.CLAGroupName), false } // GetEventDetailsString . . . func (ed *CLAGroupUnenrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("The user %s unenrolled the the project %s from the CLA Group %s.", - args.UserName, args.ProjectName, args.ClaGroupModel.ProjectName), false + args.UserName, args.ProjectName, args.CLAGroupName), false } // GetEventDetailsString . . . @@ -775,9 +775,6 @@ func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } - if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) - } data = data + "." return data, false } @@ -794,9 +791,6 @@ func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArg if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } - if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) - } data = data + "." return data, false } diff --git a/cla-backend-go/events/mockrepo.go b/cla-backend-go/events/mockrepo.go index 4f82ab047..e4a5def3f 100644 --- a/cla-backend-go/events/mockrepo.go +++ b/cla-backend-go/events/mockrepo.go @@ -7,6 +7,11 @@ import ( "context" "time" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/gen/models" eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" "github.com/go-openapi/strfmt" @@ -39,6 +44,47 @@ func (repo *mockRepository) GetClaGroupEvents(claGroupID string, nextKey *string panic("implement me") } +func (repo *mockRepository) GetClaGroupIDForProject(projectSFID string) (*projects_cla_groups.ProjectClaGroup, error) { + return nil, nil +} + +func (repo *mockRepository) LogEvent(args *LogEventArgs) { + repo.LogEventWithContext(utils.NewContext(), args) +} + +func (repo *mockRepository) LogEventWithContext(ctx context.Context, args *LogEventArgs) { + event := models.Event{ + EventType: args.EventType, + + UserID: args.UserID, + UserName: args.UserName, + LfUsername: args.LfUsername, + + EventCLAGroupID: args.CLAGroupID, + EventCLAGroupName: args.CLAGroupName, + + EventCompanyID: args.CompanyID, + EventCompanySFID: args.CompanySFID, + EventCompanyName: args.CompanyName, + + EventProjectID: args.ProjectID, + EventProjectSFID: args.ProjectSFID, + EventProjectName: args.ProjectName, + EventParentProjectSFID: args.ParentProjectSFID, + EventParentProjectName: args.ParentProjectName, + + //EventData: eventData, + //EventSummary: eventSummary, + + //ContainsPII: containsPII, + } + + err := repo.CreateEvent(&event) + if err != nil { + log.WithError(err).Warn("unable to create event") + } +} + var events []*models.Event // NewMockRepository creates a new instance of the mock event repository diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 0b79bbb0b..87a5614d9 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -128,8 +128,8 @@ func (repo *repository) CreateEvent(event *models.Event) error { addAttribute(input.Item, "event_project_sfid", event.EventProjectSFID) addAttribute(input.Item, "event_project_name", event.EventProjectName) addAttribute(input.Item, "event_project_name_lower", strings.ToLower(event.EventProjectName)) - addAttribute(input.Item, "event_parent_project_sfid", strings.ToLower(event.EventParentProjectSFID)) - addAttribute(input.Item, "event_parent_project_name", strings.ToLower(event.EventParentProjectName)) + addAttribute(input.Item, "event_parent_project_sfid", event.EventParentProjectSFID) + addAttribute(input.Item, "event_parent_project_name", event.EventParentProjectName) addAttribute(input.Item, "event_data", event.EventData) addAttribute(input.Item, "event_summary", event.EventSummary) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 177f299e5..dbef1e7ff 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -8,6 +8,8 @@ import ( "errors" "fmt" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/sirupsen/logrus" @@ -46,6 +48,7 @@ type CombinedRepo interface { GetCompany(ctx context.Context, companyID string) (*models.Company, error) GetUserByUserName(userName string, fullMatch bool) (*models.User, error) GetUser(userID string) (*models.User, error) + GetClaGroupIDForProject(projectSFID string) (*projects_cla_groups.ProjectClaGroup, error) } type service struct { @@ -165,15 +168,14 @@ func (s *service) loadCompany(ctx context.Context, args *LogEventArgs) error { func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ - "functionName": "loadCLAGroup", + "functionName": "events.service.loadCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } // First, attempt to user the CLA Group model that was provided... if args.ClaGroupModel != nil { args.CLAGroupID = args.ClaGroupModel.ProjectID - args.ProjectName = args.ClaGroupModel.ProjectName - args.ProjectSFID = args.ClaGroupModel.ProjectExternalID + args.CLAGroupName = args.ClaGroupModel.ProjectName } else { // Did they set the CLA Group ID? claGroupID := "" @@ -192,7 +194,15 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { } args.ClaGroupModel = claGroupModel args.CLAGroupName = claGroupModel.ProjectName - args.ProjectSFID = claGroupModel.ProjectExternalID + } else if args.ProjectSFID != "" { + projectCLAGroupModel, projectCLAGroupErr := s.combinedRepo.GetClaGroupIDForProject(args.ProjectSFID) + if projectCLAGroupErr != nil || projectCLAGroupModel == nil { + log.WithFields(f).WithError(projectCLAGroupErr).Warnf("failed to load project CLA Group mapping by SFID: %s", args.ProjectSFID) + return nil + } + + args.CLAGroupID = projectCLAGroupModel.ClaGroupID + args.CLAGroupName = projectCLAGroupModel.ClaGroupName } } @@ -205,10 +215,15 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - if utils.IsUUIDv4(args.ProjectID) { // internal CLA Group ID - return s.loadCLAGroup(ctx, args) - } else if utils.IsSalesForceID(args.ProjectID) { // external SF project ID + // Should be the same value for now...cleanup: need to remove one or the other + if args.ProjectID == "" && args.ProjectSFID != "" { + args.ProjectID = args.ProjectSFID + } + if args.ProjectSFID == "" && args.ProjectID != "" { args.ProjectSFID = args.ProjectID + } + + if utils.IsSalesForceID(args.ProjectID) { // Check if project exists in platform project service log.WithFields(f).Debugf("loading salesforce project by ID: %s...", args.ProjectSFID) project, projectErr := project_service.GetClient().GetProject(args.ProjectSFID) @@ -227,12 +242,11 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", project.Parent) return nil } - log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s", project.Parent) + log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s - resulting in ID: %s with name: %s", + project.Parent, parentProject.ID, parentProject.Name) args.ParentProjectSFID = parentProject.ID args.ParentProjectName = parentProject.Name } - - return nil } return nil @@ -290,12 +304,12 @@ func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { return err } - err = s.loadCLAGroup(ctx, args) + err = s.loadSFProject(ctx, args) if err != nil { return err } - err = s.loadSFProject(ctx, args) + err = s.loadCLAGroup(ctx, args) if err != nil { return err } @@ -342,7 +356,7 @@ func (s *service) LogEventWithContext(ctx context.Context, args *LogEventArgs) { LfUsername: args.LfUsername, EventCLAGroupID: args.CLAGroupID, - EventCLAGroupName: args.ProjectName, + EventCLAGroupName: args.CLAGroupName, EventCompanyID: args.CompanyID, EventCompanySFID: args.CompanySFID, diff --git a/cla-backend-go/tests/events_test.go b/cla-backend-go/tests/events_test.go index ba8e3a74b..20a8cd365 100644 --- a/cla-backend-go/tests/events_test.go +++ b/cla-backend-go/tests/events_test.go @@ -32,5 +32,5 @@ func TestEventsService(t *testing.T) { CompanyID: aws.String("company-1234"), }) assert.Nil(t, err, "Error is nil") - assert.Equal(t, len(eventsSearch.Events), 1) + assert.Equal(t, 1, len(eventsSearch.Events)) } diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 7a0dd68f9..763f7fe1f 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -402,14 +402,14 @@ func (s *service) AssociateCLAGroupWithProjects(ctx context.Context, request *As for _, projectSFID := range request.ProjectSFIDList { // Invoke the go routine - any errors will be handled below - go func(sfid string) { + go func(projectSFID, parentProjectSFID, claGroupID string) { defer wg.Done() - log.WithFields(f).Debugf("associating cla_group with project: %s", sfid) - err := s.projectsClaGroupsRepo.AssociateClaGroupWithProject(request.CLAGroupID, sfid, request.FoundationSFID) + log.WithFields(f).Debugf("associating cla_group with project: %s", projectSFID) + err := s.projectsClaGroupsRepo.AssociateClaGroupWithProject(claGroupID, projectSFID, parentProjectSFID) if err != nil { - log.WithFields(f).WithError(err).Warnf("associating cla_group with project: %s failed", sfid) + log.WithFields(f).WithError(err).Warnf("associating cla_group with project: %s failed", projectSFID) log.WithFields(f).Debug("deleting stale entries from cla_group project association") - deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(request.CLAGroupID, request.ProjectSFIDList, false) + deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(claGroupID, request.ProjectSFIDList, false) if deleteErr != nil { log.WithFields(f).WithError(deleteErr).Warn("deleting stale entries from cla_group project association failed") } @@ -418,13 +418,14 @@ func (s *service) AssociateCLAGroupWithProjects(ctx context.Context, request *As } // add event log entry s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.CLAGroupEnrolledProject, - ProjectSFID: sfid, - CLAGroupID: request.CLAGroupID, - LfUsername: request.AuthUser.UserName, - EventData: &events.CLAGroupEnrolledProjectData{}, + EventType: events.CLAGroupEnrolledProject, + ProjectSFID: projectSFID, + ParentProjectSFID: parentProjectSFID, + CLAGroupID: claGroupID, + LfUsername: request.AuthUser.UserName, + EventData: &events.CLAGroupEnrolledProjectData{}, }) - }(projectSFID) + }(projectSFID, request.FoundationSFID, request.CLAGroupID) } // Wait for the go routines to finish @@ -491,19 +492,19 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, pro for _, projectSFID := range projectSFIDList { // Execute as a go routine - go func(psClient *v2ProjectService.Client, sfid string) { + go func(psClient *v2ProjectService.Client, projectSFID string) { defer wg.Done() - enableProjectErr := psClient.EnableCLA(sfid) + enableProjectErr := psClient.EnableCLA(projectSFID) if enableProjectErr != nil { log.WithFields(f).WithError(enableProjectErr). - Warnf("unable to enable CLA service for project: %s, error: %+v", sfid, enableProjectErr) + Warnf("unable to enable CLA service for project: %s, error: %+v", projectSFID, enableProjectErr) errorList = append(errorList, enableProjectErr) } else { - log.WithFields(f).Debugf("enabled CLA service for project: %s", sfid) + log.WithFields(f).Debugf("enabled CLA service for project: %s", projectSFID) // add event log entry s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ProjectServiceCLAEnabled, - ProjectID: sfid, + ProjectID: projectSFID, LfUsername: authUser.UserName, EventData: &events.ProjectServiceCLAEnabledData{}, }) @@ -541,19 +542,19 @@ func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, pr for _, projectSFID := range projectSFIDList { // Execute as a go routine - go func(psClient *v2ProjectService.Client, sfid string) { + go func(psClient *v2ProjectService.Client, projectSFID string) { defer wg.Done() - disableProjectErr := psClient.DisableCLA(sfid) + disableProjectErr := psClient.DisableCLA(projectSFID) if disableProjectErr != nil { log.WithFields(f).WithError(disableProjectErr). - Warnf("unable to disable CLA service for project: %s, error: %+v", sfid, disableProjectErr) + Warnf("unable to disable CLA service for project: %s, error: %+v", projectSFID, disableProjectErr) errorList = append(errorList, disableProjectErr) } else { - log.WithFields(f).Debugf("disabled CLA service for project: %s", sfid) + log.WithFields(f).Debugf("disabled CLA service for project: %s", projectSFID) // add event log entry s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ProjectServiceCLADisabled, - ProjectID: sfid, + ProjectID: projectSFID, LfUsername: authUser.UserName, EventData: &events.ProjectServiceCLADisabledData{}, }) From 4906606f20e7b277d86181283955d82b337124b4 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 10 Mar 2021 04:02:12 +0300 Subject: [PATCH 0150/1276] [#PCC-626] Feature/GH org installation URL (#2766) - Updated response for gh orgs with installation URL Signed-off-by: wanyaland --- cla-backend-go/github_organizations/helpers.go | 15 ++++++++++++--- .../swagger/common/github-organization.yaml | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/github_organizations/helpers.go b/cla-backend-go/github_organizations/helpers.go index bd34f67b5..f256e38d6 100644 --- a/cla-backend-go/github_organizations/helpers.go +++ b/cla-backend-go/github_organizations/helpers.go @@ -5,6 +5,8 @@ package github_organizations import ( "context" + "fmt" + netURL "net/url" "sync" "github.com/communitybridge/easycla/cla-backend-go/gen/models" @@ -38,10 +40,17 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations ghorg.GithubInfo.Error = err.Error() } else { url := strfmt.URI(*user.HTMLURL) + installURL := netURL.URL{ + Scheme: "https", + Host: "github.com", + Path: fmt.Sprintf("/%s/settings/installations/%d", ghorg.OrganizationName, ghorg.OrganizationInstallationID), + } + installationURL := strfmt.URI(installURL.String()) ghorg.GithubInfo.Details = &models.GithubOrganizationGithubInfoDetails{ - Bio: user.Bio, - HTMLURL: &url, - ID: user.ID, + Bio: user.Bio, + HTMLURL: &url, + ID: user.ID, + InstallationURL: &installationURL, } } diff --git a/cla-backend-go/swagger/common/github-organization.yaml b/cla-backend-go/swagger/common/github-organization.yaml index ee85820e0..327bd724f 100644 --- a/cla-backend-go/swagger/common/github-organization.yaml +++ b/cla-backend-go/swagger/common/github-organization.yaml @@ -60,6 +60,12 @@ properties: x-nullable: true example: "https://github.com/communitybridge" format: uri + installationURL: + type: string + x-nullable: true + example: "https://github.com/organizations/deal-test-org-2/settings/installations/1235464" + format: uri + repositories: type: object properties: From 1ca3f374e3b540320e2613d6df7871e3f9e85321 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 9 Mar 2021 17:31:34 -0800 Subject: [PATCH 0151/1276] Added Additional Event Details for Unenroll Projects (#2767) - Added CLA Group ID for event logging - Added project service get by project SFID caching Signed-off-by: David Deal --- cla-backend-go/events/repository.go | 10 ++- cla-backend-go/v2/cla_groups/helpers.go | 16 +++-- cla-backend-go/v2/cla_groups/service.go | 16 ++--- cla-backend-go/v2/project-service/client.go | 75 +++++++++++++++++++-- 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 87a5614d9..f6a95c284 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -88,6 +88,10 @@ func toDateFormat(t time.Time) string { // Create event will create event in database. func (repo *repository) CreateEvent(event *models.Event) error { + f := logrus.Fields{ + "functionName": "events.repository.CreateEvent", + } + if event.UserID == "" { return ErrUserIDRequired } @@ -96,7 +100,7 @@ func (repo *repository) CreateEvent(event *models.Event) error { } eventID, err := uuid.NewV4() if err != nil { - log.Warnf("Unable to generate a UUID for a whitelist request, error: %v", err) + log.WithFields(f).WithError(err).Warnf("Unable to generate a UUID for a whitelist request, error: %v", err) return err } @@ -147,10 +151,10 @@ func (repo *repository) CreateEvent(event *models.Event) error { _, err = repo.dynamoDBClient.PutItem(input) if err != nil { - log.Warnf("Unable to create a new event, error: %v", err) + log.WithFields(f).WithError(err).Warnf("Unable to create a new event, error: %v", err) return err } - log.Printf("added event : %s", eventID.String()) + log.WithFields(f).Infof("added event ID: %s of type: %s", eventID.String(), event.EventType) return nil } diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 763f7fe1f..cb5555221 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -474,13 +474,14 @@ func (s *service) UnassociateCLAGroupWithProjects(ctx context.Context, request * } // EnableCLAService enable CLA service attribute in the project service for the specified project list -func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error { +func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, claGroupID string, projectSFIDList []string) error { f := logrus.Fields{ "functionName": "EnableCLAService", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, "projectSFIDList": strings.Join(projectSFIDList, ","), + "claGroupID": claGroupID, } log.WithFields(f).Debug("enabling CLA service in platform project service") @@ -492,7 +493,7 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, pro for _, projectSFID := range projectSFIDList { // Execute as a go routine - go func(psClient *v2ProjectService.Client, projectSFID string) { + go func(psClient *v2ProjectService.Client, claGroupID, projectSFID string) { defer wg.Done() enableProjectErr := psClient.EnableCLA(projectSFID) if enableProjectErr != nil { @@ -505,11 +506,12 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, pro s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ProjectServiceCLAEnabled, ProjectID: projectSFID, + CLAGroupID: claGroupID, LfUsername: authUser.UserName, EventData: &events.ProjectServiceCLAEnabledData{}, }) } - }(psc, projectSFID) + }(psc, claGroupID, projectSFID) } // Wait until all go routines are done wg.Wait() @@ -524,13 +526,14 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, pro } // DisableCLAService disable CLA service attribute in the project service for the specified project list -func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error { +func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, claGroupID string, projectSFIDList []string) error { f := logrus.Fields{ "functionName": "DisableCLAService", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, "projectSFIDList": strings.Join(projectSFIDList, ","), + "claGroupID": claGroupID, } log.WithFields(f).Debug("disabling CLA service in platform project service") @@ -542,7 +545,7 @@ func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, pr for _, projectSFID := range projectSFIDList { // Execute as a go routine - go func(psClient *v2ProjectService.Client, projectSFID string) { + go func(psClient *v2ProjectService.Client, claGroupID, projectSFID string) { defer wg.Done() disableProjectErr := psClient.DisableCLA(projectSFID) if disableProjectErr != nil { @@ -555,11 +558,12 @@ func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, pr s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ProjectServiceCLADisabled, ProjectID: projectSFID, + CLAGroupID: claGroupID, LfUsername: authUser.UserName, EventData: &events.ProjectServiceCLADisabledData{}, }) } - }(psc, projectSFID) + }(psc, claGroupID, projectSFID) } // Wait until all go routines are done wg.Wait() diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 3499a5d9d..885563452 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -63,8 +63,8 @@ type Service interface { UnenrollProjectsInClaGroup(ctx context.Context, request *UnenrollProjectsModel) error AssociateCLAGroupWithProjects(ctx context.Context, request *AssociateCLAGroupWithProjectsModel) error UnassociateCLAGroupWithProjects(ctx context.Context, request *UnassociateCLAGroupWithProjectsModel) error - EnableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error - DisableCLAService(ctx context.Context, authUser *auth.User, projectSFIDList []string) error + EnableCLAService(ctx context.Context, authUser *auth.User, claGroupID string, projectSFIDList []string) error + DisableCLAService(ctx context.Context, authUser *auth.User, claGroupID string, projectSFIDList []string) error ValidateCLAGroup(ctx context.Context, input *models.ClaGroupValidationRequest) (bool, []string) } @@ -928,16 +928,16 @@ func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollP }(ctx, request.AuthUser, request.CLAGroupID, request.FoundationSFID, request.ProjectSFIDList) // Separate go routine for enabling the CLA Service in the project service - go func(c context.Context, projSFIDList []string) { + go func(c context.Context, claGroupID string, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("enabling CLA service in platform project service") // Note: log entry will be created by enable CLA Service call - errEnableCLA := s.EnableCLAService(c, request.AuthUser, projSFIDList) + errEnableCLA := s.EnableCLAService(c, request.AuthUser, claGroupID, projSFIDList) if errEnableCLA != nil { log.WithFields(f).WithError(errEnableCLA).Warn("enabling CLA service in platform project service failed") errorList = append(errorList, errEnableCLA) } - }(ctx, request.ProjectSFIDList) + }(ctx, request.CLAGroupID, request.ProjectSFIDList) // Wait until all go routines are done wg.Wait() @@ -990,16 +990,16 @@ func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *Unenr }(ctx, request.AuthUser, request.CLAGroupID, request.FoundationSFID, request.ProjectSFIDList) // Separate go routine for disabling the CLA Service in the project service - go func(c context.Context, projSFIDList []string) { + go func(c context.Context, claGroupID string, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("disabling CLA service in platform project service") // Note: log entry will be created by disable CLA Service call - errDisableCLA := s.DisableCLAService(c, request.AuthUser, projSFIDList) + errDisableCLA := s.DisableCLAService(c, request.AuthUser, claGroupID, projSFIDList) if errDisableCLA != nil { log.WithFields(f).WithError(errDisableCLA).Warn("disabling CLA service in platform project service failed") errorList = append(errorList, errDisableCLA) } - }(ctx, request.ProjectSFIDList) + }(ctx, request.CLAGroupID, request.ProjectSFIDList) // Wait until all go routines are done wg.Wait() diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 8b682a9ea..00831915d 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -34,6 +34,8 @@ type Client struct { var ( projectServiceClient *Client + // Short term cache - only for the lifetime of this lambda + projectServiceModes = make(map[string]*models.ProjectOutputDetailed) ) // InitClient initializes the user_service client @@ -65,37 +67,77 @@ func (pmm *Client) getProject(projectSFID string, auth runtime.ClientAuthInfoWri // GetProject returns project details func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed, error) { + f := logrus.Fields{ + "functionName": "v2.project-service.client.GetProject", + "projectSFID": projectSFID, + } + + // Lookup in cache first + existingModel, exists := projectServiceModes[projectSFID] + if exists { + log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModes)) + return existingModel, nil + } + log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModes)) + tok, err := token.GetToken() if err != nil { return nil, err } clientAuth := runtimeClient.BearerToken(tok) - return pmm.getProject(projectSFID, clientAuth) + + // Lookup the project + projectModel, err := pmm.getProject(projectSFID, clientAuth) + if err != nil { + return nil, err + } + + // Update our cache for next time + projectServiceModes[projectSFID] = projectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModes)) + + return projectModel, nil } // GetProjectByName returns project details for the associated project name func (pmm *Client) GetProjectByName(projectName string) (*models.ProjectListSearch, error) { + f := logrus.Fields{ + "functionName": "v2.project-service.client.GetProjectByName", + "projectName": projectName, + } tok, err := token.GetToken() if err != nil { + log.WithFields(f).WithError(err).Warning("problem retrieving token") return nil, err } + clientAuth := runtimeClient.BearerToken(tok) result, err := pmm.cl.Project.SearchProjects(&project.SearchProjectsParams{ Name: []string{projectName}, }, clientAuth) if err != nil { + log.WithFields(f).WithError(err).Warning("problem searching projects by name") return nil, err } + return result.Payload, nil } // GetParentProject returns the parent project SFID if there is a parent, otherwise returns the provided projectSFID func (pmm *Client) GetParentProject(projectSFID string) (string, error) { f := logrus.Fields{ - "functionName": "getParentProject", + "functionName": "v2.project-service.client.GetParentProject", "projectSFID": projectSFID, } + // Lookup in cache first + existingModel, exists := projectServiceModes[projectSFID] + if exists { + log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModes)) + return existingModel.Parent, nil + } + log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModes)) + log.WithFields(f).Debug("looking up projectModel in SF by projectSFID") projectModel, err := pmm.GetProject(projectSFID) if err != nil { @@ -103,6 +145,10 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { return "", err } + // Update our cache for next time + projectServiceModes[projectSFID] = projectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModes)) + // Do they have a parent? if projectModel.Parent == "" || (projectModel.Foundation != nil && (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { @@ -117,7 +163,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { // IsTheLinuxFoundation returns true if the specified project SFID is the The Linux Foundation project func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { f := logrus.Fields{ - "functionName": "project-service.IsTheLinuxFoundation", + "functionName": "v2.project-service.client.IsTheLinuxFoundation", + "projectSFID": projectSFID, } log.WithFields(f).Debug("querying project...") @@ -139,7 +186,8 @@ func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { // IsParentTheLinuxFoundation returns true if the parent is the The Linux Foundation project func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) { f := logrus.Fields{ - "functionName": "IsParentTheLinuxFoundation", + "functionName": "v2.project-service.client.IsParentTheLinuxFoundation", + "projectSFID": projectSFID, } log.WithFields(f).Debug("querying project...") @@ -170,21 +218,31 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) // EnableCLA enables CLA service in project-service func (pmm *Client) EnableCLA(projectSFID string) error { + f := logrus.Fields{ + "functionName": "v2.project-service.client.EnableCLA", + "projectSFID": projectSFID, + } + tok, err := token.GetToken() if err != nil { + log.WithFields(f).WithError(err).Warning("problem retrieving token") return err } clientAuth := runtimeClient.BearerToken(tok) + projectDetails, err := pmm.getProject(projectSFID, clientAuth) if err != nil { + log.WithFields(f).WithError(err).Warning("problem retrieving project by SFID") return err } + for _, serviceName := range projectDetails.EnabledServices { if serviceName == CLA { // CLA already enabled return nil } } + enabledServices := projectDetails.EnabledServices enabledServices = append(enabledServices, CLA) return pmm.updateEnabledServices(projectSFID, enabledServices, clientAuth) @@ -210,15 +268,24 @@ func (pmm *Client) updateEnabledServices(projectSFID string, enabledServices []s // DisableCLA enables CLA service in project-service func (pmm *Client) DisableCLA(projectSFID string) error { + f := logrus.Fields{ + "functionName": "v2.project-service.client.DisableCLA", + "projectSFID": projectSFID, + } + tok, err := token.GetToken() if err != nil { + log.WithFields(f).WithError(err).Warning("problem retrieving token") return err } clientAuth := runtimeClient.BearerToken(tok) + projectDetails, err := pmm.getProject(projectSFID, clientAuth) if err != nil { + log.WithFields(f).WithError(err).Warning("problem retrieving project by SFID") return err } + newEnabledServices := make([]string, 0) var claFound bool for _, serviceName := range projectDetails.EnabledServices { From 87cd9921da7afebf8fd9ead75744707ae993293d Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Mar 2021 09:37:57 -0800 Subject: [PATCH 0152/1276] Resolved Index Out of Range Error (#2768) - return request ID without having to query the record again - avoid data consistency issue (save -> read) - renamed a few functions to match current naming conventions - updated logging Signed-off-by: David Deal --- cla-backend-go/approval_list/handlers.go | 2 +- cla-backend-go/approval_list/repository.go | 80 +++++++++++----------- cla-backend-go/approval_list/service.go | 56 +++++++-------- 3 files changed, 70 insertions(+), 68 deletions(-) diff --git a/cla-backend-go/approval_list/handlers.go b/cla-backend-go/approval_list/handlers.go index 0a6b59bec..932cafbc8 100644 --- a/cla-backend-go/approval_list/handlers.go +++ b/cla-backend-go/approval_list/handlers.go @@ -91,7 +91,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore "functionName": "CompanyListCclaWhitelistRequestsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - log.WithFields(f).Debugf("Invoking ListCclaWhitelistRequest with Company ID: %+v, Project ID: %+v, Status: %+v", + log.WithFields(f).Debugf("Invoking ListCclaApprovalListRequests with Company ID: %+v, Project ID: %+v, Status: %+v", params.CompanyID, params.ProjectID, params.Status) result, err := service.ListCclaWhitelistRequest(params.CompanyID, params.ProjectID, params.Status) if err != nil { diff --git a/cla-backend-go/approval_list/repository.go b/cla-backend-go/approval_list/repository.go index 5d9697f7c..6a025bdc7 100644 --- a/cla-backend-go/approval_list/repository.go +++ b/cla-backend-go/approval_list/repository.go @@ -33,13 +33,13 @@ const ( ProjectIDIndex = "ccla-approval-list-request-project-id-index" ) -// IRepository interface defines the functions for the whitelist service +// IRepository interface defines the functions for the approval list service type IRepository interface { - AddCclaWhitelistRequest(company *models.Company, project *models.ClaGroup, user *models.User, requesterName, requesterEmail string) (string, error) - GetCclaWhitelistRequest(requestID string) (*CLARequestModel, error) - ApproveCclaWhitelistRequest(requestID string) error - RejectCclaWhitelistRequest(requestID string) error - ListCclaWhitelistRequest(companyID string, projectID, status, userID *string) (*models.CclaWhitelistRequestList, error) + AddCclaApprovalRequest(company *models.Company, project *models.ClaGroup, user *models.User, requesterName, requesterEmail string) (string, error) + GetCclaApprovalListRequest(requestID string) (*CLARequestModel, error) + ApproveCclaApprovalListRequest(requestID string) error + RejectCclaApprovalListRequest(requestID string) error + ListCclaApprovalListRequests(companyID string, projectID, status, userID *string) (*models.CclaWhitelistRequestList, error) GetRequestsByCLAGroup(claGroupID string) ([]CLARequestModel, error) UpdateRequestsByCLAGroup(model *project.DBProjectModel) error } @@ -50,19 +50,19 @@ type repository struct { tableName string } -// NewRepository creates a new instance of the whitelist service +// NewRepository creates a new instance of the approval list service func NewRepository(awsSession *session.Session, stage string) IRepository { return repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), - tableName: fmt.Sprintf("cla-%s-ccla-whitelist-requests", stage), + tableName: fmt.Sprintf("cla-%s-ccla-whitelist-requests", stage), // TODO: rename table } } -// AddCclaWhitelistRequest adds the specified request -func (repo repository) AddCclaWhitelistRequest(company *models.Company, project *models.ClaGroup, user *models.User, requesterName, requesterEmail string) (string, error) { +// AddCclaApprovalRequest adds the specified request +func (repo repository) AddCclaApprovalRequest(company *models.Company, project *models.ClaGroup, user *models.User, requesterName, requesterEmail string) (string, error) { f := logrus.Fields{ - "functionName": "AddCclaWhitelistRequest", + "functionName": "v1.approval_list.repository.AddCclaApprovalRequest", "requesterName": requesterName, "requesterEmail": requesterEmail, } @@ -96,24 +96,17 @@ func (repo repository) AddCclaWhitelistRequest(company *models.Company, project _, err = repo.dynamoDBClient.PutItem(input) if err != nil { - log.WithFields(f).Warnf("AddCclaWhitelistRequest - unable to create a new ccla approval request, error: %v", err) + log.WithFields(f).Warnf("AddCclaApprovalRequest - unable to create a new ccla approval request, error: %v", err) return status, err } - // Load the new record - should be able to find it quickly - record, readErr := repo.ListCclaWhitelistRequest(company.CompanyID, &project.ProjectID, nil, &user.UserID) - if readErr != nil || record == nil || record.List == nil { - log.WithFields(f).Warnf("AddCclaWhitelistRequest - unable to read newly created invite record, error: %v", readErr) - return status, err - } - - return record.List[0].RequestID, nil + return requestID.String(), nil } -// GetCclaWhitelistRequest fetches the specified request by ID -func (repo repository) GetCclaWhitelistRequest(requestID string) (*CLARequestModel, error) { +// GetCclaApprovalListRequest fetches the specified request by ID +func (repo repository) GetCclaApprovalListRequest(requestID string) (*CLARequestModel, error) { f := logrus.Fields{ - "functionName": "GetCclaWhitelistRequest", + "functionName": "v1.approval_list.repository.GetCclaApprovalListRequest", "requestID": requestID, } @@ -141,10 +134,10 @@ func (repo repository) GetCclaWhitelistRequest(requestID string) (*CLARequestMod return &requestModel, nil } -// ApproveCclaWhitelistRequest approves the specified request -func (repo repository) ApproveCclaWhitelistRequest(requestID string) error { +// ApproveCclaApprovalListRequest approves the specified request +func (repo repository) ApproveCclaApprovalListRequest(requestID string) error { f := logrus.Fields{ - "functionName": "ApproveCclaWhitelistRequest", + "functionName": "v1.approval_list.repository.ApproveCclaApprovalListRequest", "requestID": requestID, } @@ -180,10 +173,10 @@ func (repo repository) ApproveCclaWhitelistRequest(requestID string) error { return nil } -// RejectCclaWhitelistRequest rejects the specified request -func (repo repository) RejectCclaWhitelistRequest(requestID string) error { +// RejectCclaApprovalListRequest rejects the specified request +func (repo repository) RejectCclaApprovalListRequest(requestID string) error { f := logrus.Fields{ - "functionName": "RejectCclaWhitelistRequest", + "functionName": "v1.approval_list.repository.RejectCclaApprovalListRequest", "requestID": requestID, } @@ -220,13 +213,21 @@ func (repo repository) RejectCclaWhitelistRequest(requestID string) error { return nil } -// ListCclaWhitelistRequest list the requests for the specified query parameters -func (repo repository) ListCclaWhitelistRequest(companyID string, projectID, status, userID *string) (*models.CclaWhitelistRequestList, error) { +// ListCclaApprovalListRequests list the requests for the specified query parameters +func (repo repository) ListCclaApprovalListRequests(companyID string, projectID, status, userID *string) (*models.CclaWhitelistRequestList, error) { + f := logrus.Fields{ + "functionName": "v1.approval_list.repository.ListCclaApprovalListRequests", + "companyID": companyID, + "projectID": projectID, + "status": status, + "userID": utils.StringValue(userID), + } + if projectID == nil { - return nil, errors.New("project ID can not be nil for ListCclaWhitelistRequest") + return nil, errors.New("project ID can not be nil for ListCclaApprovalListRequests") } - log.Debugf("ListCclaWhitelistRequest with Company ID: %s, Project ID: %+v, Status: %+v, User ID: %+v", + log.WithFields(f).Debugf("ListCclaApprovalListRequests with Company ID: %s, Project ID: %+v, Status: %+v, User ID: %+v", companyID, projectID, status, userID) // hashkey is company_id, range key is project_id @@ -243,7 +244,7 @@ func (repo repository) ListCclaWhitelistRequest(companyID string, projectID, sta // Add the status filter if provided if status != nil { - log.Debugf("ListCclaWhitelistRequest - Adding status: %s", *status) + log.WithFields(f).Debugf("ListCclaApprovalListRequests - Adding status: %s", *status) statusFilterExpression := expression.Name("request_status").Equal(expression.Value(*status)) filter = addConditionToFilter(filter, statusFilterExpression, &filterAdded) } @@ -260,6 +261,7 @@ func (repo repository) ListCclaWhitelistRequest(companyID string, projectID, sta // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { + log.WithFields(f).WithError(err).Warn("error building query") return nil, err } @@ -276,13 +278,13 @@ func (repo repository) ListCclaWhitelistRequest(companyID string, projectID, sta queryOutput, queryErr := repo.dynamoDBClient.Query(input) if queryErr != nil { - log.Warnf("list requests error while querying, error: %+v", queryErr) + log.WithFields(f).WithError(queryErr).Warnf("list requests error while querying, error: %+v", queryErr) return nil, queryErr } list, err := buildCclaWhitelistRequestsModels(queryOutput) if err != nil { - log.Warnf("unmarshall requests error while decoding the response, error: %+v", err) + log.WithFields(f).WithError(err).Warnf("unmarshall requests error while decoding the response, error: %+v", err) return nil, err } @@ -292,7 +294,7 @@ func (repo repository) ListCclaWhitelistRequest(companyID string, projectID, sta // GetRequestsByCLAGroup retrieves a list of requests for the specified CLA Group func (repo repository) GetRequestsByCLAGroup(claGroupID string) ([]CLARequestModel, error) { f := logrus.Fields{ - "functionName": "GetRequestsByCLAGroup", + "functionName": "v1.approval_list.repository.GetRequestsByCLAGroup", "claGroupID": claGroupID, "tableName": repo.tableName, "indexName": ProjectIDIndex, @@ -361,10 +363,10 @@ func (repo repository) GetRequestsByCLAGroup(claGroupID string) ([]CLARequestMod return projectRequests, nil } -// GetRequestsByCLAGroup retrieves a list of requests for the specified CLA Group +// UpdateRequestsByCLAGroup updates a list of requests for the specified CLA Group func (repo repository) UpdateRequestsByCLAGroup(model *project.DBProjectModel) error { f := logrus.Fields{ - "functionName": "UpdateRequestsByCLAGroup", + "functionName": "v1.approval_list.repository.UpdateRequestsByCLAGroup", "claGroupID": model.ProjectID, "tableName": repo.tableName, } diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 7c307ed1b..56ba68188 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -60,7 +60,7 @@ type service struct { httpClient *http.Client } -// NewService creates a new whitelist service +// NewService creates a new approval list service func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups.Repository, projService project.Service, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, corpConsoleURL string, httpClient *http.Client) IService { return service{ repo: repo, @@ -78,35 +78,35 @@ func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, claGroupID string, args models.CclaWhitelistRequestInput) (string, error) { list, err := s.ListCclaWhitelistRequestByCompanyProjectUser(companyID, &claGroupID, nil, &args.ContributorID) if err != nil { - log.Warnf("AddCclaWhitelistRequest - error looking up existing contributor invite requests for company: %s, project: %s, user by id: %s with name: %s, email: %s, error: %+v", + log.Warnf("AddCclaApprovalRequest - error looking up existing contributor invite requests for company: %s, project: %s, user by id: %s with name: %s, email: %s, error: %+v", companyID, claGroupID, args.ContributorID, args.ContributorName, args.ContributorEmail, err) return "", err } for _, item := range list.List { if item.RequestStatus == "pending" || item.RequestStatus == "approved" { - log.Warnf("AddCclaWhitelistRequest - found existing contributor invite - id: %s, request for company: %s, project: %s, user by id: %s with name: %s, email: %s", + log.Warnf("AddCclaApprovalRequest - found existing contributor invite - id: %s, request for company: %s, project: %s, user by id: %s with name: %s, email: %s", list.List[0].RequestID, companyID, claGroupID, args.ContributorID, args.ContributorName, args.ContributorEmail) return "", ErrCclaApprovalRequestAlreadyExists } } companyModel, err := s.companyRepo.GetCompany(ctx, companyID) if err != nil { - log.Warnf("AddCclaWhitelistRequest - unable to lookup company by id: %s, error: %+v", companyID, err) + log.Warnf("AddCclaApprovalRequest - unable to lookup company by id: %s, error: %+v", companyID, err) return "", err } claGroupModel, err := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { - log.Warnf("AddCclaWhitelistRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) + log.Warnf("AddCclaApprovalRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) return "", err } userModel, err := s.userRepo.GetUser(args.ContributorID) if err != nil { - log.Warnf("AddCclaWhitelistRequest - unable to lookup user by id: %s with name: %s, email: %s, error: %+v", + log.Warnf("AddCclaApprovalRequest - unable to lookup user by id: %s with name: %s, email: %s, error: %+v", args.ContributorID, args.ContributorName, args.ContributorEmail, err) return "", err } if userModel == nil { - log.Warnf("AddCclaWhitelistRequest - unable to lookup user by id: %s with name: %s, email: %s, error: user object not found", + log.Warnf("AddCclaApprovalRequest - unable to lookup user by id: %s with name: %s, email: %s, error: user object not found", args.ContributorID, args.ContributorName, args.ContributorEmail) return "", errors.New("invalid user") } @@ -116,14 +116,14 @@ func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, pageSize := int64(5) sig, sigErr := s.signatureRepo.GetProjectCompanySignatures(ctx, companyID, claGroupID, &signed, &approved, nil, &sortOrder, &pageSize) if sigErr != nil || sig == nil || sig.Signatures == nil { - log.Warnf("AddCclaWhitelistRequest - unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", + log.Warnf("AddCclaApprovalRequest - unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", companyID, claGroupID, sig, err) return "", err } - requestID, addErr := s.repo.AddCclaWhitelistRequest(companyModel, claGroupModel, userModel, args.ContributorName, args.ContributorEmail) + requestID, addErr := s.repo.AddCclaApprovalRequest(companyModel, claGroupModel, userModel, args.ContributorName, args.ContributorEmail) if addErr != nil { - log.Warnf("AddCclaWhitelistRequest - unable to add Approval Request for id: %s with name: %s, email: %s, error: %+v", + log.Warnf("AddCclaApprovalRequest - unable to add Approval Request for id: %s with name: %s, email: %s, error: %+v", args.ContributorID, args.ContributorName, args.ContributorEmail, addErr) } @@ -137,38 +137,38 @@ func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user.CLAUser, companyID, claGroupID, requestID string) error { f := logrus.Fields{ - "functionName": "ApproveCclaWhitelistRequest", + "functionName": "ApproveCclaApprovalListRequest", "companyID": companyID, "claGroupID": claGroupID, "requestID": requestID, "Approver": claUser.Name, } - err := s.repo.ApproveCclaWhitelistRequest(requestID) + err := s.repo.ApproveCclaApprovalListRequest(requestID) if err != nil { - log.WithFields(f).Warnf("ApproveCclaWhitelistRequest - problem updating approved list with 'approved' status for request: %s, error: %+v", + log.WithFields(f).Warnf("ApproveCclaApprovalListRequest - problem updating approved list with 'approved' status for request: %s, error: %+v", requestID, err) return err } - requestModel, err := s.repo.GetCclaWhitelistRequest(requestID) + requestModel, err := s.repo.GetCclaApprovalListRequest(requestID) if err != nil { - log.Warnf("ApproveCclaWhitelistRequest - unable to lookup request by id: %s, error: %+v", requestID, err) + log.Warnf("ApproveCclaApprovalListRequest - unable to lookup request by id: %s, error: %+v", requestID, err) return err } companyModel, err := s.companyRepo.GetCompany(ctx, companyID) if err != nil { - log.Warnf("ApproveCclaWhitelistRequest - unable to lookup company by id: %s, error: %+v", companyID, err) + log.Warnf("ApproveCclaApprovalListRequest - unable to lookup company by id: %s, error: %+v", companyID, err) return err } _, err = s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { - log.Warnf("ApproveCclaWhitelistRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) + log.Warnf("ApproveCclaApprovalListRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) return err } if requestModel.UserEmails == nil { - msg := fmt.Sprintf("ApproveCclaWhitelistRequest - unable to send approval email - email missing for request: %+v, error: %+v", + msg := fmt.Sprintf("ApproveCclaApprovalListRequest - unable to send approval email - email missing for request: %+v, error: %+v", requestModel, err) log.Warnf(msg) return errors.New(msg) @@ -221,26 +221,26 @@ func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user. // RejectCclaWhitelistRequest is the handler for the decline CLA request func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claGroupID, requestID string) error { - err := s.repo.RejectCclaWhitelistRequest(requestID) + err := s.repo.RejectCclaApprovalListRequest(requestID) if err != nil { - log.Warnf("RejectCclaWhitelistRequest - problem updating approved list with 'rejected' status for request: %s, error: %+v", requestID, err) + log.Warnf("RejectCclaApprovalListRequest - problem updating approved list with 'rejected' status for request: %s, error: %+v", requestID, err) return err } - requestModel, err := s.repo.GetCclaWhitelistRequest(requestID) + requestModel, err := s.repo.GetCclaApprovalListRequest(requestID) if err != nil { - log.Warnf("RejectCclaWhitelistRequest - unable to lookup request by id: %s, error: %+v", requestID, err) + log.Warnf("RejectCclaApprovalListRequest - unable to lookup request by id: %s, error: %+v", requestID, err) return err } companyModel, err := s.companyRepo.GetCompany(ctx, companyID) if err != nil { - log.Warnf("RejectCclaWhitelistRequest - unable to lookup company by id: %s, error: %+v", companyID, err) + log.Warnf("RejectCclaApprovalListRequest - unable to lookup company by id: %s, error: %+v", companyID, err) return err } claGroupModel, err := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { - log.Warnf("RejectCclaWhitelistRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) + log.Warnf("RejectCclaApprovalListRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) return err } @@ -249,13 +249,13 @@ func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claG pageSize := int64(5) sig, sigErr := s.signatureRepo.GetProjectCompanySignatures(ctx, companyID, claGroupID, &signed, &approved, nil, &sortOrder, &pageSize) if sigErr != nil || sig == nil || sig.Signatures == nil { - log.Warnf("RejectCclaWhitelistRequest - unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", + log.Warnf("RejectCclaApprovalListRequest - unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", companyID, claGroupID, sig, err) return err } if requestModel.UserEmails == nil { - msg := fmt.Sprintf("RejectCclaWhitelistRequest - unable to send approval email - email missing for request: %+v, error: %+v", + msg := fmt.Sprintf("RejectCclaApprovalListRequest - unable to send approval email - email missing for request: %+v, error: %+v", requestModel, err) log.Warnf(msg) return errors.New(msg) @@ -273,12 +273,12 @@ func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claG // ListCclaWhitelistRequest is the handler for the list CLA request func (s service) ListCclaWhitelistRequest(companyID string, claGroupID, status *string) (*models.CclaWhitelistRequestList, error) { - return s.repo.ListCclaWhitelistRequest(companyID, claGroupID, status, nil) + return s.repo.ListCclaApprovalListRequests(companyID, claGroupID, status, nil) } // ListCclaWhitelistRequestByCompanyProjectUser is the handler for the list CLA request func (s service) ListCclaWhitelistRequestByCompanyProjectUser(companyID string, claGroupID, status, userID *string) (*models.CclaWhitelistRequestList, error) { - return s.repo.ListCclaWhitelistRequest(companyID, claGroupID, status, userID) + return s.repo.ListCclaApprovalListRequests(companyID, claGroupID, status, userID) } // sendRequestSentEmail sends emails to the CLA managers specified in the signature record From 851713a6d423963c6082bd0fd30d81a05e361749 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Mar 2021 17:51:42 -0800 Subject: [PATCH 0153/1276] Resolved Nil Pointer + v1 Corporate CLA Console Link Issue (#2771) - Added v1 corporate console link (with https), updated SSM config - Updated email template to not set the https (removes duplicate) - added cla group version to data model - updated unit tests - added nil/empty guard on empty projectSFID list Signed-off-by: David Deal --- cla-backend-go/approval_list/service.go | 2 +- cla-backend-go/cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 8 ++++---- cla-backend-go/config/config.go | 1 + cla-backend-go/config/ssm.go | 9 ++++----- cla-backend-go/emails/approval_list_templates.go | 16 +++++++++------- .../emails/approval_list_templates_test.go | 2 +- cla-backend-go/emails/params.go | 1 + cla-backend-go/emails/prefill.go | 2 ++ .../emails/v2_cla_manager_templates.go | 6 +++--- cla-backend-go/utils/email.go | 3 +-- 11 files changed, 28 insertions(+), 24 deletions(-) diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 56ba68188..67ea33315 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -404,7 +404,7 @@ func (s service) sendRequestApprovedEmailToRecipient(ctx context.Context, emailP f := logrus.Fields{ "functionName": "sendRequestApprovedEmailToRecipient", - utils.XREQUESTID: ctx.Value((utils.XREQUESTID)), + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": emailParams.CompanyName, "recipientName": emailParams.RecipientName, "recipientAddress": emailParams.RecipientAddress, diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index fbc6f20f7..d7a0624c8 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -121,7 +121,7 @@ func init() { }) usersService := users.NewService(usersRepo, eventsService) - companyService := company.NewService(companyRepo, configFile.CorporateConsoleURL, userRepo, usersService) + companyService := company.NewService(companyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) v2CompanyService := v2Company.NewService(companyService, signaturesRepo, projectRepo, usersRepo, companyRepo, projectClaGroupRepo, eventsService) organization_service.InitClient(configFile.APIGatewayURL, eventsService) acs_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 53946431c..04d488cbe 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -264,14 +264,14 @@ func server(localMode bool) http.Handler { healthService := health.New(Version, Commit, Branch, BuildDate) templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) v1ProjectService := project.NewService(projectRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) - emailTemplateService := emails.NewEmailTemplateService(projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleURL, configFile.CorporateConsoleV2URL) + emailTemplateService := emails.NewEmailTemplateService(projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) v2ProjectService := v2Project.NewService(v1ProjectService, projectRepo, projectClaGroupRepo) - v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleURL, userRepo, usersService) + v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, projectRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, projectRepo, projectClaGroupRepo, v1CompanyService) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo) - v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleURL) + v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) @@ -327,7 +327,7 @@ func server(localMode bool) http.Handler { v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, projectClaGroupRepo) - v2Company.Configure(v2API, v2CompanyService, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleURL) + v2Company.Configure(v2API, v2CompanyService, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleV1URL) cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService) v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, configFile.CorporateConsoleV2URL, projectClaGroupRepo, userRepo) sign.Configure(v2API, v2SignService) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 537f7ab3f..b0541c7e7 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -51,6 +51,7 @@ type Config struct { AllowedOrigins []string `json:"-"` CorporateConsoleURL string `json:"corporateConsoleURL"` + CorporateConsoleV1URL string `json:"corporateConsoleV1URL"` CorporateConsoleV2URL string `json:"corporateConsoleV2URL"` // SNSEventTopic the topic ARN for events diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index b6f0ba61c..ea3d85c22 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -70,6 +70,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-gh-test-repository-%s", stage), fmt.Sprintf("cla-gh-test-repository-id-%s", stage), fmt.Sprintf("cla-corporate-base-%s", stage), + fmt.Sprintf("cla-corporate-v1-base-%s", stage), fmt.Sprintf("cla-corporate-v2-base-%s", stage), fmt.Sprintf("cla-doc-raptor-api-key-%s", stage), fmt.Sprintf("cla-session-store-table-%s", stage), @@ -146,11 +147,9 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.GitHub.TestRepositoryID = resp.value case fmt.Sprintf("cla-corporate-base-%s", stage): - corporateConsoleURLValue := resp.value - if corporateConsoleURLValue == "corporate.prod.lfcla.com" { - corporateConsoleURLValue = "corporate.lfcla.com" - } - config.CorporateConsoleURL = corporateConsoleURLValue + config.CorporateConsoleURL = resp.value + case fmt.Sprintf("cla-corporate-v1-base-%s", stage): + config.CorporateConsoleV1URL = resp.value case fmt.Sprintf("cla-corporate-v2-base-%s", stage): config.CorporateConsoleV2URL = resp.value case fmt.Sprintf("cla-doc-raptor-api-key-%s", stage): diff --git a/cla-backend-go/emails/approval_list_templates.go b/cla-backend-go/emails/approval_list_templates.go index 344633917..d677e8cc3 100644 --- a/cla-backend-go/emails/approval_list_templates.go +++ b/cla-backend-go/emails/approval_list_templates.go @@ -4,6 +4,8 @@ package emails import ( + "errors" + "github.com/communitybridge/easycla/cla-backend-go/utils" ) @@ -69,6 +71,10 @@ const ( // RenderApprovalListTemplate renders RenderApprovalListTemplate func RenderApprovalListTemplate(svc EmailTemplateService, projectSFIDs []string, params ApprovalListApprovedTemplateParams) (string, error) { + if len(projectSFIDs) == 0 { + return "", errors.New("projectSFIDs list is empty") + } + // prefill the projects data claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(utils.V2, projectSFIDs[0]) if err != nil { @@ -76,8 +82,7 @@ func RenderApprovalListTemplate(svc EmailTemplateService, projectSFIDs []string, } params.CLAGroupTemplateParams = claGroupParams - return RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, - ApprovalListApprovedTemplate, params) + return RenderTemplate(utils.V2, ApprovalListApprovedTemplateName, ApprovalListApprovedTemplate, params) } // RequestToAuthorizeTemplateParams is email params for RequestToAuthorizeTemplate @@ -107,7 +112,7 @@ const (

    {{.OptionalMessage}}


    {{end}}

    If you want to add them to the Approved List, please -log into the EasyCLA Corporate +log into the EasyCLA Corporate Console, where you can approve this user's request by selecting the 'Manage Approved List' and adding the contributor's email, the contributor's entire email domain, their GitHub ID or the entire GitHub Organization for the repository. This will permit them to begin contributing to {{.Project.ExternalProjectName}} on behalf of {{.CompanyName}}.

    @@ -124,8 +129,5 @@ func RenderRequestToAuthorizeTemplate(svc EmailTemplateService, claGroupVersion // assign the prefilled struct params.CLAGroupTemplateParams = claGroupParams - return RenderTemplate(claGroupVersion, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, - params, - ) - + return RenderTemplate(claGroupVersion, RequestToAuthorizeTemplateName, RequestToAuthorizeTemplate, params) } diff --git a/cla-backend-go/emails/approval_list_templates_test.go b/cla-backend-go/emails/approval_list_templates_test.go index e00f215d3..2c35ad419 100644 --- a/cla-backend-go/emails/approval_list_templates_test.go +++ b/cla-backend-go/emails/approval_list_templates_test.go @@ -67,7 +67,7 @@ func TestRequestToAuthorizeTemplate(t *testing.T) { CLAGroupTemplateParams: CLAGroupTemplateParams{ Projects: []CLAProjectParams{{ExternalProjectName: "JohnsProjectExternal"}}, CLAGroupName: "JohnsProject", - CorporateConsole: "CorporateConsoleURLValue", + CorporateConsole: "https://CorporateConsoleURLValue", }, CLAManagers: []ClaManagerInfoParams{ {LfUsername: "LFUserName", Email: "LFEmail"}, diff --git a/cla-backend-go/emails/params.go b/cla-backend-go/emails/params.go index 510003874..2ceac7000 100644 --- a/cla-backend-go/emails/params.go +++ b/cla-backend-go/emails/params.go @@ -73,6 +73,7 @@ type CLAGroupTemplateParams struct { // multiple children ChildProjectCount int Projects []CLAProjectParams + Version string } // GetProjectNameOrFoundation returns if the foundationName is set it gets back diff --git a/cla-backend-go/emails/prefill.go b/cla-backend-go/emails/prefill.go index df2f26f22..c4a87ab9f 100644 --- a/cla-backend-go/emails/prefill.go +++ b/cla-backend-go/emails/prefill.go @@ -92,6 +92,7 @@ func (s *emailTemplateServiceProvider) getV2CLAGroupTemplateParamsFromProjectSFI params := &CLAGroupTemplateParams{} params.CLAGroupName = projectCLAGroup.ClaGroupName params.CorporateConsole = s.corporateConsoleV2 + params.Version = projectCLAGroup.Version projects, err := s.repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) if err != nil { @@ -132,6 +133,7 @@ func (s *emailTemplateServiceProvider) getV1CLAGroupTemplateParamsFromProjectSFI return CLAGroupTemplateParams{ CorporateConsole: s.corporateConsoleV1, CLAGroupName: claGroup.ProjectName, + Version: claGroup.Version, ChildProjectCount: 1, Projects: []CLAProjectParams{ { diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index 3dc314f75..db23d5730 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -32,7 +32,7 @@ const (

    This is a notification email from EasyCLA regarding the organization {{.CompanyName}}.

    The following contributor would like to submit a contribution to the projects(s): {{.GetProjectsOrProject}} and is requesting to be added to the approval list as a contributor for your organization:

    {{.UserDetails}}

    -

    Approval can be done at {{.CorporateConsole}}. Visit any of the project(s):{{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}} and add the contributor to the approved list.

    +

    Approval can be done at {{.CorporateConsole}}. Visit any of the project(s):{{range $index, $projectName := .Projects}}{{if $index}},{{end}}{{$projectName.GetProjectFullURL}}{{end}} and add the contributor to the approved list.

    Please notify the contributor once they are added to the approved list of contributors so that they can complete their contribution.

    ` ) @@ -64,7 +64,7 @@ const ( V2OrgAdminTemplate = `

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the CLA setup and signing process for the organization {{.CompanyName}}.

    -

    {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA in support of the following project(s):

    +

    {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA in support of the following project(s):

    • {{.Project.ExternalProjectName}}
    @@ -134,7 +134,7 @@ const ( V2CLAManagerDesigneeCorporateTemplate = `

    Hello {{.RecipientName}},

    This is a notification email from EasyCLA regarding the CLA setup and signing process for the organization {{.CompanyName}}.

    -

    {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for the organization {{.CompanyName}} in support of the following project(s):

    +

    {{.SenderName}} {{.SenderEmail}} has identified you as a potential candidate to setup the Corporate CLA for the organization {{.CompanyName}} in support of the following project(s):

    • {{.Project.ExternalProjectName}}
    diff --git a/cla-backend-go/utils/email.go b/cla-backend-go/utils/email.go index 476b3d25b..bf3758f34 100644 --- a/cla-backend-go/utils/email.go +++ b/cla-backend-go/utils/email.go @@ -5,7 +5,6 @@ package utils import ( "errors" - "fmt" "strings" "github.com/sirupsen/logrus" @@ -103,7 +102,7 @@ func GetCorporateURL(isV2Project bool) string { if isV2Project { return config.GetConfig().CorporateConsoleV2URL } - return fmt.Sprintf("https://%s", config.GetConfig().CorporateConsoleURL) + return config.GetConfig().CorporateConsoleV1URL } // GetEmailHelpContent returns the standard email help paragraph details. From c6a1779fc783454db0060f446149bc4762d44d5a Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Mar 2021 17:50:30 -0800 Subject: [PATCH 0154/1276] Resolved Nil Pointer - Added missing services in cmd/server.go and email templates - Updated logging Signed-off-by: David Deal --- cla-backend-go/approval_list/handlers.go | 29 ++++-- cla-backend-go/approval_list/service.go | 111 ++++++++++++++--------- cla-backend-go/auth/authorizer.go | 4 +- cla-backend-go/cmd/server.go | 18 ++-- cla-backend-go/emails/prefill.go | 3 +- 5 files changed, 104 insertions(+), 61 deletions(-) diff --git a/cla-backend-go/approval_list/handlers.go b/cla-backend-go/approval_list/handlers.go index 932cafbc8..c8df6a62d 100644 --- a/cla-backend-go/approval_list/handlers.go +++ b/cla-backend-go/approval_list/handlers.go @@ -27,7 +27,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore func(params company.AddCclaWhitelistRequestParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - requestID, err := service.AddCclaWhitelistRequest(ctx, params.CompanyID, params.ProjectID, params.Body) + requestID, err := service.AddCclaApprovalListRequest(ctx, params.CompanyID, params.ProjectID, params.Body) if err != nil { return company.NewAddCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } @@ -47,7 +47,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore func(params company.ApproveCclaWhitelistRequestParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - err := service.ApproveCclaWhitelistRequest(ctx, claUser, params.CompanyID, params.ProjectID, params.RequestID) + err := service.ApproveCclaApprovalListRequest(ctx, claUser, params.CompanyID, params.ProjectID, params.RequestID) if err != nil { return company.NewApproveCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } @@ -67,7 +67,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore func(params company.RejectCclaWhitelistRequestParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - err := service.RejectCclaWhitelistRequest(ctx, params.CompanyID, params.ProjectID, params.RequestID) + err := service.RejectCclaApprovalListRequest(ctx, params.CompanyID, params.ProjectID, params.RequestID) if err != nil { return company.NewRejectCclaWhitelistRequestBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } @@ -93,7 +93,7 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore } log.WithFields(f).Debugf("Invoking ListCclaApprovalListRequests with Company ID: %+v, Project ID: %+v, Status: %+v", params.CompanyID, params.ProjectID, params.Status) - result, err := service.ListCclaWhitelistRequest(params.CompanyID, params.ProjectID, params.Status) + result, err := service.ListCclaApprovalListRequest(params.CompanyID, params.ProjectID, params.Status) if err != nil { return company.NewListCclaWhitelistRequestsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } @@ -103,9 +103,22 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore api.CompanyListCclaWhitelistRequestsByCompanyAndProjectHandler = company.ListCclaWhitelistRequestsByCompanyAndProjectHandlerFunc( func(params company.ListCclaWhitelistRequestsByCompanyAndProjectParams, claUser *user.CLAUser) middleware.Responder { - log.Debugf("Invoking ListCclaWhitelistRequestByCompanyProjectUser with Company ID: %+v, Project ID: %+v, Status: %+v", + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "v1.approval_list.handlers.CompanyListCclaWhitelistRequestsByCompanyAndProjectHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyID": params.CompanyID, + "projectID": params.ProjectID, + "status": utils.StringValue(params.Status), + "claUserName": claUser.Name, + "claUserUserID": claUser.UserID, + "claUserLFEmail": claUser.LFEmail, + "claUserLFUsername": claUser.LFUsername, + } + log.WithFields(f).Debugf("Invoking ListCclaApprovalListRequestByCompanyProjectUser with Company ID: %+v, Project ID: %+v, Status: %+v", params.CompanyID, params.ProjectID, params.Status) - result, err := service.ListCclaWhitelistRequestByCompanyProjectUser(params.CompanyID, ¶ms.ProjectID, params.Status, nil) + result, err := service.ListCclaApprovalListRequestByCompanyProjectUser(params.CompanyID, ¶ms.ProjectID, params.Status, nil) if err != nil { return company.NewListCclaWhitelistRequestsByCompanyAndProjectBadRequest().WithPayload(errorResponse(err)) } @@ -115,9 +128,9 @@ func Configure(api *operations.ClaAPI, service IService, sessionStore *dynastore api.CompanyListCclaWhitelistRequestsByCompanyAndProjectAndUserHandler = company.ListCclaWhitelistRequestsByCompanyAndProjectAndUserHandlerFunc( func(params company.ListCclaWhitelistRequestsByCompanyAndProjectAndUserParams, claUser *user.CLAUser) middleware.Responder { - log.Debugf("Invoking ListCclaWhitelistRequestByCompanyProjectUser with Company ID: %+v, Project ID: %+v, Status: %+v, User: %+v", + log.Debugf("Invoking ListCclaApprovalListRequestByCompanyProjectUser with Company ID: %+v, Project ID: %+v, Status: %+v, User: %+v", params.CompanyID, params.ProjectID, params.Status, claUser.LFUsername) - result, err := service.ListCclaWhitelistRequestByCompanyProjectUser(params.CompanyID, ¶ms.ProjectID, params.Status, &claUser.LFUsername) + result, err := service.ListCclaApprovalListRequestByCompanyProjectUser(params.CompanyID, ¶ms.ProjectID, params.Status, &claUser.LFUsername) if err != nil { return company.NewListCclaWhitelistRequestsByCompanyAndProjectAndUserBadRequest().WithPayload(errorResponse(err)) } diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 67ea33315..4bf7077c1 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -40,11 +40,11 @@ const ( // IService interface defines the service methods/functions type IService interface { - AddCclaWhitelistRequest(ctx context.Context, companyID string, claGroupID string, args models.CclaWhitelistRequestInput) (string, error) - ApproveCclaWhitelistRequest(ctx context.Context, claUser *user.CLAUser, ClacompanyID, claGroupID, requestID string) error - RejectCclaWhitelistRequest(ctx context.Context, companyID, claGroupID, requestID string) error - ListCclaWhitelistRequest(companyID string, claGroupID, status *string) (*models.CclaWhitelistRequestList, error) - ListCclaWhitelistRequestByCompanyProjectUser(companyID string, claGroupID, status, userID *string) (*models.CclaWhitelistRequestList, error) + AddCclaApprovalListRequest(ctx context.Context, companyID string, claGroupID string, args models.CclaWhitelistRequestInput) (string, error) + ApproveCclaApprovalListRequest(ctx context.Context, claUser *user.CLAUser, ClacompanyID, claGroupID, requestID string) error + RejectCclaApprovalListRequest(ctx context.Context, companyID, claGroupID, requestID string) error + ListCclaApprovalListRequest(companyID string, claGroupID, status *string) (*models.CclaWhitelistRequestList, error) + ListCclaApprovalListRequestByCompanyProjectUser(companyID string, claGroupID, status, userID *string) (*models.CclaWhitelistRequestList, error) } type service struct { @@ -61,54 +61,75 @@ type service struct { } // NewService creates a new approval list service -func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups.Repository, projService project.Service, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, corpConsoleURL string, httpClient *http.Client) IService { +func NewService(repo IRepository, projectsCLAGroupRepository projects_cla_groups.Repository, projService project.Service, userRepo users.UserRepository, companyRepo company.IRepository, projectRepo project.ProjectRepository, signatureRepo signatures.SignatureRepository, emailTemplateService emails.EmailTemplateService, corpConsoleURL string, httpClient *http.Client) IService { return service{ repo: repo, - projectsCLAGroupRepository: projectsCLAGroupRepository, projectService: projService, userRepo: userRepo, companyRepo: companyRepo, projectRepo: projectRepo, signatureRepo: signatureRepo, + projectsCLAGroupRepository: projectsCLAGroupRepository, + emailTemplateService: emailTemplateService, corpConsoleURL: corpConsoleURL, httpClient: httpClient, } } -func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, claGroupID string, args models.CclaWhitelistRequestInput) (string, error) { - list, err := s.ListCclaWhitelistRequestByCompanyProjectUser(companyID, &claGroupID, nil, &args.ContributorID) +func (s service) AddCclaApprovalListRequest(ctx context.Context, companyID string, claGroupID string, args models.CclaWhitelistRequestInput) (string, error) { + f := logrus.Fields{ + "functionName": "v1.approval_list.service.AddCclaApprovalListRequest", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyID": companyID, + "claGroupID": claGroupID, + "RecipientName": args.RecipientName, + "RecipientEmail": args.RecipientEmail, + "ContributorID": args.ContributorID, + "ContributorName": args.ContributorName, + "ContributorEmail": args.ContributorEmail, + "Message": args.Message, + } + + list, err := s.ListCclaApprovalListRequestByCompanyProjectUser(companyID, &claGroupID, nil, &args.ContributorID) if err != nil { - log.Warnf("AddCclaApprovalRequest - error looking up existing contributor invite requests for company: %s, project: %s, user by id: %s with name: %s, email: %s, error: %+v", + log.WithFields(f).WithError(err).Warnf("error looking up existing contributor invite requests for company: %s, project: %s, user by id: %s with name: %s, email: %s, error: %+v", companyID, claGroupID, args.ContributorID, args.ContributorName, args.ContributorEmail, err) return "", err } for _, item := range list.List { if item.RequestStatus == "pending" || item.RequestStatus == "approved" { - log.Warnf("AddCclaApprovalRequest - found existing contributor invite - id: %s, request for company: %s, project: %s, user by id: %s with name: %s, email: %s", + log.WithFields(f).Warnf("found existing contributor invite - id: %s, request for company: %s, project: %s, user by id: %s with name: %s, email: %s", list.List[0].RequestID, companyID, claGroupID, args.ContributorID, args.ContributorName, args.ContributorEmail) return "", ErrCclaApprovalRequestAlreadyExists } } companyModel, err := s.companyRepo.GetCompany(ctx, companyID) if err != nil { - log.Warnf("AddCclaApprovalRequest - unable to lookup company by id: %s, error: %+v", companyID, err) + log.WithFields(f).Warnf("unable to lookup company by id: %s, error: %+v", companyID, err) return "", err } claGroupModel, err := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { - log.Warnf("AddCclaApprovalRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) + log.WithFields(f).Warnf("unable to lookup project by id: %s, error: %+v", claGroupID, err) return "", err } + + log.WithFields(f).Debugf("looking up user by user ID: %s", args.ContributorID) userModel, err := s.userRepo.GetUser(args.ContributorID) - if err != nil { - log.Warnf("AddCclaApprovalRequest - unable to lookup user by id: %s with name: %s, email: %s, error: %+v", + if err != nil || userModel == nil { + log.WithFields(f).WithError(err).Warnf("unable to lookup user by id: %s with name: %s, email: %s, error: %+v", args.ContributorID, args.ContributorName, args.ContributorEmail, err) - return "", err - } - if userModel == nil { - log.Warnf("AddCclaApprovalRequest - unable to lookup user by id: %s with name: %s, email: %s, error: user object not found", - args.ContributorID, args.ContributorName, args.ContributorEmail) - return "", errors.New("invalid user") + + log.WithFields(f).Debugf("looking up user by user email: %s", args.ContributorEmail) + userModel, err = s.userRepo.GetUserByEmail(args.ContributorEmail) + if err != nil || userModel == nil { + log.WithFields(f).WithError(err).Warnf("unable to lookup user by email: %s with name: %s, error: %+v", + args.ContributorName, args.ContributorEmail, err) + if err != nil { + return "", err + } + return "", errors.New("invalid user") + } } signed, approved := true, true @@ -116,14 +137,14 @@ func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, pageSize := int64(5) sig, sigErr := s.signatureRepo.GetProjectCompanySignatures(ctx, companyID, claGroupID, &signed, &approved, nil, &sortOrder, &pageSize) if sigErr != nil || sig == nil || sig.Signatures == nil { - log.Warnf("AddCclaApprovalRequest - unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", + log.WithFields(f).Warnf("unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", companyID, claGroupID, sig, err) return "", err } requestID, addErr := s.repo.AddCclaApprovalRequest(companyModel, claGroupModel, userModel, args.ContributorName, args.ContributorEmail) if addErr != nil { - log.Warnf("AddCclaApprovalRequest - unable to add Approval Request for id: %s with name: %s, email: %s, error: %+v", + log.WithFields(f).Warnf("unable to add Approval Request for id: %s with name: %s, email: %s, error: %+v", args.ContributorID, args.ContributorName, args.ContributorEmail, addErr) } @@ -133,16 +154,16 @@ func (s service) AddCclaWhitelistRequest(ctx context.Context, companyID string, return requestID, nil } -// ApproveCclaWhitelistRequest is the handler for the approve CLA request -func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user.CLAUser, companyID, claGroupID, requestID string) error { - +// ApproveCclaApprovalListRequest is the handler for the approve CLA request +func (s service) ApproveCclaApprovalListRequest(ctx context.Context, claUser *user.CLAUser, companyID, claGroupID, requestID string) error { f := logrus.Fields{ - "functionName": "ApproveCclaApprovalListRequest", + "functionName": "v1.approval_list.service.ApproveCclaApprovalListRequest", "companyID": companyID, "claGroupID": claGroupID, "requestID": requestID, "Approver": claUser.Name, } + err := s.repo.ApproveCclaApprovalListRequest(requestID) if err != nil { log.WithFields(f).Warnf("ApproveCclaApprovalListRequest - problem updating approved list with 'approved' status for request: %s, error: %+v", @@ -219,28 +240,35 @@ func (s service) ApproveCclaWhitelistRequest(ctx context.Context, claUser *user. return nil } -// RejectCclaWhitelistRequest is the handler for the decline CLA request -func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claGroupID, requestID string) error { +// RejectCclaApprovalListRequest is the handler for the decline CLA request +func (s service) RejectCclaApprovalListRequest(ctx context.Context, companyID, claGroupID, requestID string) error { + f := logrus.Fields{ + "functionName": "v1.approval_list.service.RejectCclaApprovalListRequest", + "companyID": companyID, + "claGroupID": claGroupID, + "requestID": requestID, + } + err := s.repo.RejectCclaApprovalListRequest(requestID) if err != nil { - log.Warnf("RejectCclaApprovalListRequest - problem updating approved list with 'rejected' status for request: %s, error: %+v", requestID, err) + log.WithFields(f).WithError(err).Warnf("problem updating approved list with 'rejected' status for request: %s, error: %+v", requestID, err) return err } requestModel, err := s.repo.GetCclaApprovalListRequest(requestID) if err != nil { - log.Warnf("RejectCclaApprovalListRequest - unable to lookup request by id: %s, error: %+v", requestID, err) + log.WithFields(f).WithError(err).Warnf("unable to lookup request by id: %s, error: %+v", requestID, err) return err } companyModel, err := s.companyRepo.GetCompany(ctx, companyID) if err != nil { - log.Warnf("RejectCclaApprovalListRequest - unable to lookup company by id: %s, error: %+v", companyID, err) + log.WithFields(f).WithError(err).Warnf("unable to lookup company by id: %s, error: %+v", companyID, err) return err } claGroupModel, err := s.projectRepo.GetCLAGroupByID(ctx, claGroupID, DontLoadRepoDetails) if err != nil { - log.Warnf("RejectCclaApprovalListRequest - unable to lookup project by id: %s, error: %+v", claGroupID, err) + log.WithFields(f).WithError(err).Warnf("unable to lookup project by id: %s, error: %+v", claGroupID, err) return err } @@ -249,15 +277,15 @@ func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claG pageSize := int64(5) sig, sigErr := s.signatureRepo.GetProjectCompanySignatures(ctx, companyID, claGroupID, &signed, &approved, nil, &sortOrder, &pageSize) if sigErr != nil || sig == nil || sig.Signatures == nil { - log.Warnf("RejectCclaApprovalListRequest - unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", + log.WithFields(f).WithError(sigErr).Warnf("unable to lookup signature by company id: %s project id: %s - (or no managers), sig: %+v, error: %+v", companyID, claGroupID, sig, err) return err } if requestModel.UserEmails == nil { - msg := fmt.Sprintf("RejectCclaApprovalListRequest - unable to send approval email - email missing for request: %+v, error: %+v", + msg := fmt.Sprintf("unable to send approval email - email missing for request: %+v, error: %+v", requestModel, err) - log.Warnf(msg) + log.WithFields(f).Warnf(msg) return errors.New(msg) } @@ -271,13 +299,13 @@ func (s service) RejectCclaWhitelistRequest(ctx context.Context, companyID, claG return nil } -// ListCclaWhitelistRequest is the handler for the list CLA request -func (s service) ListCclaWhitelistRequest(companyID string, claGroupID, status *string) (*models.CclaWhitelistRequestList, error) { +// ListCclaApprovalListRequest is the handler for the list CLA request +func (s service) ListCclaApprovalListRequest(companyID string, claGroupID, status *string) (*models.CclaWhitelistRequestList, error) { return s.repo.ListCclaApprovalListRequests(companyID, claGroupID, status, nil) } -// ListCclaWhitelistRequestByCompanyProjectUser is the handler for the list CLA request -func (s service) ListCclaWhitelistRequestByCompanyProjectUser(companyID string, claGroupID, status, userID *string) (*models.CclaWhitelistRequestList, error) { +// ListCclaApprovalListRequestByCompanyProjectUser is the handler for the list CLA request +func (s service) ListCclaApprovalListRequestByCompanyProjectUser(companyID string, claGroupID, status, userID *string) (*models.CclaWhitelistRequestList, error) { return s.repo.ListCclaApprovalListRequests(companyID, claGroupID, status, userID) } @@ -401,9 +429,8 @@ func (s service) sendRequestRejectedEmailToRecipient(emailParams emails.CommonEm } func (s service) sendRequestApprovedEmailToRecipient(ctx context.Context, emailParams emails.CommonEmailParams, claUser user.CLAUser, projectSFIDs []string) { - f := logrus.Fields{ - "functionName": "sendRequestApprovedEmailToRecipient", + "functionName": "v1.approval_list.service.sendRequestApprovedEmailToRecipient", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": emailParams.CompanyName, "recipientName": emailParams.RecipientName, diff --git a/cla-backend-go/auth/authorizer.go b/cla-backend-go/auth/authorizer.go index c973ed894..49569d2b3 100644 --- a/cla-backend-go/auth/authorizer.go +++ b/cla-backend-go/auth/authorizer.go @@ -5,6 +5,7 @@ package auth import ( "errors" + "fmt" "strings" "github.com/sirupsen/logrus" @@ -50,7 +51,7 @@ func NewAuthorizer(authValidator Validator, userPermissioner UserPermissioner) A // SecurityAuth creates a new CLA user based on the token and scopes func (a Authorizer) SecurityAuth(token string, scopes []string) (*user.CLAUser, error) { f := logrus.Fields{ - "functionName": "auth.SecurityAuth", + "functionName": "auth.authorizer.SecurityAuth", "scopes": strings.Join(scopes, ","), } // This handler is called by the runtime whenever a route needs authentication @@ -68,6 +69,7 @@ func (a Authorizer) SecurityAuth(token string, scopes []string) (*user.CLAUser, } return nil, err } + f["claims"] = fmt.Sprintf("%+v", claims) // Get the username from the token claims usernameClaim, ok := claims[a.authValidator.usernameClaim] diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 04d488cbe..a56c20175 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -238,7 +238,7 @@ func server(localMode bool) http.Handler { v1CompanyRepo := v1Company.NewRepository(awsSession, stage) signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo) projectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) - projectRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, projectClaGroupRepo) + v1CLAGroupRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, projectClaGroupRepo) eventsRepo := events.NewRepository(awsSession, stage) metricsRepo := metrics.NewRepository(awsSession, stage, configFile.APIGatewayURL, projectClaGroupRepo) githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) @@ -248,7 +248,7 @@ func server(localMode bool) http.Handler { eventsService := events.NewService(eventsRepo, combinedRepo{ usersRepo, v1CompanyRepo, - projectRepo, + v1CLAGroupRepo, projectClaGroupRepo, }) @@ -263,19 +263,19 @@ func server(localMode bool) http.Handler { usersService := users.NewService(usersRepo, eventsService) healthService := health.New(Version, Commit, Branch, BuildDate) templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) - v1ProjectService := project.NewService(projectRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) - emailTemplateService := emails.NewEmailTemplateService(projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) - v2ProjectService := v2Project.NewService(v1ProjectService, projectRepo, projectClaGroupRepo) + v1ProjectService := project.NewService(v1CLAGroupRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) + emailTemplateService := emails.NewEmailTemplateService(v1CLAGroupRepo, projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) + v2ProjectService := v2Project.NewService(v1ProjectService, v1CLAGroupRepo, projectClaGroupRepo) v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) - v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, projectRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) - v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, projectRepo, projectClaGroupRepo, v1CompanyService) + v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, v1CLAGroupRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) + v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, projectClaGroupRepo, v1CompanyService) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo) v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) - v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, projectRepo, signaturesRepo, configFile.CorporateConsoleV2URL, http.DefaultClient) + v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, v1CLAGroupRepo, signaturesRepo, emailTemplateService, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) @@ -311,7 +311,7 @@ func server(localMode bool) http.Handler { v2Template.Configure(v2API, templateService, eventsService) github.Configure(api, configFile.GitHub.ClientID, configFile.GitHub.ClientSecret, configFile.GitHub.AccessToken, sessionStore) signatures.Configure(api, v1SignaturesService, sessionStore, eventsService) - v2Signatures.Configure(v2API, v1ProjectService, projectRepo, v1CompanyService, v1SignaturesService, sessionStore, eventsService, v2SignatureService, projectClaGroupRepo) + v2Signatures.Configure(v2API, v1ProjectService, v1CLAGroupRepo, v1CompanyService, v1SignaturesService, sessionStore, eventsService, v2SignatureService, projectClaGroupRepo) approval_list.Configure(api, v1ApprovalListService, sessionStore, v1SignaturesService, eventsService) v1Company.Configure(api, v1CompanyService, usersService, companyUserValidation, eventsService) docs.Configure(api) diff --git a/cla-backend-go/emails/prefill.go b/cla-backend-go/emails/prefill.go index c4a87ab9f..3dc3fba90 100644 --- a/cla-backend-go/emails/prefill.go +++ b/cla-backend-go/emails/prefill.go @@ -29,8 +29,9 @@ type emailTemplateServiceProvider struct { } // NewEmailTemplateService creates a new instance of email template service -func NewEmailTemplateService(repository projects_cla_groups.Repository, projectService project.Service, corporateConsoleV1, corporateConsoleV2 string) EmailTemplateService { +func NewEmailTemplateService(claGroupRepository project.ProjectRepository, repository projects_cla_groups.Repository, projectService project.Service, corporateConsoleV1, corporateConsoleV2 string) EmailTemplateService { return &emailTemplateServiceProvider{ + claGroupRepository: claGroupRepository, repository: repository, projectService: projectService, corporateConsoleV1: corporateConsoleV1, From 4802de32ec18c426333f059450ae3b3362c6c0a4 Mon Sep 17 00:00:00 2001 From: makkalot Date: Thu, 11 Mar 2021 11:14:06 +0200 Subject: [PATCH 0155/1276] don't fail if foundation sfid is not set properly Signed-off-by: makkalot --- cla-backend-go/emails/prefill.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/emails/prefill.go b/cla-backend-go/emails/prefill.go index 3dc3fba90..790a1ef14 100644 --- a/cla-backend-go/emails/prefill.go +++ b/cla-backend-go/emails/prefill.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" @@ -126,9 +128,12 @@ func (s *emailTemplateServiceProvider) getV1CLAGroupTemplateParamsFromProjectSFI return CLAGroupTemplateParams{}, fmt.Errorf("project service lookup error for SFID: %s, error : %+v", projectSFID, projectErr) } - signedResult, err := s.projectService.SignedAtFoundationLevel(context.Background(), claGroup.FoundationSFID) - if err != nil { - return CLAGroupTemplateParams{}, fmt.Errorf("fetching the SignedAtFoundationLevel for foundation : %s failed : %v", claGroup.FoundationSFID, err) + var signedResult bool + if claGroup.FoundationSFID != "" { + signedResult, err = s.projectService.SignedAtFoundationLevel(context.Background(), claGroup.FoundationSFID) + if err != nil { + log.Warnf("fetching the SignedAtFoundationLevel for foundation : %s failed : %v skipping assigning in email params", claGroup.FoundationSFID, err) + } } return CLAGroupTemplateParams{ From d99f9ef907b59421b349672454862d764171aaf3 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Thu, 11 Mar 2021 17:33:36 +0300 Subject: [PATCH 0156/1276] Bug/Corporate Console Events - Updated log events with right parent project details(standalone, multi level types) - Updated query using parent_project index in the search query Signed-off-by: wanyaland --- cla-backend-go/events/repository.go | 18 +++++++++--------- cla-backend-go/events/service.go | 14 +++++++++++--- cla-backend-go/v2/events/handlers.go | 7 ++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index f6a95c284..62e531950 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -359,12 +359,15 @@ func (repo *repository) queryEventsTable(indexName string, condition expression. ExpressionAttributeValues: expr.Values(), KeyConditionExpression: expr.KeyCondition(), ProjectionExpression: expr.Projection(), - FilterExpression: expr.Filter(), TableName: aws.String(tableName), IndexName: aws.String(indexName), ScanIndexForward: aws.Bool(false), } + if filter != nil { + queryInput.FilterExpression = expr.Filter() + } + if all { pageSize = aws.Int64(HugePageSize) } else { @@ -497,15 +500,12 @@ func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, found "paramPageSize": utils.Int64Value(paramPageSize), "loadAll": all, } - key := fmt.Sprintf("%s#%s", companySFID, foundationSFID) - log.WithFields(f).Debugf("adding key condition of 'company_sfid_foundation_sfid = %s'", key) - keyCondition := expression.Key("company_sfid_foundation_sfid").Equal(expression.Value(key)) + log.WithFields(f).Debugf("adding key condition of 'event_parent_project_sfid = %s'", foundationSFID) + keyCondition := expression.Key("event_parent_project_sfid").Equal(expression.Value(foundationSFID)) var filter expression.ConditionBuilder - if companyID != "" { - log.WithFields(f).Debugf("adding filter condition of 'event_company_id = %s'", companyID) - filter = expression.Name("event_company_id").Equal(expression.Value(companyID)) - } - return repo.queryEventsTable(CompanySFIDFoundationSFIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) + log.WithFields(f).Debugf("adding filter condition of 'event_company_sfid = %s ", companySFID) + filter = expression.Name("event_company_sfid").Equal(expression.Value(companySFID)) + return repo.queryEventsTable(EventFoundationSFIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } // GetCompanyClaGroupEvents returns the list of events for cla group and the company diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index dbef1e7ff..c2a9bb26a 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -242,10 +242,18 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", project.Parent) return nil } + var parentProjectName, parentProjectID string + if utils.IsProjectCategory(project, parentProject) { + parentProjectName = parentProject.Name + parentProjectID = parentProject.ID + } else { + parentProjectName = project.Name + parentProjectID = project.ID + } log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s - resulting in ID: %s with name: %s", - project.Parent, parentProject.ID, parentProject.Name) - args.ParentProjectSFID = parentProject.ID - args.ParentProjectName = parentProject.Name + project.Parent, parentProjectID, parentProjectName) + args.ParentProjectSFID = parentProjectID + args.ParentProjectName = parentProjectName } } diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index 0803f5934..224b5c3f3 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -307,11 +307,12 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe log.WithFields(f).Warnf("problem loading project by SFID: %s", params.ProjectSFID) return events.NewGetCompanyProjectEventsBadRequest().WithPayload(errorResponse(reqID, err)) } - var result *v1Models.EventList - if projectDetails.ProjectType == utils.ProjectTypeProjectGroup { - result, err = service.GetCompanyFoundationEvents(v1Company.CompanyExternalID, params.CompanyID, params.ProjectSFID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) + if utils.IsProjectHasRootParent(projectDetails) { + log.WithFields(f).Debugf("loading foundation level events for projectSFID: %s...", params.ProjectSFID) + result, err = service.GetCompanyFoundationEvents(v1Company.CompanyExternalID, "", params.ProjectSFID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) } else { + log.WithFields(f).Debugf("loading project level events for projectSFID :%s...", params.ProjectSFID) pm, perr := projectsClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) if perr != nil { if perr == projects_cla_groups.ErrProjectNotAssociatedWithClaGroup { From 9e102bfad1fd455526ed8660feb0aa2c20b22482 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Mar 2021 21:52:19 +0300 Subject: [PATCH 0157/1276] Feature/Get GH orgs response (#2776) - Updated GET endpoint for gh orgs with response installationURL key Signed-off-by: wanyaland --- cla-backend-go/swagger/cla.v2.yaml | 5 +++++ cla-backend-go/v2/github_organizations/service.go | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index b3735a92e..c45018913 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -5039,6 +5039,11 @@ definitions: type: boolean description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. x-omitempty: false + installationURL: + type: string + x-nullable: true + example: "https://github.com/organizations/deal-test-org-2/settings/installations/1235464" + format: uri github_organization_name: type: string description: The GitHub Organization name diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 46dd3e082..aa80c5b39 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -6,12 +6,14 @@ package github_organizations import ( "context" "fmt" + "net/url" "sort" "strconv" "strings" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/go-openapi/strfmt" "github.com/sirupsen/logrus" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -137,6 +139,13 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } } + installURL := url.URL{ + Scheme: "https", + Host: "github.com", + Path: fmt.Sprintf("/%s/settings/installations/%d", org.OrganizationName, org.OrganizationInstallationID), + } + installationURL := strfmt.URI(installURL.String()) + rorg := &models.ProjectGithubOrganization{ AutoEnabled: org.AutoEnabled, AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, @@ -145,6 +154,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) ConnectionStatus: "", // updated below GithubOrganizationName: org.OrganizationName, Repositories: make([]*models.ProjectGithubRepository, 0), + InstallationURL: &installationURL, } orgmap[org.OrganizationName] = rorg From c917b4256a2a23a76d129f4a3cf526f9854b3a2c Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Mar 2021 23:57:25 +0300 Subject: [PATCH 0158/1276] Bug/Project Service (#2777) - Resolved project service query by updating base path for client stub calls Signed-off-by: wanyaland --- cla-backend-go/v2/project-service/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 00831915d..724c47e29 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -44,7 +44,7 @@ func InitClient(APIGwURL string) { projectServiceClient = &Client{ cl: client.NewHTTPClientWithConfig(strfmt.Default, &client.TransportConfig{ Host: APIGwURL, - BasePath: "project-service/v1", + BasePath: "project-service", Schemes: []string{"https"}, }), } From 72555656d41d5155cc2488498bc0a34db7325e42 Mon Sep 17 00:00:00 2001 From: wanyaland Date: Fri, 12 Mar 2021 13:44:24 +0300 Subject: [PATCH 0159/1276] Bug/Installation URL - Updated installation url path with 'organizations' Signed-off-by: wanyaland --- cla-backend-go/github_organizations/helpers.go | 2 +- cla-backend-go/v2/github_organizations/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/github_organizations/helpers.go b/cla-backend-go/github_organizations/helpers.go index f256e38d6..a42e43a3e 100644 --- a/cla-backend-go/github_organizations/helpers.go +++ b/cla-backend-go/github_organizations/helpers.go @@ -43,7 +43,7 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations installURL := netURL.URL{ Scheme: "https", Host: "github.com", - Path: fmt.Sprintf("/%s/settings/installations/%d", ghorg.OrganizationName, ghorg.OrganizationInstallationID), + Path: fmt.Sprintf("/organizations/%s/settings/installations/%d", ghorg.OrganizationName, ghorg.OrganizationInstallationID), } installationURL := strfmt.URI(installURL.String()) ghorg.GithubInfo.Details = &models.GithubOrganizationGithubInfoDetails{ diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index aa80c5b39..c45350976 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -142,7 +142,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) installURL := url.URL{ Scheme: "https", Host: "github.com", - Path: fmt.Sprintf("/%s/settings/installations/%d", org.OrganizationName, org.OrganizationInstallationID), + Path: fmt.Sprintf("/organizations/%s/settings/installations/%d", org.OrganizationName, org.OrganizationInstallationID), } installationURL := strfmt.URI(installURL.String()) From 33c65e2f7626f5c39f2ca103d861851fde5e4d4b Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 12 Mar 2021 20:45:53 +0300 Subject: [PATCH 0160/1276] Bug/Cla Group Events (#2779) - Resolved query for cla-group-events with using event_cla_group_id as index Signed-off-by: wanyaland --- cla-backend-go/events/repository.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 62e531950..544d3f14b 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -503,7 +503,7 @@ func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, found log.WithFields(f).Debugf("adding key condition of 'event_parent_project_sfid = %s'", foundationSFID) keyCondition := expression.Key("event_parent_project_sfid").Equal(expression.Value(foundationSFID)) var filter expression.ConditionBuilder - log.WithFields(f).Debugf("adding filter condition of 'event_company_sfid = %s ", companySFID) + log.WithFields(f).Debugf("adding filter condition of 'event_company_sfid = %s'", companySFID) filter = expression.Name("event_company_sfid").Equal(expression.Value(companySFID)) return repo.queryEventsTable(EventFoundationSFIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } @@ -519,15 +519,12 @@ func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGrou "paramPageSize": utils.Int64Value(paramPageSize), "loadAll": all, } - key := fmt.Sprintf("%s#%s", companySFID, claGroupID) - log.WithFields(f).Debugf("adding key condition of 'company_sfid_project_id = %s'", key) - keyCondition := expression.Key("company_sfid_project_id").Equal(expression.Value(key)) + log.WithFields(f).Debugf("adding key condition of 'event_cla_group_id = %s'", claGroupID) + keyCondition := expression.Key("event_cla_group_id").Equal(expression.Value(claGroupID)) var filter expression.ConditionBuilder - if companyID != "" { - log.WithFields(f).Debugf("adding filter condition of 'event_company_id = %s'", companyID) - filter = expression.Name("event_company_id").Equal(expression.Value(companyID)) - } - return repo.queryEventsTable(CompanySFIDProjectIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) + log.WithFields(f).Debugf("adding filter condition of 'event_company_sfid = %s'", companySFID) + filter = expression.Name("event_company_sfid").Equal(expression.Value(companySFID)) + return repo.queryEventsTable(EventCLAGroupIDEpochIndex, keyCondition, &filter, nextKey, paramPageSize, all, nil) } // GetCompanyEvents returns the list of events for given company id and event types From bb0b5e9db4aebf91892aa3e39809d87218035fa1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 12 Mar 2021 13:45:37 -0800 Subject: [PATCH 0161/1276] Updated Get Company Projects Error Handling (#2780) - Updated error handling - Sped up get signature details/metrics routines - decreased PROD AWSF API latency from 11s to 5s Signed-off-by: David Deal --- cla-backend-go/project/repository.go | 2 +- .../projects_cla_groups/repository.go | 1 - cla-backend-go/signatures/converters.go | 31 +-- cla-backend-go/signatures/repository.go | 3 - cla-backend-go/v2/cla_groups/service.go | 48 +++-- cla-backend-go/v2/company/handlers.go | 54 ++++-- cla-backend-go/v2/company/service.go | 176 ++++++++++++------ 7 files changed, 211 insertions(+), 104 deletions(-) diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 2dfa9863f..7f83486d5 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -354,7 +354,7 @@ func (repo *repo) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSF } } - log.WithFields(f).Debugf("foundation projects!: %#v ", projects) + // log.WithFields(f).Debugf("foundation projects!: %#v ", projects) return &models.ClaGroups{ ResultCount: int64(len(projects)), diff --git a/cla-backend-go/projects_cla_groups/repository.go b/cla-backend-go/projects_cla_groups/repository.go index 0126440c5..be33acecf 100644 --- a/cla-backend-go/projects_cla_groups/repository.go +++ b/cla-backend-go/projects_cla_groups/repository.go @@ -79,7 +79,6 @@ func (repo *repo) queryClaGroupsProjects(keyCondition expression.KeyConditionBui "keyCondition": fmt.Sprintf("%+v", keyCondition), } - log.WithFields(f).Debug("building query...") expr, err := expression.NewBuilder().WithKeyCondition(keyCondition).Build() if err != nil { log.WithFields(f).Warnf("error building expression for project cla groups, error: %v", err) diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go index a347d2a50..3639a6b13 100644 --- a/cla-backend-go/signatures/converters.go +++ b/cla-backend-go/signatures/converters.go @@ -18,11 +18,11 @@ import ( ) // buildProjectSignatureModels converts the response model into a response data model -func (repo repository) buildProjectSignatureModels(ctx context.Context, results *dynamodb.QueryOutput, projectID string, loadACLDetails bool) ([]*models.Signature, error) { +func (repo repository) buildProjectSignatureModels(ctx context.Context, results *dynamodb.QueryOutput, claGroupID string, loadACLDetails bool) ([]*models.Signature, error) { f := logrus.Fields{ - "functionName": "signatures.converters.buildProjectSignatureModels", + "functionName": "v1.signatures.converters.buildProjectSignatureModels", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectID": projectID, + "claGroupID": claGroupID, } var sigs []*models.Signature @@ -31,8 +31,8 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) if err != nil { - log.WithFields(f).Warnf("error unmarshalling signatures from database for project: %s, error: %v", - projectID, err) + log.WithFields(f).Warnf("error unmarshalling signatures from database for cla group ID: %s, error: %v", + claGroupID, err) return nil, err } @@ -103,7 +103,8 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results if sigModel.SignatureReferenceType == utils.SignatureReferenceTypeUser { userModel, userErr := repo.usersRepo.GetUser(sigModel.SignatureReferenceID) if userErr != nil || userModel == nil { - log.WithFields(f).Warnf("unable to lookup user using id: %s, error: %v", sigModel.SignatureReferenceID, userErr) + log.WithFields(f).WithError(userErr).Warnf("unable to lookup user for signature: %s with reference type: %s using signature reference id: %s", + sigModel.SignatureID, sigModel.SignatureReferenceType, sigModel.SignatureReferenceID) } else { userName = userModel.Username userLFID = userModel.LfUsername @@ -114,7 +115,8 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results if signatureUserCompanyID != "" { dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, signatureUserCompanyID) if companyErr != nil { - log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", signatureUserCompanyID, companyErr) + log.WithFields(f).WithError(companyErr).Warnf("unable to lookup company record for signature: %s with reference type: %s using signature user company id: %s", + sigModel.SignatureID, sigModel.SignatureReferenceType, signatureUserCompanyID) } else { companyName = dbCompanyModel.CompanyName companySigningEntityName = dbCompanyModel.SigningEntityName @@ -123,7 +125,8 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results } else if sigModel.SignatureReferenceType == utils.SignatureReferenceTypeCompany { dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, sigModel.SignatureReferenceID) if companyErr != nil { - log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", sigModel.SignatureReferenceID, companyErr) + log.WithFields(f).WithError(companyErr).Warnf("unable to lookup company record for signature: %s with reference type: %s using signature reference id: %s", + sigModel.SignatureID, sigModel.SignatureReferenceType, sigModel.SignatureReferenceID) } else { companyName = dbCompanyModel.CompanyName companySigningEntityName = dbCompanyModel.SigningEntityName @@ -138,10 +141,10 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results if loadACLDetails { userModel, userErr := repo.usersRepo.GetUserByUserName(userName, true) if userErr != nil { - log.WithFields(f).Warnf("unable to lookup user using username: %s, error: %v", userName, userErr) + log.WithFields(f).WithError(userErr).Warnf("unable to lookup user by userNmae: %s in ACL for signature: %s", userName, sigModel.SignatureID) } else { if userModel == nil { - log.WithFields(f).Warnf("User looking for username is null: %s for signature: %s", userName, sigModel.SignatureID) + log.WithFields(f).Warnf("unable to lookup user by userNmae: %s in ACL for signature: %s", userName, sigModel.SignatureID) } else { signatureACL = append(signatureACL, *userModel) } @@ -168,7 +171,7 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results // buildProjectSignatureSummaryModels converts the response model into a signature summary model func (repo repository) buildProjectSignatureSummaryModels(ctx context.Context, results *dynamodb.QueryOutput, projectID string) ([]*models.SignatureSummary, error) { f := logrus.Fields{ - "functionName": "signatures.converters.buildProjectSignatureSummaryModels", + "functionName": "v1.signatures.converters.buildProjectSignatureSummaryModels", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectID": projectID, } @@ -235,7 +238,8 @@ func (repo repository) buildProjectSignatureSummaryModels(ctx context.Context, r if signatureUserCompanyID != "" { dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, signatureUserCompanyID) if companyErr != nil { - log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", signatureUserCompanyID, companyErr) + log.WithFields(f).WithError(companyErr).Warnf("unable to lookup company record for signature: %s with reference type: %s using signature user company id: %s", + sigModel.SignatureID, sigModel.SignatureReferenceType, signatureUserCompanyID) } else { companyName = dbCompanyModel.CompanyName companySigningEntityName = dbCompanyModel.SigningEntityName @@ -244,7 +248,8 @@ func (repo repository) buildProjectSignatureSummaryModels(ctx context.Context, r } else if sigModel.SignatureReferenceType == "company" { dbCompanyModel, companyErr := repo.companyRepo.GetCompany(ctx, sigModel.SignatureReferenceID) if companyErr != nil { - log.WithFields(f).Warnf("unable to lookup company using id: %s, error: %v", sigModel.SignatureReferenceID, companyErr) + log.WithFields(f).WithError(companyErr).Warnf("unable to lookup company record for signature: %s with reference type: %s using signature reference id: %s", + sigModel.SignatureID, sigModel.SignatureReferenceType, sigModel.SignatureReferenceID) } else { companyName = dbCompanyModel.CompanyName companySigningEntityName = dbCompanyModel.SigningEntityName diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index db0ae7a95..6cda340ae 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -466,7 +466,6 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u // Loop until we have all the records for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call - //log.WithFields(f).Debugf("Running signature project query using queryInput: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) //log.WithFields(f).Debugf("Ran signature project query, results: %+v, error: %+v", results, errQuery) if errQuery != nil { @@ -807,7 +806,6 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // Loop until we have all the records for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call - log.WithFields(f).Debugf("Running signature project query using queryInput: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { log.WithFields(f).Warnf("error retrieving project signature ID for project: %s, error: %v", @@ -1026,7 +1024,6 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si // Loop until we have all the records for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call - log.WithFields(f).Debugf("Running signature project query using queryInput: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { log.WithFields(f).Warnf("error retrieving project signature ID for project: %s, error: %v", diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 885563452..cfd2b74e5 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -576,23 +576,41 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje } // One more pass to update the metrics - bulk lookup the metrics and update the response model - log.WithFields(f).Debugf("Loading metrics for %d CLA Groups - updating response", len(claGroupIDList.List())) - var iclaSignatureCount, cclaSignatureCount int64 - for _, responseEntry := range responseModel.List { - log.Debugf("cla group entry logs %s", responseEntry.ClaGroupID) - iclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeICLA), SignatureType: aws.String(utils.SignatureTypeCLA)}) - if err != nil { - log.Warnf("error while getting ICLA Signature using clagroupID %s Error: %v", responseEntry.ClaGroupID, err) - } - iclaSignatureCount = iclaSignatureDetails.ResultCount + log.WithFields(f).Debugf("Loading metrics for %d CLA Groups...", len(claGroupIDList.List())) + type MetricsResult struct { + index int + iclaSignatureCount int64 + cclaSignatureCount int64 + Error error + } + metricsResultChannel := make(chan *MetricsResult, len(responseModel.List)) + + for idx, responseEntry := range responseModel.List { + go func(index int, responseEntry *models.ClaGroupSummary) { + log.WithFields(f).Debugf("fetching project signature metrics for CLA Group (%d): %s - %s", index, responseEntry.ClaGroupID, responseEntry.ClaGroupName) + iclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeICLA), SignatureType: aws.String(utils.SignatureTypeCLA)}) + if err != nil { + log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + } - cclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeCCLA), SignatureType: aws.String(utils.SignatureTypeCCLA)}) - if err != nil { - log.Warnf("error while getting ICLA Signature using clagroupID %s Error: %v", responseEntry.ClaGroupID, err) - } - cclaSignatureCount = cclaSignatureDetails.ResultCount + cclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeCCLA), SignatureType: aws.String(utils.SignatureTypeCCLA)}) + if err != nil { + log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + } + + metricsResultChannel <- &MetricsResult{ + index: index, + iclaSignatureCount: iclaSignatureDetails.ResultCount, + cclaSignatureCount: cclaSignatureDetails.ResultCount, + Error: err, + } + }(idx, responseEntry) + } - responseEntry.TotalSignatures = cclaSignatureCount + iclaSignatureCount + log.WithFields(f).Debugf("Waiting for metrics responses for %d CLA Groups...", len(claGroupIDList.List())) + for range responseModel.List { + response := <-metricsResultChannel + responseModel.List[response.index].TotalSignatures = response.cclaSignatureCount + response.iclaSignatureCount } // Sort the response based on the Foundation and CLA group name diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index f0d7f3428..fc1704d87 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -35,7 +35,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyByInternalIDHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyByInternalIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": params.CompanyID, "authUserName": utils.StringValue(params.XUSERNAME), @@ -77,7 +77,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyByExternalIDHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyByExternalIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, "authUserName": utils.StringValue(params.XUSERNAME), @@ -123,7 +123,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyProjectClaManagersHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyProjectClaManagersHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, @@ -165,7 +165,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyCLAGroupManagersHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyCLAGroupManagersHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "companyID": params.CompanyID, @@ -192,7 +192,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyProjectActiveClaHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyProjectActiveClaHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, @@ -240,10 +240,11 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyProjectContributorsHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyProjectContributorsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, + "searchTerm": utils.StringValue(params.SearchTerm), "authUserName": utils.StringValue(params.XUSERNAME), "authUserEmail": utils.StringValue(params.XEMAIL), } @@ -259,6 +260,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } return company.NewGetCompanyProjectActiveClaBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } + log.WithFields(f).Debugf("looked company by internal ID") // PM - check if authorized by project scope - allow if PM has project ID scope that matches // Contact,Community Program Manager,CLA Manager,CLA Manager Designee,Company Admin - check if authorized by organization scope - allow if {Contact,Community Program Manager,CLA Manager,CLA Manager Designee,Company Admin} has organization ID scope that matches @@ -272,10 +274,26 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo utils.ErrorResponseForbidden(reqID, msg)) } + log.WithFields(f).Debugf("querying for employee contributors...") result, err := service.GetCompanyProjectContributors(ctx, params.ProjectSFID, params.CompanyID, utils.StringValue(params.SearchTerm)) if err != nil { - if _, ok := err.(*utils.CompanyNotFound); ok { - return company.NewGetCompanyProjectContributorsNotFound().WithXRequestID(reqID) + if companyErr, ok := err.(*utils.CompanyNotFound); ok { + msg := fmt.Sprintf("Company not found with ID: %s", companyErr.CompanyID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectContributorsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } + if claGroupErr, ok := err.(*utils.CLAGroupNotFound); ok { + msg := fmt.Sprintf("CLA Group not found with ID: %s", claGroupErr.CLAGroupID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectContributorsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } + if _, ok := err.(*utils.ProjectCLAGroupMappingNotFound); ok { + msg := fmt.Sprintf("CLA Group not found with project SFID: %s", params.ProjectSFID) + log.WithFields(f).Warn(msg) + return company.NewGetCompanyProjectContributorsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, msg, err)) } return company.NewGetCompanyProjectContributorsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } @@ -288,7 +306,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyProjectClaHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyProjectClaHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companySFID": params.CompanySFID, @@ -328,7 +346,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "company.handlers.CompanyCreateCompanyHandler", + "functionName": "v2.company.handlers.CompanyCreateCompanyHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "userID": params.UserID, "companyName": aws.StringValue(params.Input.CompanyName), @@ -368,7 +386,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyByNameHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyByNameHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": params.CompanyName, } @@ -428,7 +446,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyByNameHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyByNameHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signingEntityName": params.SigningEntityName, } @@ -458,7 +476,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyDeleteCompanyByIDHandler", + "functionName": "v2.company.handlers.CompanyDeleteCompanyByIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": params.CompanyID, "authUserName": utils.StringValue(params.XUSERNAME), @@ -512,7 +530,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "company.handlers.CompanyDeleteCompanyBySFIDHandler", + "functionName": "v2.company.handlers.CompanyDeleteCompanyBySFIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, "authUserName": utils.StringValue(params.XUSERNAME), @@ -569,7 +587,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "company.handlers.CompanyContributorAssociationHandler", + "functionName": "v2.company.handlers.CompanyContributorAssociationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, "userEmail": params.Body.UserEmail.String(), @@ -595,7 +613,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "company.handlers.CompanyContributorAssociationHandler", + "functionName": "v2.company.handlers.CompanyContributorAssociationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": params.CompanySFID, } @@ -652,7 +670,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "company.handlers.CompanyGetCompanyByInternalIDHandler", + "functionName": "v2.company.handlers.CompanyGetCompanyByInternalIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": params.CompanyName, "websiteName": params.WebsiteName, @@ -701,7 +719,7 @@ func errorResponse(reqID string, err error) *models.ErrorResponse { // isUserHaveAccessToCLAProjectOrganization is a helper function to determine if the user has access to the specified project and organization func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *auth.User, projectSFID, organizationSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { f := logrus.Fields{ - "functionName": "company.handlers.isUserHaveAccessToCLAProjectOrganization", + "functionName": "v2.company.handlers.isUserHaveAccessToCLAProjectOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "organizationSFID": organizationSFID, diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index beda500d3..f1fadcb39 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -127,7 +127,7 @@ func NewService(v1CompanyService v1Company.IService, sigRepo signatures.Signatur func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyModel *models.Company, projectSFID string) (*models.CompanyClaManagers, error) { f := logrus.Fields{ - "functionName": "GetCompanyProjectCLAManagers", + "functionName": "v2.company.service.GetCompanyProjectCLAManagers", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "companyID": v1CompanyModel.CompanyID, @@ -254,7 +254,7 @@ func fillEventsInfo(claManagers []*v2Models.CompanyClaManager, addedEvents *v1Mo func (s *service) GetCompanyAdmins(ctx context.Context, companySFID string) (*models.CompanyAdminList, error) { f := logrus.Fields{ - "functionName": "GetCompanyAdmins", + "functionName": "v2.company.service.GetCompanyAdmins", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, } @@ -289,7 +289,7 @@ func (s *service) GetCompanyAdmins(ctx context.Context, companySFID string) (*mo func (s *service) GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) { f := logrus.Fields{ - "functionName": "GetCompanyProjectActiveCLAs", + "functionName": "v2.company.service.GetCompanyProjectActiveCLAs", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "companyID": companyID, @@ -330,23 +330,28 @@ func (s *service) GetCompanyProjectActiveCLAs(ctx context.Context, companyID str func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companyID string, searchTerm string) (*models.CorporateContributorList, error) { f := logrus.Fields{ - "functionName": "GetCompanyProjectContributors", + "functionName": "v2.company.service.GetCompanyProjectContributors", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "companyID": companyID, "searchTerm": searchTerm, } + list := make([]*models.CorporateContributor, 0) + log.WithFields(f).Debugf("querying for employee contributors...") sigs, err := s.getAllCompanyProjectEmployeeSignatures(ctx, companyID, projectSFID) if err != nil { - log.WithFields(f).Warnf("problem fetching all company project employee signatures, error: %+v", err) + log.WithFields(f).WithError(err).Warn("problem fetching all company project employee signatures") return nil, err } if len(sigs) == 0 { + log.WithFields(f).Debug("not signatures found - returning emtpy list") return &models.CorporateContributorList{ List: list, }, nil } + log.WithFields(f).Debugf("found %d signatures", len(sigs)) + var wg sync.WaitGroup result := make(chan *models.CorporateContributor) wg.Add(len(sigs)) @@ -355,6 +360,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID close(result) }() + log.WithFields(f).Debugf("adding additional corporate contributor details for %d signatures...", len(sigs)) for _, sig := range sigs { go fillCorporateContributorModel(&wg, s.userRepo, sig, result, searchTerm) } @@ -370,7 +376,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID func (s *service) CreateCompany(ctx context.Context, params *v2Ops.CreateCompanyParams) (*models.CompanyOutput, error) { f := logrus.Fields{ - "functionName": "service.CreateCompany", + "functionName": "v2.company.service.CreateCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": params.Input.CompanyName, "signingEntityName": params.Input.SigningEntityName, @@ -485,7 +491,7 @@ func (s *service) CreateCompany(ctx context.Context, params *v2Ops.CreateCompany func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization, authUser *auth.User) (*models.CompanyOutput, error) { f := logrus.Fields{ - "functionName": "company.service.CreateCompanyFromSFModel", + "functionName": "v2.company.service.CreateCompanyFromSFModel", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "organizationID": orgModel.Name, "organizationName": orgModel.Name, @@ -520,7 +526,7 @@ func (s *service) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgMod // GetCompanyByName deletes the company by name func (s *service) GetCompanyByName(ctx context.Context, companyName string) (*models.Company, error) { f := logrus.Fields{ - "functionName": "GetCompanyByName", + "functionName": "v2.company.service.GetCompanyByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": companyName, } @@ -548,7 +554,7 @@ func (s *service) GetCompanyByName(ctx context.Context, companyName string) (*mo // GetCompanyBySigningEntityName retrieves the company by signing entity name func (s *service) GetCompanyBySigningEntityName(ctx context.Context, signingEntityName string) (*models.Company, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompanyBySigningEntityName", + "functionName": "v2.company.service.GetCompanyBySigningEntityName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signingEntityName": signingEntityName, } @@ -600,7 +606,7 @@ func (s *service) GetCompanyBySigningEntityName(ctx context.Context, signingEnti // GetCompanyByID retrieves the company by internal ID func (s *service) GetCompanyByID(ctx context.Context, companyID string) (*models.Company, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompanyByID", + "functionName": "v2.company.service.GetCompanyByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, } @@ -627,7 +633,7 @@ func (s *service) GetCompanyByID(ctx context.Context, companyID string) (*models func (s *service) AssociateContributor(ctx context.Context, companySFID string, userEmail string) (*models.Contributor, error) { f := logrus.Fields{ - "functionName": "company.service.AssociateContributor", + "functionName": "v2.company.service.AssociateContributor", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, "userEmail": userEmail, @@ -674,7 +680,7 @@ func (s *service) AssociateContributor(ctx context.Context, companySFID string, //CreateContributor creates contributor for contributor prospect func (s *service) CreateContributor(ctx context.Context, companyID string, projectID string, userEmail string, ClaGroupID string) (*models.Contributor, error) { f := logrus.Fields{ - "functionName": "company.service.CreateContributor", + "functionName": "v2.company.service.CreateContributor", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectID": projectID, @@ -766,7 +772,7 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje //AssociateContributorByGroup creates contributor by group for contributor prospect func (s *service) AssociateContributorByGroup(ctx context.Context, companySFID, userEmail string, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, ClaGroupID string) ([]*models.Contributor, string, error) { f := logrus.Fields{ - "functionName": "company.service.AssociateContributorByGroup", + "functionName": "v2.company.service.AssociateContributorByGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, "ClaGroupID": ClaGroupID, @@ -809,7 +815,7 @@ func (s *service) AssociateContributorByGroup(ctx context.Context, companySFID, // GetCompanyBySFID retrieves the company by external SFID func (s *service) GetCompanyBySFID(ctx context.Context, companySFID string) (*models.Company, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompanyBySFID", + "functionName": "v2.company.service.GetCompanyBySFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, } @@ -866,7 +872,7 @@ func (s *service) DeleteCompanyBySFID(ctx context.Context, companyID string) err func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) { f := logrus.Fields{ - "functionName": "companyModel.service.GetCompanyProjectCLA", + "functionName": "v2.company.service.GetCompanyProjectCLA", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, @@ -1075,7 +1081,7 @@ func (s *service) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, // corresponding CLA managers func (s *service) GetCompanyCLAGroupManagers(ctx context.Context, companyID, claGroupID string) (*models.CompanyClaManagers, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompanyCLAGroupManagers", + "functionName": "v2.company.service.GetCompanyCLAGroupManagers", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "claGroupID": claGroupID, @@ -1153,7 +1159,7 @@ func v2ProjectToMap(projectDetails *v2ProjectServiceModels.ProjectOutputDetailed func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, projectSFID string) (map[string]*claGroupModel, error) { f := logrus.Fields{ - "functionName": "company.service.getCLAGroupsUnderProjectOrFoundation", + "functionName": "v2.company.service.getCLAGroupsUnderProjectOrFoundation", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -1292,7 +1298,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, proj func (s *service) getAllCCLASignatures(ctx context.Context, companyID string) ([]*v1Models.Signature, error) { f := logrus.Fields{ - "functionName": "company.service.getAllCCLASignatures", + "functionName": "v2.company.service.getAllCCLASignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, } @@ -1335,7 +1341,7 @@ func getUsersInfo(lfUsernames []string) (map[string]*v2UserServiceModels.User, e func fillUsersInfo(claManagers []*models.CompanyClaManager, usermap map[string]*v2UserServiceModels.User) { f := logrus.Fields{ - "functionName": "company.service.fillUsersInfo", + "functionName": "v2.company.service.fillUsersInfo", } log.WithFields(f).Debug("filling users info...") @@ -1359,7 +1365,7 @@ func fillUsersInfo(claManagers []*models.CompanyClaManager, usermap map[string]* func fillProjectInfo(claManagers []*models.CompanyClaManager, claGroups map[string]*claGroupModel) { f := logrus.Fields{ - "functionName": "company.service.fillProjectInfo", + "functionName": "v2.company.service.fillProjectInfo", } log.WithFields(f).Debug("filling project info...") for _, claManager := range claManagers { @@ -1375,7 +1381,7 @@ func fillProjectInfo(claManagers []*models.CompanyClaManager, claGroups map[stri func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1Models.Signature, activeCla *models.ActiveCla, claGroups map[string]*claGroupModel, companyID string) { f := logrus.Fields{ - "functionName": "v1CompanyModel.service.fillActiveCLA", + "functionName": "v2.company.service.fillActiveCLA", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, } @@ -1509,18 +1515,23 @@ func (s *service) filterClaProjects(ctx context.Context, projects []*v2ProjectSe } func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepository, sig *v1Models.Signature, result chan *models.CorporateContributor, searchTerm string) { + f := logrus.Fields{ + "functionName": "v2.company.service.fillCorporateContributorModel", + } defer wg.Done() user, err := usersRepo.GetUser(sig.SignatureReferenceID) if err != nil { - log.Error("fillCorporateContributorModel: unable to get user info", err) + log.WithFields(f).Warnf("unable to load user information using signature ID: %s", sig.SignatureReferenceID) return } + if searchTerm != "" { ls := strings.ToLower(searchTerm) if !(strings.Contains(strings.ToLower(user.Username), ls) || strings.Contains(strings.ToLower(user.LfUsername), ls)) { return } } + var contributor models.CorporateContributor var sigSignedTime = sig.SignatureCreated contributor.GithubID = user.GithubUsername @@ -1528,7 +1539,7 @@ func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepos contributor.Name = user.Username t, err := utils.ParseDateTime(sig.SignatureCreated) if err != nil { - log.Error("fillCorporateContributorModel: unable to parse time", err) + log.WithFields(f).WithError(err).Warn("unable to parse time") } else { sigSignedTime = utils.TimeToString(t) } @@ -1541,12 +1552,12 @@ func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepos func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, companyID string, projectSFID string) ([]*v1Models.Signature, error) { f := logrus.Fields{ - "functionName": "company.service.getAllCompanyProjectEmployeeSignatures", + "functionName": "v2.company.service.getAllCompanyProjectEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectSFID": projectSFID, } - log.WithFields(f).Debug("getAllCompanyProjectEmployeeSignatures") + log.WithFields(f).Debug("querying company and project...") _, claGroup, err := s.getCompanyAndClaGroup(ctx, companyID, projectSFID) if err != nil { return nil, err @@ -1566,50 +1577,109 @@ func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, co // get company and project in parallel func (s *service) getCompanyAndClaGroup(ctx context.Context, companyID, projectSFID string) (*v1Models.Company, *v1Models.ClaGroup, error) { f := logrus.Fields{ - "functionName": "company.service.getCompanyAndClaGroup", + "functionName": "v2.company.service.getCompanyAndClaGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectSFID": projectSFID, } - var comp *v1Models.Company - var claGroup *v1Models.ClaGroup - var companyErr, projectErr error - // query projects and company - var cp sync.WaitGroup - cp.Add(2) - go func() { - defer cp.Done() - comp, companyErr = s.companyRepo.GetCompany(ctx, companyID) - }() - go func() { - defer cp.Done() + + type CompanyResult struct { + companyModel *v1Models.Company + Error error + } + companyResultChannel := make(chan *CompanyResult, 1) + type CLAGroupResult struct { + claGroupModel *v1Models.ClaGroup + Error error + } + claGroupResultChannel := make(chan *CLAGroupResult, 1) + + // Load the company + go func(companyID string) { + log.WithFields(f).Debugf("loading company by ID: %s", companyID) + comp, companyErr := s.companyRepo.GetCompany(ctx, companyID) + // Return the result through the channel + companyResultChannel <- &CompanyResult{ + companyModel: comp, + Error: companyErr, + } + }(companyID) + + // Load the CLA group associated with the project + go func(projectSFID string) { t := time.Now() var pm *projects_cla_groups.ProjectClaGroup - pm, projectErr = s.projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + log.WithFields(f).Debugf("loading CLA Group by project SFID: %s", projectSFID) + pm, projectErr := s.projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) if projectErr != nil { log.WithFields(f).Debugf("cla group mapping not found for projectSFID %s", projectSFID) + // Return the result through the channel + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: nil, + Error: projectErr, + } + return + } else if pm == nil || pm.ClaGroupID == "" { + // Return the result through the channel + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: nil, + Error: &utils.ProjectCLAGroupMappingNotFound{ + ProjectSFID: projectSFID, + }, + } return } - claGroup, projectErr = s.projectRepo.GetCLAGroupByID(ctx, pm.ClaGroupID, DontLoadRepoDetails) - if claGroup == nil { - projectErr = ErrProjectNotFound + + log.WithFields(f).Debugf("loading CLA Group by ID: %s", pm.ClaGroupID) + claGroup, projectErr := s.projectRepo.GetCLAGroupByID(ctx, pm.ClaGroupID, DontLoadRepoDetails) + if projectErr != nil { + // Return the result through the channel + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: claGroup, + Error: &utils.CLAGroupNotFound{ + CLAGroupID: pm.ClaGroupID, + Err: projectErr, + }, + } + } else if claGroup == nil { + // Return the result through the channel + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: claGroup, + Error: &utils.CLAGroupNotFound{ + CLAGroupID: pm.ClaGroupID, + }, + } + } else { + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: claGroup, + Error: nil, + } + log.WithField("time_taken", time.Since(t).String()).Debugf("getting project by external id : %s completed", projectSFID) } - log.WithField("time_taken", time.Since(t).String()).Debugf("getting project by external id : %s completed", projectSFID) - }() - cp.Wait() - if companyErr != nil { - return nil, nil, companyErr + }(projectSFID) + + // Grab the results + log.WithFields(f).Debug("waiting for companies query to finish...") + companyResponse := <-companyResultChannel + if companyResponse.Error != nil { + return nil, nil, companyResponse.Error } - if projectErr != nil { - return nil, nil, projectErr + log.WithFields(f).Debug("company query finished") + + log.WithFields(f).Debug("waiting for CLA Groups query to finish...") + claGroupResponse := <-claGroupResultChannel + if claGroupResponse.Error != nil { + return nil, nil, claGroupResponse.Error } - return comp, claGroup, nil + log.WithFields(f).Debug("cla groups query finished") + + return companyResponse.companyModel, claGroupResponse.claGroupModel, nil } // autoCreateCompany helper function to create a new company record based on the SF ID and underlying record in SF func (s service) autoCreateCompany(ctx context.Context, companySFID string) (*v1Models.Company, error) { f := logrus.Fields{ - "functionName": "company.service.autoCreateCompany", + "functionName": "v2.company.service.autoCreateCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companySFID": companySFID, } @@ -1655,7 +1725,7 @@ func (s service) autoCreateCompany(ctx context.Context, companySFID string) (*v1 func (s *service) GetCompanyLookup(ctx context.Context, orgName string, websiteName string) (*models.Lookup, error) { f := logrus.Fields{ - "functionName": "company.service.GetCompanyLookup", + "functionName": "v2.company.service.GetCompanyLookup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "orgName": orgName, "websiteName": websiteName, @@ -1707,7 +1777,7 @@ func (s *service) GetCompanyLookup(ctx context.Context, orgName string, websiteN func (s *service) RequestCompanyAdmin(ctx context.Context, userID string, claManagerEmail string, claManagerName string, contributorName string, contributorEmail string, projectName string, companyName string, corporateLink string) error { orgServices := orgService.GetClient() f := logrus.Fields{ - "functionName": "RequestCompanyAdmin", + "functionName": "v2.company.service.RequestCompanyAdmin", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": companyName, "claGroupName": projectName, From ff3e8e54986d70b790c8e574d9d4c21c1a586d62 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 15 Mar 2021 09:31:30 -0700 Subject: [PATCH 0162/1276] Added signature ID to contributors response (#2783) - Included signatureID in the corporate contributors response - added docusign date/time if available, otherwise use signature modified date Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 25 +++++++++++++++++-- .../swagger/common/corporate-contributor.yaml | 6 +++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 6cda340ae..5c9a68428 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2494,21 +2494,42 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla return nil, err } + log.WithFields(f).Debugf("located %d signatures...", len(dbSignatures)) for _, sig := range dbSignatures { if searchTerm != nil { if !strings.Contains(sig.SignatureReferenceNameLower, *searchTerm) { continue } } + var sigCreatedTime = sig.DateCreated t, err := utils.ParseDateTime(sig.DateCreated) if err != nil { - log.Error("fillCorporateContributorModel: unable to parse time", err) + log.WithFields(f).WithError(err).Warn("unable to parse signature date created time") } else { sigCreatedTime = utils.TimeToString(t) } + + var sigSignedTime = sig.DateModified + t, err = utils.ParseDateTime(sig.DateModified) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature date modified time") + } else { + sigSignedTime = utils.TimeToString(t) + } + // Use the user docusign date signed value if it is present - older signatures do not have this + if sig.UserDocusignDateSigned != "" { + t, err = utils.ParseDateTime(sig.UserDocusignDateSigned) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature docusign date signed time") + } else { + sigSignedTime = utils.TimeToString(t) + } + } + signatureVersion := fmt.Sprintf("v%s.%s", sig.SignatureDocumentMajorVersion, sig.SignatureDocumentMinorVersion) out.List = append(out.List, &models.CorporateContributor{ + SignatureID: sig.SignatureID, GithubID: sig.UserGithubUsername, LinuxFoundationID: sig.UserLFUsername, Name: sig.UserName, @@ -2516,7 +2537,7 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla Email: sig.UserEmail, Timestamp: sigCreatedTime, UserDocusignName: sig.UserDocusignName, - UserDocusignDateSigned: sig.UserDocusignDateSigned, + UserDocusignDateSigned: sigSignedTime, SignatureModified: sig.DateModified, }) } diff --git a/cla-backend-go/swagger/common/corporate-contributor.yaml b/cla-backend-go/swagger/common/corporate-contributor.yaml index fc4a5f489..28144b042 100644 --- a/cla-backend-go/swagger/common/corporate-contributor.yaml +++ b/cla-backend-go/swagger/common/corporate-contributor.yaml @@ -3,6 +3,10 @@ type: object properties: + signatureID: + description: internal signature ID + $ref: './common/properties/internal-id.yaml' + x-omitempty: false name: type: string example: "john doe" @@ -36,5 +40,3 @@ properties: type: string description: the signature modified created time example: '2019-05-03T18:59:13.082304+0000' - - From 8eee8cad3f95218964bea2a4bb7da1246ab959be Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 16 Mar 2021 22:55:40 +0300 Subject: [PATCH 0163/1276] [#2784] Feature/Event Data for company Admin (#2787) - Resolved masked email showing in event logs Signed-off-by: wanyaland --- cla-backend-go/v2/cla_manager/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 0c2d297da..e9765a1ef 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -749,7 +749,7 @@ func (s *service) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool CompanyID: v1CompanyModel.CompanyID, EventData: &events.ContributorNotifyCompanyAdminData{ AdminName: admin.Contact.Name, - AdminEmail: admin.Contact.EmailAddress, + AdminEmail: userService.GetPrimaryEmail(adminUser), }, }) } From 0324b33d51bde910573383cf68801c22cdef86ec Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 17 Mar 2021 21:04:40 -0700 Subject: [PATCH 0164/1276] Added Additional Event Logging (#2794) Signed-off-by: David Deal --- cla-backend-go/events/service.go | 55 ++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index c2a9bb26a..efffffb43 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -143,10 +143,14 @@ type LogEventArgs struct { func (s *service) loadCompany(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ - "functionName": "loadCompany", + "functionName": "v1.events.service.loadCompany", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } + if args == nil { + return errors.New("unable to load company data - args is nil") + } + if args.CompanyModel != nil { args.CompanyName = args.CompanyModel.CompanyName args.CompanyID = args.CompanyModel.CompanyID @@ -168,10 +172,14 @@ func (s *service) loadCompany(ctx context.Context, args *LogEventArgs) error { func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ - "functionName": "events.service.loadCLAGroup", + "functionName": "v1.events.service.loadCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } + if args == nil { + return errors.New("unable to load CLA Group data - args is nil") + } + // First, attempt to user the CLA Group model that was provided... if args.ClaGroupModel != nil { args.CLAGroupID = args.ClaGroupModel.ProjectID @@ -211,10 +219,14 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ - "functionName": "loadSFProject", + "functionName": "v1.events.service.loadSFProject", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } + if args == nil { + return errors.New("unable to load SF project data - args is nil") + } + // Should be the same value for now...cleanup: need to remove one or the other if args.ProjectID == "" && args.ProjectSFID != "" { args.ProjectID = args.ProjectSFID @@ -262,37 +274,46 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ - "functionName": "loadUser", + "functionName": "v1.events.service.loadUser", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } + if args == nil { + return errors.New("unable to load user data - args is nil") + } + if args.UserModel != nil { args.UserName = args.UserModel.Username args.UserID = args.UserModel.UserID args.LfUsername = args.UserModel.LfUsername + log.WithFields(f).Debug("loaded user for event log by caller provided user model") return nil - } - if args.UserID == "" && args.LfUsername == "" { + } else if args.UserID == "" && args.LfUsername == "" { log.WithFields(f).Warn("failed to load user for event log - user ID and username were not set") return errors.New("require userID or LfUsername") } + var userModel *models.User var err error + // Try loading by LF username if args.LfUsername != "" { + log.WithFields(f).Debugf("loading user by LF username: %s...", args.LfUsername) userModel, err = s.combinedRepo.GetUserByUserName(args.LfUsername, true) if err != nil { log.WithFields(f).WithError(err).Warnf("failed to load user by username: %s", args.LfUsername) - return err } } + + // Try loading by user ID if args.UserID != "" { + log.WithFields(f).Debugf("loading user by user ID: %s...", args.UserID) userModel, err = s.combinedRepo.GetUser(args.UserID) if err != nil { log.WithFields(f).WithError(err).Warnf("failed to load user by ID: %s", args.UserID) - return err } } + // Did we finally load the user model? if userModel != nil { args.UserModel = userModel args.UserName = userModel.Username @@ -307,23 +328,37 @@ func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { // loadDetails fetches and sets additional information into the data model required to fill out the event log entry func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "v1.events.service.loadDetails", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "args": fmt.Sprintf("%+v", args), + } + + log.WithFields(f).Debug("loading company details...") err := s.loadCompany(ctx, args) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load company details...") return err } + log.WithFields(f).Debug("loading SF project details...") err = s.loadSFProject(ctx, args) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load SF project details...") return err } + log.WithFields(f).Debug("loading CLA Group details...") err = s.loadCLAGroup(ctx, args) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load CLA Group details...") return err } + log.WithFields(f).Debug("loading user details...") err = s.loadUser(ctx, args) if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load user details...") return err } @@ -333,13 +368,13 @@ func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { // LogEventWithContext logs the event in database func (s *service) LogEventWithContext(ctx context.Context, args *LogEventArgs) { f := logrus.Fields{ - "functionName": "events.service.LogEvent", + "functionName": "events.service.LogEventWithContext", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } defer func() { if r := recover(); r != nil { - log.WithFields(f).Error("panic occurred in CreateEvent", fmt.Errorf("%v", r)) + log.WithFields(f).Errorf("panic occurred - %+v", r) } }() From e8cfa83f75481f82ce85bc3d8d95f3d69cce8c74 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 18 Mar 2021 15:05:14 -0700 Subject: [PATCH 0165/1276] Updated Logic to Set the DocuSign Signed Date in the Response Model (#2798) Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 45 +++++++++++++++------ cla-backend-go/v2/cla_groups/handlers.go | 2 +- cla-backend-go/v2/project-service/client.go | 2 + 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 5c9a68428..2569cf9c2 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2365,10 +2365,27 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID } } - signedOn := sig.DateCreated - if sig.SignedOn != "" { - signedOn = sig.SignedOn + // Set the signed date/time + var sigSignedTime string + // Use the user docusign date signed value if it is present - older signatures do not have this + if sig.UserDocusignDateSigned != "" { + // Put the date into a standard format + t, err := utils.ParseDateTime(sig.UserDocusignDateSigned) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature docusign date signed time") + } else { + sigSignedTime = utils.TimeToString(t) + } + } else { + // Put the date into a standard format + t, err := utils.ParseDateTime(sig.DateCreated) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature date created time") + } else { + sigSignedTime = utils.TimeToString(t) + } } + intermediateResponse = append(intermediateResponse, &IclaSignatureWithDetails{ IclaSignature: &models.IclaSignature{ GithubUsername: sig.UserGithubUsername, @@ -2376,9 +2393,9 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID SignatureID: sig.SignatureID, UserEmail: sig.UserEmail, UserName: sig.UserName, - SignedOn: signedOn, + SignedOn: sigSignedTime, UserDocusignName: sig.UserDocusignName, - UserDocusignDateSigned: sig.UserDocusignDateSigned, + UserDocusignDateSigned: sigSignedTime, SignatureModified: sig.DateModified, }, SignatureReferenceID: sig.SignatureReferenceID, @@ -2510,21 +2527,25 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla sigCreatedTime = utils.TimeToString(t) } - var sigSignedTime = sig.DateModified - t, err = utils.ParseDateTime(sig.DateModified) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to parse signature date modified time") - } else { - sigSignedTime = utils.TimeToString(t) - } + // Set the signed date/time + var sigSignedTime string // Use the user docusign date signed value if it is present - older signatures do not have this if sig.UserDocusignDateSigned != "" { + // Put the date into a standard format t, err = utils.ParseDateTime(sig.UserDocusignDateSigned) if err != nil { log.WithFields(f).WithError(err).Warn("unable to parse signature docusign date signed time") } else { sigSignedTime = utils.TimeToString(t) } + } else { + // Put the date into a standard format + t, err = utils.ParseDateTime(sig.DateCreated) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature date created time") + } else { + sigSignedTime = utils.TimeToString(t) + } } signatureVersion := fmt.Sprintf("v%s.%s", sig.SignatureDocumentMajorVersion, sig.SignatureDocumentMinorVersion) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index eb89bfb2f..a6e97d184 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -259,7 +259,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P project, projectErr := psc.GetProject(projectSFID) if projectErr != nil || project == nil { msg := fmt.Sprintf("Failed to get salesforce project: %s", projectSFID) - log.WithFields(f).Warn(msg) + log.WithFields(f).WithError(projectErr).Warn(msg) if _, ok := projectErr.(*v2ProjectServiceClient.GetProjectNotFound); ok { return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload( utils.ErrorResponseNotFoundWithError(reqID, fmt.Sprintf("project not found with ID: [%s]", projectSFID), projectErr)) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 724c47e29..54ecb21cc 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -87,8 +87,10 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed clientAuth := runtimeClient.BearerToken(tok) // Lookup the project + log.WithFields(f).Debugf("cache miss - looking up project in the service for: %s...", projectSFID) projectModel, err := pmm.getProject(projectSFID, clientAuth) if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to lookup project in the project service for: %s", projectSFID) return nil, err } From 32c58c247c6ab0245775ea1c729691a2ec1cc0a9 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 19 Mar 2021 01:06:10 +0300 Subject: [PATCH 0166/1276] [#2790] Feature/Activity Log User update (#2800) - Updated activity log with LF Name details Signed-off-by: wanyaland --- cla-backend-go/events/event_data.go | 12 ++++----- cla-backend-go/events/service.go | 38 ++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index d6d54d7b8..1bd7802a1 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -457,7 +457,7 @@ func (ed *CompanyACLRequestDeniedEventData) GetEventDetailsString(args *LogEvent // GetEventDetailsString . . . func (ed *CompanyACLUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User with LF Username: %s added to the ACL for Company: %s by: %s.", - ed.UserLFID, args.CompanyName, args.UserName) + args.LFUser.Name, args.CompanyName, args.UserName) return data, true } @@ -725,14 +725,14 @@ func (ed *ContributorAssignCLADesignee) GetEventDetailsString(args *LogEventArgs // GetEventDetailsString . . . func (ed *UserConvertToContactData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s was converted to Contact state for Project: %s with ID: %s.", - args.LfUsername, args.ProjectName, args.ProjectSFID) + args.LFUser.Name, args.ProjectName, args.ProjectSFID) return data, true } // GetEventDetailsString . . . func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s was assigned Scope: %s with Role: %s for Project: %s with ID: %s.", - args.LfUsername, + args.LFUser.Name, ed.Scope, ed.Role, args.ProjectName, args.ProjectSFID) return data, true } @@ -980,7 +980,7 @@ func (ed *CompanyACLRequestDeniedEventData) GetEventSummaryString(args *LogEvent // GetEventSummaryString . . . func (ed *CompanyACLUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user with LF username %s was added to the access list for the company %s by the user %s.", - ed.UserLFID, args.CompanyName, args.UserName) + args.LFUser.Name, args.CompanyName, args.UserName) return data, true } @@ -1533,7 +1533,7 @@ func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs // GetEventSummaryString . . . func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was converted to a contact", args.LfUsername) + data := fmt.Sprintf("The user %s was converted to a contact", args.LFUser.Name) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1549,7 +1549,7 @@ func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString . . . func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added to the role %s", args.LfUsername, ed.Role) + data := fmt.Sprintf("The user %s was added to the role %s", args.LFUser.Name, ed.Role) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index efffffb43..b2ea502ce 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -11,6 +11,8 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + user_service "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" + userServiceModels "github.com/communitybridge/easycla/cla-backend-go/v2/user-service/models" "github.com/sirupsen/logrus" @@ -122,6 +124,7 @@ type LogEventArgs struct { LfUsername string UserName string UserModel *models.User + LFUser *userServiceModels.User CLAGroupID string CLAGroupName string @@ -316,7 +319,12 @@ func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { // Did we finally load the user model? if userModel != nil { args.UserModel = userModel - args.UserName = userModel.Username + // Update username with LF Name value if exists ... + if args.LFUser != nil { + args.UserName = args.LFUser.Name + } else { + args.UserName = userModel.Username + } args.UserID = userModel.UserID args.LfUsername = userModel.LfUsername } else { @@ -326,6 +334,27 @@ func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { return nil } +func (s *service) loadLFUser(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "v1.events.service.loadLFUser", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + if args.LFUser != nil { + return nil + } + + if args.LfUsername != "" { + lfUser, lfUserErr := user_service.GetClient().GetUserByUsername(args.LfUsername) + if lfUserErr != nil || lfUser == nil { + log.WithFields(f).Warnf("unable to fetch LF User by username: %s", args.LfUsername) + return nil + } + args.LFUser = lfUser + } + return nil +} + // loadDetails fetches and sets additional information into the data model required to fill out the event log entry func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ @@ -355,6 +384,13 @@ func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { return err } + log.WithFields(f).Debug("loading LF user details ...") + err = s.loadLFUser(ctx, args) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load LF User details...") + return err + } + log.WithFields(f).Debug("loading user details...") err = s.loadUser(ctx, args) if err != nil { From f859ee8d2b618773dcedcd2657bdc27148459dc8 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Fri, 19 Mar 2021 00:07:50 +0200 Subject: [PATCH 0167/1276] [#2432] github v4 branch protection (#2788) Signed-off-by: makkalot --- cla-backend-go/Makefile | 3 + cla-backend-go/gerrits/repository.go | 2 +- .../github/branch_protection/interfaces.go | 32 + .../github/branch_protection/mock.go | 133 ++++ .../branch_protection/protected_branch.go | 336 +++++++++ .../protected_branch_limiter.go | 114 +++ .../protected_branch_test.go | 375 ++++++++++ .../branch_protection/protected_branch_v4.go | 160 +++++ cla-backend-go/github/client.go | 3 +- cla-backend-go/github/mock.go | 103 --- cla-backend-go/github/protected_branch.go | 655 ------------------ .../github/protected_branch_test.go | 351 ---------- cla-backend-go/repositories/handlers.go | 6 +- .../repositories/mock/mock_repository.go | 46 +- .../repositories/mock/mock_service.go | 59 +- cla-backend-go/repositories/repository.go | 6 +- cla-backend-go/swagger/cla.v2.yaml | 5 + cla-backend-go/tests/github_v4_test.go | 10 +- cla-backend-go/utils/constants.go | 3 + cla-backend-go/v2/dynamo_events/autoenable.go | 8 +- .../v2/dynamo_events/github_organization.go | 23 +- .../v2/dynamo_events/github_repository.go | 23 +- cla-backend-go/v2/metrics/repository.go | 2 +- cla-backend-go/v2/repositories/handlers.go | 23 +- cla-backend-go/v2/repositories/service.go | 71 +- 25 files changed, 1335 insertions(+), 1217 deletions(-) create mode 100644 cla-backend-go/github/branch_protection/interfaces.go create mode 100644 cla-backend-go/github/branch_protection/mock.go create mode 100644 cla-backend-go/github/branch_protection/protected_branch.go create mode 100644 cla-backend-go/github/branch_protection/protected_branch_limiter.go create mode 100644 cla-backend-go/github/branch_protection/protected_branch_test.go create mode 100644 cla-backend-go/github/branch_protection/protected_branch_v4.go delete mode 100644 cla-backend-go/github/mock.go delete mode 100644 cla-backend-go/github/protected_branch.go delete mode 100644 cla-backend-go/github/protected_branch_test.go diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 862bb8d54..e4579a430 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -179,6 +179,9 @@ mock: @cd $(MAKEFILE_DIR) && mkdir -p repositories/mock @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -source=repositories/service.go -package=mock -destination=repositories/mock/mock_service.go @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -source=repositories/repository.go -package=mock -destination=repositories/mock/mock_repository.go + # mocks for github + @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -package=branch_protection -destination=github/branch_protection/mock.go -self_package=github.com/communitybridge/easycla/cla-backend-go/github/branch_protection github.com/communitybridge/easycla/cla-backend-go/github/branch_protection CombinedRepository + run: go run main.go diff --git a/cla-backend-go/gerrits/repository.go b/cla-backend-go/gerrits/repository.go index d93a21e5b..57c2c29a8 100644 --- a/cla-backend-go/gerrits/repository.go +++ b/cla-backend-go/gerrits/repository.go @@ -32,7 +32,7 @@ var ( ErrGerritNotFound = errors.New("gerrit not found") ) -// Repository defines functions of Repositories +// Repository defines functions of V3Repositories type Repository interface { AddGerrit(ctx context.Context, input *models.Gerrit) (*models.Gerrit, error) GetGerrit(ctx context.Context, gerritID string) (*models.Gerrit, error) diff --git a/cla-backend-go/github/branch_protection/interfaces.go b/cla-backend-go/github/branch_protection/interfaces.go new file mode 100644 index 000000000..847b3d250 --- /dev/null +++ b/cla-backend-go/github/branch_protection/interfaces.go @@ -0,0 +1,32 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package branch_protection + +import ( + "context" + + "github.com/google/go-github/v33/github" + "github.com/shurcooL/githubv4" +) + +// V3Repositories is part of the interface working with github repositories, it's inside of the github client +// It's extracted here as interface so we can mock that functionality in the tests. +type V3Repositories interface { + ListByOrg(ctx context.Context, org string, opt *github.RepositoryListByOrgOptions) ([]*github.Repository, *github.Response, error) + Get(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error) +} + +// V4BranchProtectionRepository has v4 (graphQL) branch protection functionality +type V4BranchProtectionRepository interface { + GetRepositoryBranchProtections(ctx context.Context, repositoryOwner, repositoryName string) (*RepoBranchProtectionQueryResult, error) + CreateBranchProtection(ctx context.Context, input *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) + UpdateBranchProtection(ctx context.Context, input *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) + GetRepositoryIDFromName(ctx context.Context, repositoryOwner, repositoryName string) (string, error) +} + +//CombinedRepository is combination of V3Repositories and V4BranchProtectionRepository +type CombinedRepository interface { + V3Repositories + V4BranchProtectionRepository +} diff --git a/cla-backend-go/github/branch_protection/mock.go b/cla-backend-go/github/branch_protection/mock.go new file mode 100644 index 000000000..456925c0e --- /dev/null +++ b/cla-backend-go/github/branch_protection/mock.go @@ -0,0 +1,133 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/communitybridge/easycla/cla-backend-go/github/branch_protection (interfaces: CombinedRepository) + +// Package branch_protection is a generated GoMock package. +package branch_protection + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + github "github.com/google/go-github/v33/github" + githubv4 "github.com/shurcooL/githubv4" +) + +// MockCombinedRepository is a mock of CombinedRepository interface +type MockCombinedRepository struct { + ctrl *gomock.Controller + recorder *MockCombinedRepositoryMockRecorder +} + +// MockCombinedRepositoryMockRecorder is the mock recorder for MockCombinedRepository +type MockCombinedRepositoryMockRecorder struct { + mock *MockCombinedRepository +} + +// NewMockCombinedRepository creates a new mock instance +func NewMockCombinedRepository(ctrl *gomock.Controller) *MockCombinedRepository { + mock := &MockCombinedRepository{ctrl: ctrl} + mock.recorder = &MockCombinedRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockCombinedRepository) EXPECT() *MockCombinedRepositoryMockRecorder { + return m.recorder +} + +// CreateBranchProtection mocks base method +func (m *MockCombinedRepository) CreateBranchProtection(arg0 context.Context, arg1 *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBranchProtection", arg0, arg1) + ret0, _ := ret[0].(*CreateRepoBranchProtectionMutation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBranchProtection indicates an expected call of CreateBranchProtection +func (mr *MockCombinedRepositoryMockRecorder) CreateBranchProtection(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBranchProtection", reflect.TypeOf((*MockCombinedRepository)(nil).CreateBranchProtection), arg0, arg1) +} + +// Get mocks base method +func (m *MockCombinedRepository) Get(arg0 context.Context, arg1, arg2 string) (*github.Repository, *github.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].(*github.Repository) + ret1, _ := ret[1].(*github.Response) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get +func (mr *MockCombinedRepositoryMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCombinedRepository)(nil).Get), arg0, arg1, arg2) +} + +// GetRepositoryBranchProtections mocks base method +func (m *MockCombinedRepository) GetRepositoryBranchProtections(arg0 context.Context, arg1, arg2 string) (*RepoBranchProtectionQueryResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRepositoryBranchProtections", arg0, arg1, arg2) + ret0, _ := ret[0].(*RepoBranchProtectionQueryResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRepositoryBranchProtections indicates an expected call of GetRepositoryBranchProtections +func (mr *MockCombinedRepositoryMockRecorder) GetRepositoryBranchProtections(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryBranchProtections", reflect.TypeOf((*MockCombinedRepository)(nil).GetRepositoryBranchProtections), arg0, arg1, arg2) +} + +// GetRepositoryIDFromName mocks base method +func (m *MockCombinedRepository) GetRepositoryIDFromName(arg0 context.Context, arg1, arg2 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRepositoryIDFromName", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRepositoryIDFromName indicates an expected call of GetRepositoryIDFromName +func (mr *MockCombinedRepositoryMockRecorder) GetRepositoryIDFromName(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryIDFromName", reflect.TypeOf((*MockCombinedRepository)(nil).GetRepositoryIDFromName), arg0, arg1, arg2) +} + +// ListByOrg mocks base method +func (m *MockCombinedRepository) ListByOrg(arg0 context.Context, arg1 string, arg2 *github.RepositoryListByOrgOptions) ([]*github.Repository, *github.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListByOrg", arg0, arg1, arg2) + ret0, _ := ret[0].([]*github.Repository) + ret1, _ := ret[1].(*github.Response) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListByOrg indicates an expected call of ListByOrg +func (mr *MockCombinedRepositoryMockRecorder) ListByOrg(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByOrg", reflect.TypeOf((*MockCombinedRepository)(nil).ListByOrg), arg0, arg1, arg2) +} + +// UpdateBranchProtection mocks base method +func (m *MockCombinedRepository) UpdateBranchProtection(arg0 context.Context, arg1 *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateBranchProtection", arg0, arg1) + ret0, _ := ret[0].(*UpdateRepoBranchProtectionMutation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateBranchProtection indicates an expected call of UpdateBranchProtection +func (mr *MockCombinedRepositoryMockRecorder) UpdateBranchProtection(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBranchProtection", reflect.TypeOf((*MockCombinedRepository)(nil).UpdateBranchProtection), arg0, arg1) +} diff --git a/cla-backend-go/github/branch_protection/protected_branch.go b/cla-backend-go/github/branch_protection/protected_branch.go new file mode 100644 index 000000000..ea56ef5a3 --- /dev/null +++ b/cla-backend-go/github/branch_protection/protected_branch.go @@ -0,0 +1,336 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package branch_protection + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/shurcooL/githubv4" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + + githubpkg "github.com/google/go-github/v33/github" +) + +const ( + // DefaultBranchName is the default branch we'll be working with if not specified + DefaultBranchName = "main" +) + +var ( + // ErrBranchNotProtected indicates the situation where the branch is not enabled for protection on github side + ErrBranchNotProtected = errors.New("not protected") +) + +type combinedRepositoryProvider struct { + V3Repositories + V4BranchProtectionRepository +} + +type branchProtectionRepositoryConfig struct { + enableBlockingLimiter bool + enableNonBlockingLimiter bool +} + +// BranchProtectionRepositoryOption enables optional parameters to BranchProtectionRepository +type BranchProtectionRepositoryOption func(config *branchProtectionRepositoryConfig) + +// EnableBlockingLimiter enables the blocking limiter +func EnableBlockingLimiter() BranchProtectionRepositoryOption { + return func(config *branchProtectionRepositoryConfig) { + config.enableBlockingLimiter = true + } +} + +// EnableNonBlockingLimiter enables the non-blocking limiter +func EnableNonBlockingLimiter() BranchProtectionRepositoryOption { + return func(config *branchProtectionRepositoryConfig) { + config.enableNonBlockingLimiter = true + } +} + +// BranchProtectionRepository contains helper methods interacting with github api related to branch protection +type BranchProtectionRepository struct { + combinedRepo CombinedRepository +} + +// NewBranchProtectionRepository creates a new BranchProtectionRepository +func NewBranchProtectionRepository(installationID int64, opts ...BranchProtectionRepositoryOption) (*BranchProtectionRepository, error) { + v4BranchProtectionRepo, err := NewBranchProtectionRepositoryV4(installationID) + if err != nil { + return nil, fmt.Errorf("initializing v4 github client failed : %v", err) + } + + v3Client, err := github.NewGithubAppClient(installationID) + if err != nil { + return nil, fmt.Errorf("initializing v3 github client failed : %v", err) + } + + combinedRepo := combinedRepositoryProvider{ + V3Repositories: v3Client.Repositories, + V4BranchProtectionRepository: v4BranchProtectionRepo, + } + + return newBranchProtectionRepository(combinedRepo, opts...), nil +} + +func newBranchProtectionRepository(combinedRepo CombinedRepository, opts ...BranchProtectionRepositoryOption) *BranchProtectionRepository { + config := &branchProtectionRepositoryConfig{} + for _, o := range opts { + o(config) + } + + if config.enableNonBlockingLimiter { + combinedRepo = NewNonBlockLimiterRepositories(combinedRepo) + } else if config.enableBlockingLimiter { + combinedRepo = NewBlockLimiterRepositories(combinedRepo) + } + + return &BranchProtectionRepository{ + combinedRepo: combinedRepo, + } +} + +// GetOwnerName retrieves the owner name of the given org and repo name +func (bp *BranchProtectionRepository) GetOwnerName(ctx context.Context, orgName, repoName string) (string, error) { + repoName = CleanGithubRepoName(repoName) + log.Debugf("GetOwnerName : getting owner name for org %s and repoName : %s", orgName, repoName) + listOpt := &githubpkg.RepositoryListByOrgOptions{ + ListOptions: githubpkg.ListOptions{ + PerPage: 30, + }, + } + for { + repos, resp, err := bp.combinedRepo.ListByOrg(ctx, orgName, listOpt) + if err != nil { + if ok, wErr := github.CheckAndWrapForKnownErrors(resp, err); ok { + return "", wErr + } + return "", err + } + + if len(repos) == 0 { + log.Warnf("GetOwnerName : no repos found under orgName : %s (maybe no access ?)", orgName) + return "", nil + } + + for _, repo := range repos { + if *repo.Name == repoName { + if repo.Owner != nil { + owner := *repo.Owner + return *owner.Login, nil + } + } + } + + // means we're at the end of it so exit + if resp.NextPage == 0 { + log.Warnf("GetOwnerName : owner name not found for org : %s and repo : %s", orgName, repoName) + return "", nil + } + + // set it to the next page + listOpt.Page = resp.NextPage + } +} + +// GetDefaultBranchForRepo helps with pulling the default branch for the given repo +func (bp *BranchProtectionRepository) GetDefaultBranchForRepo(ctx context.Context, owner, repoName string) (string, error) { + repoName = CleanGithubRepoName(repoName) + repo, resp, err := bp.combinedRepo.Get(ctx, owner, repoName) + if err != nil { + if ok, wErr := github.CheckAndWrapForKnownErrors(resp, err); ok { + return "", wErr + } + return "", err + } + + var defaultBranch string + if repo.DefaultBranch == nil { + defaultBranch = DefaultBranchName + } else { + defaultBranch = *repo.DefaultBranch + } + + return defaultBranch, nil +} + +// GetProtectedBranch fetches the protected branch details +func (bp *BranchProtectionRepository) GetProtectedBranch(ctx context.Context, owner, repoName, protectedBranchName string) (*BranchProtectionRule, error) { + repoName = CleanGithubRepoName(repoName) + branchProtections, err := bp.combinedRepo.GetRepositoryBranchProtections(ctx, owner, repoName) + if err != nil { + return nil, fmt.Errorf("fetching repo protections for owner : %s and repoName : %s failed : %w", owner, repoName, err) + } + + // it's not found this pattern or branch + if branchProtections.RepositoryOwner.Repository.BranchProtectionRules.TotalCount == 0 { + return nil, ErrBranchNotProtected + } + + for _, protection := range branchProtections.RepositoryOwner.Repository.BranchProtectionRules.Nodes { + if protection.Pattern == protectedBranchName { + return &protection, nil + } + } + + return nil, ErrBranchNotProtected +} + +//EnableBranchProtection enables branch protection if not enabled and makes sure passed arguments such as enforceAdmin +//statusChecks are applied. The operation makes sure it doesn't override the existing checks. +func (bp *BranchProtectionRepository) EnableBranchProtection(ctx context.Context, owner, repoName, branchName string, enforceAdmin bool, enableStatusChecks, disableStatusChecks []string) error { + repoName = CleanGithubRepoName(repoName) + + // fetch the existing ones + queryResult, err := bp.combinedRepo.GetRepositoryBranchProtections(ctx, owner, repoName) + if err != nil { + return err + } + + currentProtections := queryResult.RepositoryOwner.Repository.BranchProtectionRules.Nodes + repoID := queryResult.RepositoryOwner.Repository.ID + + createInput, updateInput := prepareBranchProtectionMutation(repoID, currentProtections, &BranchProtectionRule{ + Pattern: branchName, + RequiredStatusCheckContexts: enableStatusChecks, + RequiresStatusChecks: true, + IsAdminEnforced: enforceAdmin, + AllowsDeletions: false, + AllowsForcePushes: false, + }) + if createInput != nil { + _, createErr := bp.combinedRepo.CreateBranchProtection(ctx, createInput) + if createErr != nil { + return fmt.Errorf("creating new branch protection rule for owner : %s and repo : %s failed : %v", owner, repoName, createErr) + } + return nil + } + + _, err = bp.combinedRepo.UpdateBranchProtection(ctx, updateInput) + if err != nil { + return fmt.Errorf("updating current branch rule for owner : %s and repo name : %s, failed : %v", owner, repoName, err) + } + + return nil +} + +//mergeStatusChecks merges the current checks with the new ones and disable the ones that are specified +func mergeStatusChecks(currentChecks []string, enableContexts, disableContexts []string) []string { + + // seems github api is not happy with nils for arrays ;) + if len(enableContexts) == 0 { + enableContexts = []string{} + } + + if currentChecks == nil { + currentChecks = []string{} + } + + finalContexts := []string{} + uniqueEnableContexts := map[string]bool{} + + for _, c := range currentChecks { + // first disable the ones we're not interested into + found := false + if len(disableContexts) > 0 { + for _, disableContext := range disableContexts { + if disableContext == c { + found = true + break + } + } + } + + if found { + continue + } + + uniqueEnableContexts[c] = true + finalContexts = append(finalContexts, c) + } + + for _, c := range enableContexts { + if uniqueEnableContexts[c] { + continue + } + + uniqueEnableContexts[c] = true + finalContexts = append(finalContexts, c) + } + + return finalContexts +} + +// prepareBranchProtectionMutation creates the mutation input objects to modify the branch protection +// the logic is pulled out so we can unit test it without mocking the connections +func prepareBranchProtectionMutation(repoID string, currentProtections []BranchProtectionRule, input *BranchProtectionRule) (*githubv4.CreateBranchProtectionRuleInput, *githubv4.UpdateBranchProtectionRuleInput) { + var foundBranchProtectionRule *BranchProtectionRule + if len(currentProtections) > 0 { + for _, protection := range currentProtections { + if protection.Pattern == input.Pattern { + currentProtection := protection + foundBranchProtectionRule = ¤tProtection + break + } + } + } + + if foundBranchProtectionRule == nil { + var statusChecks []githubv4.String + for _, check := range input.RequiredStatusCheckContexts { + statusChecks = append(statusChecks, githubv4.String(check)) + } + + createInput := githubv4.CreateBranchProtectionRuleInput{ + RepositoryID: repoID, + Pattern: githubv4.String(input.Pattern), + AllowsForcePushes: githubv4.NewBoolean(false), + AllowsDeletions: githubv4.NewBoolean(false), + IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(input.IsAdminEnforced)), + RequiresStatusChecks: githubv4.NewBoolean(true), + RequiredStatusCheckContexts: &statusChecks, + } + + return &createInput, nil + } + + // it's an existing one we need to update and make sure all of them it's at state we want it + mergedStatusChecks := mergeStatusChecks(foundBranchProtectionRule.RequiredStatusCheckContexts, input.RequiredStatusCheckContexts, nil) + var finalStatusChecks []githubv4.String + + for _, check := range mergedStatusChecks { + finalStatusChecks = append(finalStatusChecks, githubv4.String(check)) + } + + updateInput := githubv4.UpdateBranchProtectionRuleInput{ + BranchProtectionRuleID: githubv4.ID(foundBranchProtectionRule.ID), + Pattern: githubv4.NewString(githubv4.String(input.Pattern)), + IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(input.IsAdminEnforced)), + RequiresStatusChecks: githubv4.NewBoolean(true), + AllowsDeletions: githubv4.NewBoolean(false), + AllowsForcePushes: githubv4.NewBoolean(false), + RequiredStatusCheckContexts: &finalStatusChecks, + } + + return nil, &updateInput +} + +//IsEnforceAdminEnabled checks if enforce admin option is enabled for the branch protection +func IsEnforceAdminEnabled(protection *BranchProtectionRule) bool { + return protection.IsAdminEnforced +} + +// CleanGithubRepoName removes the orgname if existing in the string +func CleanGithubRepoName(githubRepoName string) string { + if strings.Contains(githubRepoName, "/") { + parts := strings.Split(githubRepoName, "/") + githubRepoName = parts[len(parts)-1] + } + return githubRepoName +} diff --git a/cla-backend-go/github/branch_protection/protected_branch_limiter.go b/cla-backend-go/github/branch_protection/protected_branch_limiter.go new file mode 100644 index 000000000..7166e9de8 --- /dev/null +++ b/cla-backend-go/github/branch_protection/protected_branch_limiter.go @@ -0,0 +1,114 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package branch_protection + +import ( + "context" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/github" + githubpkg "github.com/google/go-github/v33/github" + "github.com/shurcooL/githubv4" + "go.uber.org/ratelimit" + "golang.org/x/time/rate" +) + +// rate limiting variables +var ( + // blockingRateLimit is useful for background tasks where the interaction is more predictable + blockingRateLimit = ratelimit.New(2) + // nonBlockingRateLimit is preferred when the github methods would be called realtime + // in this case we can call Allow method to check if can proceed or return error + nonBlockingRateLimit = rate.NewLimiter(2, 5) +) + +type blockingRateLimitRepositories struct { + CombinedRepository +} + +// NewBlockLimiterRepositories returns a new instance of V3Repositories interface with blocking rate limiting +// where when the limit is reached the next call blocks till the bucket is ready again +func NewBlockLimiterRepositories(repo CombinedRepository) CombinedRepository { + return blockingRateLimitRepositories{ + CombinedRepository: repo, + } +} + +func (b blockingRateLimitRepositories) ListByOrg(ctx context.Context, org string, opt *githubpkg.RepositoryListByOrgOptions) ([]*githubpkg.Repository, *githubpkg.Response, error) { + blockingRateLimit.Take() + return b.CombinedRepository.ListByOrg(ctx, org, opt) +} + +func (b blockingRateLimitRepositories) Get(ctx context.Context, owner, repo string) (*githubpkg.Repository, *githubpkg.Response, error) { + blockingRateLimit.Take() + return b.CombinedRepository.Get(ctx, owner, repo) +} + +func (b blockingRateLimitRepositories) GetRepositoryBranchProtections(ctx context.Context, repositoryOwner, repositoryName string) (*RepoBranchProtectionQueryResult, error) { + blockingRateLimit.Take() + return b.CombinedRepository.GetRepositoryBranchProtections(ctx, repositoryOwner, repositoryName) +} +func (b blockingRateLimitRepositories) CreateBranchProtection(ctx context.Context, input *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) { + blockingRateLimit.Take() + return b.CombinedRepository.CreateBranchProtection(ctx, input) +} +func (b blockingRateLimitRepositories) UpdateBranchProtection(ctx context.Context, input *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) { + blockingRateLimit.Take() + return b.CombinedRepository.UpdateBranchProtection(ctx, input) +} +func (b blockingRateLimitRepositories) GetRepositoryIDFromName(ctx context.Context, repositoryOwner, repositoryName string) (string, error) { + blockingRateLimit.Take() + return b.CombinedRepository.GetRepositoryIDFromName(ctx, repositoryOwner, repositoryName) +} + +type nonBlockingRateLimitRepositories struct { + CombinedRepository +} + +// NewNonBlockLimiterRepositories returns a new instance of V3Repositories interface with non blocking rate limiting +func NewNonBlockLimiterRepositories(repo CombinedRepository) CombinedRepository { + return nonBlockingRateLimitRepositories{CombinedRepository: repo} +} + +func (nb nonBlockingRateLimitRepositories) ListByOrg(ctx context.Context, org string, opt *githubpkg.RepositoryListByOrgOptions) ([]*githubpkg.Repository, *githubpkg.Response, error) { + if nonBlockingRateLimit.Allow() { + return nb.CombinedRepository.ListByOrg(ctx, org, opt) + } + return nil, nil, fmt.Errorf("too many requests : %w", github.ErrRateLimited) +} + +func (nb nonBlockingRateLimitRepositories) Get(ctx context.Context, owner, repo string) (*githubpkg.Repository, *githubpkg.Response, error) { + if nonBlockingRateLimit.Allow() { + return nb.CombinedRepository.Get(ctx, owner, repo) + } + return nil, nil, fmt.Errorf("too many requests : %w", github.ErrRateLimited) +} + +func (nb nonBlockingRateLimitRepositories) GetRepositoryBranchProtections(ctx context.Context, repositoryOwner, repositoryName string) (*RepoBranchProtectionQueryResult, error) { + if nonBlockingRateLimit.Allow() { + return nb.CombinedRepository.GetRepositoryBranchProtections(ctx, repositoryOwner, repositoryName) + } + return nil, fmt.Errorf("too many requests : %w", github.ErrRateLimited) +} + +func (nb nonBlockingRateLimitRepositories) CreateBranchProtection(ctx context.Context, input *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) { + if nonBlockingRateLimit.Allow() { + return nb.CombinedRepository.CreateBranchProtection(ctx, input) + } + return nil, fmt.Errorf("too many requests : %w", github.ErrRateLimited) +} + +func (nb nonBlockingRateLimitRepositories) UpdateBranchProtection(ctx context.Context, input *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) { + if nonBlockingRateLimit.Allow() { + return nb.CombinedRepository.UpdateBranchProtection(ctx, input) + } + return nil, fmt.Errorf("too many requests : %w", github.ErrRateLimited) +} + +func (nb nonBlockingRateLimitRepositories) GetRepositoryIDFromName(ctx context.Context, repositoryOwner, repositoryName string) (string, error) { + if nonBlockingRateLimit.Allow() { + return nb.CombinedRepository.GetRepositoryIDFromName(ctx, repositoryOwner, repositoryName) + } + return "", fmt.Errorf("too many requests : %w", github.ErrRateLimited) +} diff --git a/cla-backend-go/github/branch_protection/protected_branch_test.go b/cla-backend-go/github/branch_protection/protected_branch_test.go new file mode 100644 index 000000000..96e0ceec4 --- /dev/null +++ b/cla-backend-go/github/branch_protection/protected_branch_test.go @@ -0,0 +1,375 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package branch_protection + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/bmizerany/assert" + "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/golang/mock/gomock" + "github.com/shurcooL/githubv4" +) + +func V4StringSlice(val ...string) *[]githubv4.String { + var v []githubv4.String + for _, c := range val { + v = append(v, githubv4.String(c)) + } + + return &v +} + +// TestMergeStatusChecks tests the functionality of where we enable/disable checks +func TestMergeStatusChecks(t *testing.T) { + + testCases := []struct { + Name string + currentChecks []string + expectedChecks []string + enableContexts []string + disableContexts []string + }{ + { + Name: "all empty", + expectedChecks: []string{}, + }, + { + Name: "empty state enable", + expectedChecks: []string{"EasyCLA"}, + enableContexts: []string{"EasyCLA"}, + }, + { + Name: "preserve existing enable more", + currentChecks: []string{"travis-ci"}, + expectedChecks: []string{"travis-ci", "EasyCLA"}, + enableContexts: []string{"EasyCLA"}, + }, + { + Name: "preserve existing disable some", + currentChecks: []string{"travis-ci", "EasyCLA"}, + expectedChecks: []string{"travis-ci"}, + disableContexts: []string{"EasyCLA"}, + }, + { + Name: "add and remove in same operation", + currentChecks: []string{"travis-ci", "DCO", "EasyCLA"}, + expectedChecks: []string{"travis-ci", "EasyCLA", "CodeQL"}, + enableContexts: []string{"CodeQL"}, + disableContexts: []string{"DCO"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(tt *testing.T) { + result := mergeStatusChecks(tc.currentChecks, tc.enableContexts, tc.disableContexts) + assert.Equal(tt, tc.expectedChecks, result) + }) + } +} + +func TestEnableBranchProtection(t *testing.T) { + owner := "johnenable" + repo := "johnsrepoenable" + branchName := DefaultBranchName + + testCases := []struct { + Name string + Checks []string + CurrentProtections *RepoBranchProtectionQueryResult + CreateProtectionRequest *githubv4.CreateBranchProtectionRuleInput + UpdateProtectionRequest *githubv4.UpdateBranchProtectionRuleInput + Err error + }{ + { + Name: "success", + Checks: []string{"easyCLA"}, + CurrentProtections: &RepoBranchProtectionQueryResult{ + RepositoryOwner: struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + }{Repository: BranchProtectionRuleRepositoryParam{ + Name: "repoNameValue", + ID: "repoIDValue", + BranchProtectionRules: BranchProtectionRuleQueryParam{}, + }}, + }, + CreateProtectionRequest: &githubv4.CreateBranchProtectionRuleInput{ + RepositoryID: githubv4.ID("repoIDValue"), + Pattern: githubv4.String(branchName), + AllowsForcePushes: githubv4.NewBoolean(false), + AllowsDeletions: githubv4.NewBoolean(false), + IsAdminEnforced: githubv4.NewBoolean(true), + RequiresStatusChecks: githubv4.NewBoolean(true), + RequiredStatusCheckContexts: V4StringSlice("easyCLA"), + }, + }, + { + Name: "preserve existing checks", + Checks: []string{"easyCLA"}, + CurrentProtections: &RepoBranchProtectionQueryResult{ + RepositoryOwner: struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + }{Repository: BranchProtectionRuleRepositoryParam{ + Name: "repoNameValue", + ID: "repoIDValue", + BranchProtectionRules: BranchProtectionRuleQueryParam{ + TotalCount: 1, + Nodes: []BranchProtectionRule{ + { + ID: "branchProtectionID", + Pattern: branchName, + RequiredStatusCheckContexts: []string{ + "circle/ci", + }, + }, + }, + }, + }}, + }, + UpdateProtectionRequest: &githubv4.UpdateBranchProtectionRuleInput{ + BranchProtectionRuleID: githubv4.ID("branchProtectionID"), + Pattern: githubv4.NewString(githubv4.String(branchName)), + AllowsForcePushes: githubv4.NewBoolean(false), + AllowsDeletions: githubv4.NewBoolean(false), + IsAdminEnforced: githubv4.NewBoolean(true), + RequiresStatusChecks: githubv4.NewBoolean(true), + RequiredStatusCheckContexts: V4StringSlice("circle/ci", "easyCLA"), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(tt *testing.T) { + ctrl := gomock.NewController(tt) + defer ctrl.Finish() + + m := NewMockCombinedRepository(ctrl) + m. + EXPECT(). + GetRepositoryBranchProtections(gomock.Any(), owner, repo). + Return(tc.CurrentProtections, nil) + + if tc.CreateProtectionRequest != nil { + m. + EXPECT(). + CreateBranchProtection(gomock.Any(), tc.CreateProtectionRequest). + Return(nil, nil) + } + + if tc.UpdateProtectionRequest != nil { + m. + EXPECT(). + UpdateBranchProtection(gomock.Any(), tc.UpdateProtectionRequest). + Return(nil, nil) + } + + branchProtectionRepo := newBranchProtectionRepository(m) + err := branchProtectionRepo.EnableBranchProtection(context.Background(), owner, repo, branchName, true, tc.Checks, nil) + if err != nil { + tt.Errorf("enable branch proteciton failed : %v", err) + } + }) + } +} + +func TestNonBlockingRateLimitRepositories_GetBranchProtection(t *testing.T) { + owner := "johnblocking" + repo := "johnsrepoblocking" + branchName := DefaultBranchName + + t.Run("no limit reached", func(tt *testing.T) { + ctrl := gomock.NewController(tt) + defer ctrl.Finish() + + protection := &RepoBranchProtectionQueryResult{ + RepositoryOwner: struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + }{Repository: BranchProtectionRuleRepositoryParam{ + Name: "repoNameValue", + ID: "repoIDValue", + BranchProtectionRules: BranchProtectionRuleQueryParam{ + TotalCount: 1, + Nodes: []BranchProtectionRule{ + { + ID: "branchProtectionID", + Pattern: branchName, + RequiredStatusCheckContexts: []string{ + "circle/ci", + }, + }, + }, + }, + }}, + } + + m := NewMockCombinedRepository(ctrl) + m. + EXPECT(). + GetRepositoryBranchProtections(gomock.Any(), owner, repo). + Return(protection, nil) + + nonBlockLimitRepo := newBranchProtectionRepository(m, EnableNonBlockingLimiter()) + p, err := nonBlockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) + if err != nil { + tt.Errorf("no error expected : %v", err) + } + assert.Equal(tt, protection.RepositoryOwner.Repository.BranchProtectionRules.Nodes[0], *p) + }) + + t.Run("limit reached", func(tt *testing.T) { + ctrl := gomock.NewController(tt) + defer ctrl.Finish() + + protection := &RepoBranchProtectionQueryResult{ + RepositoryOwner: struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + }{Repository: BranchProtectionRuleRepositoryParam{ + Name: "repoNameValue", + ID: "repoIDValue", + BranchProtectionRules: BranchProtectionRuleQueryParam{ + TotalCount: 1, + Nodes: []BranchProtectionRule{ + { + ID: "branchProtectionID", + Pattern: branchName, + RequiredStatusCheckContexts: []string{ + "circle/ci", + }, + }, + }, + }, + }}, + } + + m := NewMockCombinedRepository(ctrl) + m. + EXPECT(). + GetRepositoryBranchProtections(gomock.Any(), owner, repo). + Return(protection, nil).AnyTimes() + + nonBlockLimitRepo := newBranchProtectionRepository(m, EnableNonBlockingLimiter()) + // call it 100 times in loop to make it fail + var expectedErr error + for i := 0; i < 100; i++ { + _, err := nonBlockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) + if err != nil { + expectedErr = err + break + } + } + + if expectedErr == nil { + tt.Fatalf("no error returned") + return + } + + if !errors.Is(expectedErr, github.ErrRateLimited) { + tt.Fatalf("was expecting ErrRateLimited got : %v", expectedErr) + return + } + }) +} + +func TestBlockingRateLimitRepositories_GetBranchProtection(t *testing.T) { + owner := "john" + repo := "johnsrepo" + branchName := DefaultBranchName + + t.Run("no limit reached", func(tt *testing.T) { + ctrl := gomock.NewController(tt) + defer ctrl.Finish() + + protection := &RepoBranchProtectionQueryResult{ + RepositoryOwner: struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + }{Repository: BranchProtectionRuleRepositoryParam{ + Name: "repoNameValue", + ID: "repoIDValue", + BranchProtectionRules: BranchProtectionRuleQueryParam{ + TotalCount: 1, + Nodes: []BranchProtectionRule{ + { + ID: "branchProtectionID", + Pattern: branchName, + RequiredStatusCheckContexts: []string{ + "circle/ci", + }, + }, + }, + }, + }}, + } + + m := NewMockCombinedRepository(ctrl) + m. + EXPECT(). + GetRepositoryBranchProtections(gomock.Any(), owner, repo). + Return(protection, nil) + + blockLimitRepo := newBranchProtectionRepository(m, EnableBlockingLimiter()) + p, err := blockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) + if err != nil { + tt.Errorf("no error expected : %v", err) + } + assert.Equal(tt, protection.RepositoryOwner.Repository.BranchProtectionRules.Nodes[0], *p) + }) + + t.Run("limit reached", func(tt *testing.T) { + ctrl := gomock.NewController(tt) + defer ctrl.Finish() + + protection := &RepoBranchProtectionQueryResult{ + RepositoryOwner: struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + }{Repository: BranchProtectionRuleRepositoryParam{ + Name: "repoNameValue", + ID: "repoIDValue", + BranchProtectionRules: BranchProtectionRuleQueryParam{ + TotalCount: 1, + Nodes: []BranchProtectionRule{ + { + ID: "branchProtectionID", + Pattern: branchName, + RequiredStatusCheckContexts: []string{ + "circle/ci", + }, + }, + }, + }, + }}, + } + + m := NewMockCombinedRepository(ctrl) + m. + EXPECT(). + GetRepositoryBranchProtections(gomock.Any(), owner, repo). + Return(protection, nil).AnyTimes() + + blockLimitRepo := newBranchProtectionRepository(m, EnableBlockingLimiter()) + + // call it 100 times in loop to make it fail + var expectedErr error + start := time.Now() + for i := 0; i < 10; i++ { + _, err := blockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) + if err != nil { + expectedErr = err + break + } + } + elapsed := time.Since(start) + + if expectedErr != nil { + tt.Fatalf("no error was expected got : %v", expectedErr) + return + } + + if elapsed < 4*time.Second { + tt.Fatalf("is rate limit enabled") + } + }) +} diff --git a/cla-backend-go/github/branch_protection/protected_branch_v4.go b/cla-backend-go/github/branch_protection/protected_branch_v4.go new file mode 100644 index 000000000..20ce3ca71 --- /dev/null +++ b/cla-backend-go/github/branch_protection/protected_branch_v4.go @@ -0,0 +1,160 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package branch_protection + +import ( + "context" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/shurcooL/githubv4" +) + +// BranchProtectionRule is the data structure that's used to reflect the remote github branch protection rule +type BranchProtectionRule struct { + ID string + Pattern string + RequiredStatusCheckContexts []string + RequiresStatusChecks bool + IsAdminEnforced bool + AllowsDeletions bool + AllowsForcePushes bool +} + +// BranchProtectionRuleQueryParam is part of RepoBranchProtectionQueryResult extracted here so can +// easily be initialized +type BranchProtectionRuleQueryParam struct { + TotalCount int + Nodes []BranchProtectionRule +} + +// BranchProtectionRuleRepositoryParam is part of RepoBranchProtectionQueryResult extracted here so can +// easily be initialized +type BranchProtectionRuleRepositoryParam struct { + Name string + ID string + BranchProtectionRules BranchProtectionRuleQueryParam `graphql:"branchProtectionRules(first:10)"` +} + +// RepoBranchProtectionQueryResult is the query which queries for given owner and repository name +type RepoBranchProtectionQueryResult struct { + RepositoryOwner struct { + Repository BranchProtectionRuleRepositoryParam `graphql:"repository(name: $name)"` + } `graphql:"repositoryOwner(login: $login)"` +} + +// CreateRepoBranchProtectionMutation adds a new branch protection rule +type CreateRepoBranchProtectionMutation struct { + CreateBranchProtectionRule struct { + BranchProtectionRule struct { + Repository struct { + Name string + } + Pattern string + IsAdminEnforced bool + RequiresStatusChecks bool + AllowsDeletions bool + AllowsForcePushes bool + } + } `graphql:"createBranchProtectionRule(input: $input)"` +} + +// UpdateRepoBranchProtectionMutation updates existing branch protection rule +type UpdateRepoBranchProtectionMutation struct { + UpdateBranchProtectionRule struct { + BranchProtectionRule struct { + Repository struct { + Name string + } + Pattern string + IsAdminEnforced bool + RequiresStatusChecks bool + AllowsDeletions bool + AllowsForcePushes bool + } + } `graphql:"updateBranchProtectionRule(input: $input)"` +} + +type branchProtectionRepositoryV4 struct { + client *githubv4.Client +} + +// NewBranchProtectionRepositoryV4 creates a new branchProtectionRepositoryV4 +func NewBranchProtectionRepositoryV4(installationID int64) (*branchProtectionRepositoryV4, error) { + client, clientErr := github.NewGithubV4AppClient(installationID) + if clientErr != nil { + return nil, clientErr + } + return &branchProtectionRepositoryV4{ + client: client, + }, nil +} + +func (r *branchProtectionRepositoryV4) GetRepositoryBranchProtections(ctx context.Context, repositoryOwner, repositoryName string) (*RepoBranchProtectionQueryResult, error) { + var queryResult RepoBranchProtectionQueryResult + + variables := map[string]interface{}{ + "login": githubv4.String(repositoryOwner), + "name": githubv4.String(repositoryName), + } + + err := r.client.Query(ctx, &queryResult, variables) + if err != nil { + return nil, fmt.Errorf("fetching branch protection rules for owner : %s and repo : %s failed : %v", repositoryOwner, repositoryName, err) + } + + return &queryResult, nil +} + +func (r *branchProtectionRepositoryV4) CreateBranchProtection(ctx context.Context, input *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) { + var createMutationResult CreateRepoBranchProtectionMutation + err := r.client.Mutate(ctx, &createMutationResult, *input, nil) + if err != nil { + return nil, fmt.Errorf("creating new branch protection failed : %w", err) + } + return &createMutationResult, nil +} + +func (r *branchProtectionRepositoryV4) UpdateBranchProtection(ctx context.Context, input *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) { + var updateMutationResult UpdateRepoBranchProtectionMutation + err := r.client.Mutate(ctx, &updateMutationResult, *input, nil) + if err != nil { + return nil, fmt.Errorf("updating current branch rule failed : %w", err) + } + return &updateMutationResult, nil +} + +// GetRepositoryIDFromName when provided the organization and repository name, returns the repository ID +func (r *branchProtectionRepositoryV4) GetRepositoryIDFromName(ctx context.Context, repositoryOwner, repositoryName string) (string, error) { + + // Define the graphql query + //"query": "query{repository(name: \"test1\", owner: \"deal-test-org\") {id}}" + var query struct { + Viewer struct { + Login githubv4.String + } + Repository struct { + ID string + } `graphql:"repository(owner:$repositoryOwner, name:$repositoryName)"` + } + + // Define the variables for the query + variables := map[string]interface{}{ + "repositoryOwner": githubv4.String(repositoryOwner), + "repositoryName": githubv4.String(repositoryName), + } + + err := r.client.Query(ctx, &query, variables) + if err != nil { + return "", err + } + + return query.Repository.ID, nil +} + +// EnableBranchProtectionForPattern enables branch protection for the given branch protection input +func EnableBranchProtectionForPattern(ctx context.Context, repositoryOwner, repositoryName string, input *BranchProtectionRule) error { + + return nil +} diff --git a/cla-backend-go/github/client.go b/cla-backend-go/github/client.go index 7f526b7a7..84070b97e 100644 --- a/cla-backend-go/github/client.go +++ b/cla-backend-go/github/client.go @@ -56,7 +56,8 @@ func isGithubRateLimit(err error) (bool, error) { return false, nil } -func checkAndWrapForKnownErrors(resp *github.Response, err error) (bool, error) { +// CheckAndWrapForKnownErrors checks for some of the known error types +func CheckAndWrapForKnownErrors(resp *github.Response, err error) (bool, error) { if err == nil { return false, err } diff --git a/cla-backend-go/github/mock.go b/cla-backend-go/github/mock.go deleted file mode 100644 index 63a447c33..000000000 --- a/cla-backend-go/github/mock.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -// Code generated by MockGen. DO NOT EDIT. -// Source: protected_branch.go - -// Package github is a generated GoMock package. -package github - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - github "github.com/google/go-github/v33/github" -) - -// MockRepositories is a mock of Repositories interface -type MockRepositories struct { - ctrl *gomock.Controller - recorder *MockRepositoriesMockRecorder -} - -// MockRepositoriesMockRecorder is the mock recorder for MockRepositories -type MockRepositoriesMockRecorder struct { - mock *MockRepositories -} - -// NewMockRepositories creates a new mock instance -func NewMockRepositories(ctrl *gomock.Controller) *MockRepositories { - mock := &MockRepositories{ctrl: ctrl} - mock.recorder = &MockRepositoriesMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockRepositories) EXPECT() *MockRepositoriesMockRecorder { - return m.recorder -} - -// ListByOrg mocks base method -func (m *MockRepositories) ListByOrg(ctx context.Context, org string, opt *github.RepositoryListByOrgOptions) ([]*github.Repository, *github.Response, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListByOrg", ctx, org, opt) - ret0, _ := ret[0].([]*github.Repository) - ret1, _ := ret[1].(*github.Response) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// ListByOrg indicates an expected call of ListByOrg -func (mr *MockRepositoriesMockRecorder) ListByOrg(ctx, org, opt interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByOrg", reflect.TypeOf((*MockRepositories)(nil).ListByOrg), ctx, org, opt) -} - -// Get mocks base method -func (m *MockRepositories) Get(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", ctx, owner, repo) - ret0, _ := ret[0].(*github.Repository) - ret1, _ := ret[1].(*github.Response) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Get indicates an expected call of Get -func (mr *MockRepositoriesMockRecorder) Get(ctx, owner, repo interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepositories)(nil).Get), ctx, owner, repo) -} - -// GetBranchProtection mocks base method -func (m *MockRepositories) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*github.Protection, *github.Response, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBranchProtection", ctx, owner, repo, branch) - ret0, _ := ret[0].(*github.Protection) - ret1, _ := ret[1].(*github.Response) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetBranchProtection indicates an expected call of GetBranchProtection -func (mr *MockRepositoriesMockRecorder) GetBranchProtection(ctx, owner, repo, branch interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchProtection", reflect.TypeOf((*MockRepositories)(nil).GetBranchProtection), ctx, owner, repo, branch) -} - -// UpdateBranchProtection mocks base method -func (m *MockRepositories) UpdateBranchProtection(ctx context.Context, owner, repo, branch string, preq *github.ProtectionRequest) (*github.Protection, *github.Response, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateBranchProtection", ctx, owner, repo, branch, preq) - ret0, _ := ret[0].(*github.Protection) - ret1, _ := ret[1].(*github.Response) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// UpdateBranchProtection indicates an expected call of UpdateBranchProtection -func (mr *MockRepositoriesMockRecorder) UpdateBranchProtection(ctx, owner, repo, branch, preq interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBranchProtection", reflect.TypeOf((*MockRepositories)(nil).UpdateBranchProtection), ctx, owner, repo, branch, preq) -} diff --git a/cla-backend-go/github/protected_branch.go b/cla-backend-go/github/protected_branch.go deleted file mode 100644 index 1f449d14a..000000000 --- a/cla-backend-go/github/protected_branch.go +++ /dev/null @@ -1,655 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package github - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/go-openapi/swag" - "github.com/sirupsen/logrus" - - "github.com/jinzhu/copier" - - log "github.com/communitybridge/easycla/cla-backend-go/logging" - - githubpkg "github.com/google/go-github/v33/github" - "github.com/shurcooL/githubv4" - "go.uber.org/ratelimit" - "golang.org/x/time/rate" -) - -const ( - defaultBranchName = "master" -) - -var ( - // ErrBranchNotProtected indicates the situation where the branch is not enabled for protection on github side - ErrBranchNotProtected = errors.New("not protected") -) - -// rate limiting variables -var ( - // blockingRateLimit is useful for background tasks where the interaction is more predictable - blockingRateLimit = ratelimit.New(2) - // nonBlockingRateLimit is preferred when the github methods would be called realtime - // in this case we can call Allow method to check if can proceed or return error - nonBlockingRateLimit = rate.NewLimiter(2, 5) -) - -// Repositories is part of the interface working with github repositories, it's inside of the github client -// It's extracted here as interface so we can mock that functionality. -type Repositories interface { - ListByOrg(ctx context.Context, org string, opt *githubpkg.RepositoryListByOrgOptions) ([]*githubpkg.Repository, *githubpkg.Response, error) - Get(ctx context.Context, owner, repo string) (*githubpkg.Repository, *githubpkg.Response, error) - GetBranchProtection(ctx context.Context, owner, repo, branch string) (*githubpkg.Protection, *githubpkg.Response, error) - UpdateBranchProtection(ctx context.Context, owner, repo, branch string, preq *githubpkg.ProtectionRequest) (*githubpkg.Protection, *githubpkg.Response, error) -} - -type blockingRateLimitRepositories struct { - Repositories -} - -// NewBlockLimiterRepositories returns a new instance of Repositories interface with blocking rate limiting -// where when the limit is reached the next call blocks till the bucket is ready again -func NewBlockLimiterRepositories(repos Repositories) Repositories { - return blockingRateLimitRepositories{repos} -} - -func (b blockingRateLimitRepositories) ListByOrg(ctx context.Context, org string, opt *githubpkg.RepositoryListByOrgOptions) ([]*githubpkg.Repository, *githubpkg.Response, error) { - blockingRateLimit.Take() - return b.Repositories.ListByOrg(ctx, org, opt) -} - -func (b blockingRateLimitRepositories) Get(ctx context.Context, owner, repo string) (*githubpkg.Repository, *githubpkg.Response, error) { - blockingRateLimit.Take() - return b.Repositories.Get(ctx, owner, repo) -} - -func (b blockingRateLimitRepositories) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*githubpkg.Protection, *githubpkg.Response, error) { - blockingRateLimit.Take() - return b.Repositories.GetBranchProtection(ctx, owner, repo, branch) -} - -func (b blockingRateLimitRepositories) UpdateBranchProtection(ctx context.Context, owner, repo, branch string, preq *githubpkg.ProtectionRequest) (*githubpkg.Protection, *githubpkg.Response, error) { - blockingRateLimit.Take() - return b.Repositories.UpdateBranchProtection(ctx, owner, repo, branch, preq) -} - -type nonBlockingRateLimitRepositories struct { - Repositories -} - -// NewNonBlockLimiterRepositories returns a new instance of Repositories interface with non blocking rate limiting -func NewNonBlockLimiterRepositories(repos Repositories) Repositories { - return nonBlockingRateLimitRepositories{repos} -} - -func (nb nonBlockingRateLimitRepositories) ListByOrg(ctx context.Context, org string, opt *githubpkg.RepositoryListByOrgOptions) ([]*githubpkg.Repository, *githubpkg.Response, error) { - if nonBlockingRateLimit.Allow() { - return nb.Repositories.ListByOrg(ctx, org, opt) - } - return nil, nil, fmt.Errorf("too many requests : %w", ErrRateLimited) -} - -func (nb nonBlockingRateLimitRepositories) Get(ctx context.Context, owner, repo string) (*githubpkg.Repository, *githubpkg.Response, error) { - if nonBlockingRateLimit.Allow() { - return nb.Repositories.Get(ctx, owner, repo) - } - return nil, nil, fmt.Errorf("too many requests : %w", ErrRateLimited) -} - -func (nb nonBlockingRateLimitRepositories) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*githubpkg.Protection, *githubpkg.Response, error) { - if nonBlockingRateLimit.Allow() { - return nb.Repositories.GetBranchProtection(ctx, owner, repo, branch) - } - return nil, nil, fmt.Errorf("too many requests : %w", ErrRateLimited) -} - -func (nb nonBlockingRateLimitRepositories) UpdateBranchProtection(ctx context.Context, owner, repo, branch string, preq *githubpkg.ProtectionRequest) (*githubpkg.Protection, *githubpkg.Response, error) { - if nonBlockingRateLimit.Allow() { - return nb.Repositories.UpdateBranchProtection(ctx, owner, repo, branch, preq) - } - return nil, nil, fmt.Errorf("too many requests : %w", ErrRateLimited) -} - -type branchProtectionRepositoryConfig struct { - enableBlockingLimiter bool - enableNonBlockingLimiter bool -} - -// BranchProtectionRepositoryOption enables optional parameters to BranchProtectionRepository -type BranchProtectionRepositoryOption func(config *branchProtectionRepositoryConfig) - -// EnableBlockingLimiter enables the blocking limiter -func EnableBlockingLimiter() BranchProtectionRepositoryOption { - return func(config *branchProtectionRepositoryConfig) { - config.enableBlockingLimiter = true - } -} - -// EnableNonBlockingLimiter enables the non-blocking limiter -func EnableNonBlockingLimiter() BranchProtectionRepositoryOption { - return func(config *branchProtectionRepositoryConfig) { - config.enableNonBlockingLimiter = true - } -} - -// BranchProtectionRepository contains helper methods interacting with github api related to branch protection -type BranchProtectionRepository struct { - githubRepo Repositories -} - -// NewBranchProtectionRepository creates a new BranchProtectionRepository -func NewBranchProtectionRepository(githubRepo Repositories, opts ...BranchProtectionRepositoryOption) *BranchProtectionRepository { - config := &branchProtectionRepositoryConfig{} - for _, o := range opts { - o(config) - } - - if config.enableNonBlockingLimiter { - githubRepo = NewNonBlockLimiterRepositories(githubRepo) - } else if config.enableBlockingLimiter { - githubRepo = NewBlockLimiterRepositories(githubRepo) - } - - return &BranchProtectionRepository{ - githubRepo: githubRepo, - } -} - -// GetOwnerName retrieves the owner name of the given org and repo name -func (bp *BranchProtectionRepository) GetOwnerName(ctx context.Context, orgName, repoName string) (string, error) { - repoName = CleanGithubRepoName(repoName) - log.Debugf("GetOwnerName : getting owner name for org %s and repoName : %s", orgName, repoName) - listOpt := &githubpkg.RepositoryListByOrgOptions{ - ListOptions: githubpkg.ListOptions{ - PerPage: 30, - }, - } - for { - repos, resp, err := bp.githubRepo.ListByOrg(ctx, orgName, listOpt) - if err != nil { - if ok, wErr := checkAndWrapForKnownErrors(resp, err); ok { - return "", wErr - } - return "", err - } - - if len(repos) == 0 { - log.Warnf("GetOwnerName : no repos found under orgName : %s (maybe no access ?)", orgName) - return "", nil - } - - for _, repo := range repos { - if *repo.Name == repoName { - if repo.Owner != nil { - owner := *repo.Owner - return *owner.Login, nil - } - } - } - - // means we're at the end of it so exit - if resp.NextPage == 0 { - log.Warnf("GetOwnerName : owner name not found for org : %s and repo : %s", orgName, repoName) - return "", nil - } - - // set it to the next page - listOpt.Page = resp.NextPage - } -} - -// GetDefaultBranchForRepo helps with pulling the default branch for the given repo -func (bp *BranchProtectionRepository) GetDefaultBranchForRepo(ctx context.Context, owner, repoName string) (string, error) { - repoName = CleanGithubRepoName(repoName) - repo, resp, err := bp.githubRepo.Get(ctx, owner, repoName) - if err != nil { - if ok, wErr := checkAndWrapForKnownErrors(resp, err); ok { - return "", wErr - } - return "", err - } - - var defaultBranch string - if repo.DefaultBranch == nil { - defaultBranch = defaultBranchName - } else { - defaultBranch = *repo.DefaultBranch - } - - return defaultBranch, nil -} - -// GetProtectedBranch fetches the protected branch details -func (bp *BranchProtectionRepository) GetProtectedBranch(ctx context.Context, owner, repoName, protectedBranchName string) (*githubpkg.Protection, error) { - repoName = CleanGithubRepoName(repoName) - protection, resp, err := bp.githubRepo.GetBranchProtection(ctx, owner, repoName, protectedBranchName) - - if err != nil { - if ok, wErr := checkAndWrapForKnownErrors(resp, err); ok { - return nil, wErr - } - if resp != nil && resp.StatusCode == 404 { - if gErr, ok := err.(*githubpkg.ErrorResponse); ok { - if strings.Contains(strings.ToLower(gErr.Message), "not protected") { - return nil, ErrBranchNotProtected - } - } - } - - return nil, fmt.Errorf("fetching branch proteciton : %w", err) - } - return protection, err -} - -//EnableBranchProtection enables branch protection if not enabled and makes sure passed arguments such as enforceAdmin -//statusChecks are applied. The operation makes sure it doesn't override the existing checks. -func (bp *BranchProtectionRepository) EnableBranchProtection(ctx context.Context, owner, repoName, branchName string, enforceAdmin bool, enableStatusChecks, disableStatusChecks []string) error { - repoName = CleanGithubRepoName(repoName) - protectedBranch, err := bp.GetProtectedBranch(ctx, owner, repoName, branchName) - if err != nil && !errors.Is(err, ErrBranchNotProtected) { - return fmt.Errorf("fetching the protected branch for repo : %s : %w", repoName, err) - } - - branchProtectionRequest, err := createBranchProtectionRequest(protectedBranch, enableStatusChecks, disableStatusChecks, enforceAdmin) - if err != nil { - return fmt.Errorf("creating branch protection request failed : %v", err) - } - - _, resp, err := bp.githubRepo.UpdateBranchProtection(ctx, owner, repoName, branchName, branchProtectionRequest) - - if ok, wErr := checkAndWrapForKnownErrors(resp, err); ok { - return wErr - } - return err -} - -// GetRepositoryIDFromName when provided the organization and repository name, returns the repository ID -func GetRepositoryIDFromName(ctx context.Context, installationID int64, repositoryOwner, repositoryName string) (string, error) { - f := logrus.Fields{ - "functionName": "github.GetRepositoryIDFromName", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "installationID": installationID, - "repositoryOwner": repositoryOwner, - "repositoryName": repositoryName, - } - - log.WithFields(f).Debugf("loading GitHub v4 client using installation ID: %d", installationID) - client, clientErr := NewGithubV4AppClient(installationID) - if clientErr != nil { - log.WithFields(f).WithError(clientErr).Warnf("problem creating GitHub v4 API client with installation ID: %d", installationID) - return "", clientErr - } - log.WithFields(f).Debugf("loaded GitHub v4 client using installation ID: %d", installationID) - - // Define the graphql query - //"query": "query{repository(name: \"test1\", owner: \"deal-test-org\") {id}}" - var query struct { - Viewer struct { - Login githubv4.String - } - Repository struct { - ID string - } `graphql:"repository(owner:$repositoryOwner, name:$repositoryName)"` - } - - // Define the variables for the query - variables := map[string]interface{}{ - "repositoryOwner": githubv4.String(repositoryOwner), - "repositoryName": githubv4.String(repositoryName), - } - - log.WithFields(f).Debug("executing the query...") - err := client.Query(ctx, &query, variables) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem executing GitHub v4 query using: %+v with variables: %+v", - query, variables) - return "", err - } - - log.WithFields(f).Debugf("User %s looked up repository ID: %s wth installation ID: %d using repository name: %s", - query.Viewer.Login, query.Repository.ID, installationID, repositoryName) - return query.Repository.ID, nil -} - -// GetRepositoryBranchProtection when provided the organization and repository name, returns the repository branch protection rules/info -func GetRepositoryBranchProtection(ctx context.Context, installationID int64, repositoryOwner, repositoryName string) error { - f := logrus.Fields{ - "functionName": "github.GetRepositoryBranchProtection", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "installationID": installationID, - "repositoryOwner": repositoryOwner, - "repositoryName": repositoryName, - } - - // NOTE: This function is not complete - does not return the values as we are still evaluating/testing this API - - log.WithFields(f).Debugf("loading GitHub v4 client using installation ID: %d", installationID) - client, clientErr := NewGithubV4AppClient(installationID) - if clientErr != nil { - log.WithFields(f).WithError(clientErr).Warnf("problem creating GitHub v4 API client with installation ID: %d", installationID) - return clientErr - } - log.WithFields(f).Debugf("loaded GitHub v4 client using installation ID: %d", installationID) - - // Define the graphql query - /* - query { - repository(owner: "lee-dohm", name: "test-repo") { - branchProtectionRules(first: 10) { - nodes { - pattern - } - } - } - } - */ - var query struct { - Viewer struct { - Login githubv4.String - } - //Repository struct { - // BranchProtectionRepositoryOption struct { - // } - //} `graphql:"repository(owner:$repositoryOwner, name:$repositoryName)"` - } - - // Define the variables for the query - variables := map[string]interface{}{ - "repositoryOwner": githubv4.String(repositoryOwner), - "repositoryName": githubv4.String(repositoryName), - } - - log.WithFields(f).Debug("executing the query...") - err := client.Query(ctx, &query, variables) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem executing GitHub v4 query using: %+v with variables: %+v", - query, variables) - return err - } - - // NOTE: still need to implement logic above - return nil -} - -// EnableBranchProtectionForAll sets the branch protection for all branches for the specified repository -func EnableBranchProtectionForAll(ctx context.Context, installationID int64, repositoryOwner, repositoryName string, enforceAdmin bool, enableStatusChecks, disableStatusChecks []string) error { - f := logrus.Fields{ - "functionName": "github.EnableBranchProtectionForAll", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "installationID": installationID, - "repositoryOwner": repositoryOwner, - "repositoryName": repositoryName, - "enforceAdmin": enforceAdmin, - "enableStatusChecks": strings.Join(enableStatusChecks, ","), - "disableStatusChecks": strings.Join(disableStatusChecks, ","), - } - - log.WithFields(f).Debugf("loading GitHub v4 client using installation ID: %d", installationID) - client, clientErr := NewGithubV4AppClient(installationID) - if clientErr != nil { - log.WithFields(f).WithError(clientErr).Warnf("problem creating GitHub v4 API client with installation ID: %d", installationID) - return clientErr - } - log.WithFields(f).Debugf("loaded GitHub v4 client using installation ID: %d", installationID) - - // Define the graphql mutation/update - // This is a sample, not implemented yet - var mutation struct { - AddReaction struct { - Reaction struct { - Content githubv4.ReactionContent - } - Subject struct { - ID githubv4.ID - } - } `graphql:"addReaction(input: $input)"` - Repository struct { - ID string - } `graphql:"repository(repositoryOwner:$repositoryOwner, name:$repositoryName)"` - } - - // Lookup the unique repository ID from the organization and repository name - repositoryID, lookupErr := GetRepositoryIDFromName(ctx, installationID, repositoryOwner, repositoryName) - if lookupErr != nil { - log.WithFields(f).WithError(lookupErr).Warnf("problem loading repository ID from repository owner and repository name values using installation ID: %d", installationID) - return lookupErr - } - - input := githubv4.CreateBranchProtectionRuleInput{ - RepositoryID: repositoryID, - Pattern: "**/**", - RequiresApprovingReviews: nil, - RequiredApprovingReviewCount: nil, - RequiresCommitSignatures: nil, - RequiresLinearHistory: nil, - AllowsForcePushes: githubv4.NewBoolean(false), - AllowsDeletions: nil, - IsAdminEnforced: githubv4.NewBoolean(githubv4.Boolean(enforceAdmin)), - RequiresStatusChecks: githubv4.NewBoolean(len(enableStatusChecks) > 0), - RequiresStrictStatusChecks: nil, - RequiresCodeOwnerReviews: nil, - DismissesStaleReviews: nil, - RestrictsReviewDismissals: nil, - ReviewDismissalActorIDs: nil, - RestrictsPushes: nil, - PushActorIDs: nil, - RequiredStatusCheckContexts: nil, - ClientMutationID: nil, - } - - // Define the variables for the query - variables := map[string]interface{}{ - "repositoryOwner": githubv4.String(repositoryOwner), - "repositoryName": githubv4.String(repositoryName), - } - - return client.Mutate(ctx, &mutation, input, variables) -} - -// createBranchProtectionRequest creates a branch protection request from existing protection -func createBranchProtectionRequest(protection *githubpkg.Protection, enableStatusChecks, disableStatusChecks []string, enforceAdmin bool) (*githubpkg.ProtectionRequest, error) { - var currentChecks *githubpkg.RequiredStatusChecks - if protection != nil { - currentChecks = protection.RequiredStatusChecks - } - requiredStatusChecks := mergeStatusChecks(currentChecks, enableStatusChecks, disableStatusChecks) - - branchProtectionRequest := &githubpkg.ProtectionRequest{ - RequiredStatusChecks: requiredStatusChecks, - EnforceAdmins: enforceAdmin, - } - - // don't have to check further in this case - if protection == nil { - return branchProtectionRequest, nil - } - - if protection.RequireLinearHistory != nil { - branchProtectionRequest.RequireLinearHistory = swag.Bool(protection.RequireLinearHistory.Enabled) - } - - if protection.AllowForcePushes != nil { - branchProtectionRequest.AllowForcePushes = swag.Bool(protection.AllowForcePushes.Enabled) - } - - if protection.AllowDeletions != nil { - branchProtectionRequest.AllowDeletions = swag.Bool(protection.AllowDeletions.Enabled) - } - - if protection.RequiredPullRequestReviews != nil { - var pullRequestReviewEnforcement githubpkg.PullRequestReviewsEnforcementRequest - if err := copier.Copy(&pullRequestReviewEnforcement, protection.RequiredPullRequestReviews); err != nil { - return nil, fmt.Errorf("copying from protected branch to request failed : requiredPullRequestReviews : %v", err) - } - - // github is not happy about null arrays, prefers empty arrays ... - //No subschema in "anyOf" matched. - //For 'properties/teams', nil is not an array. - //Not all subschemas of "allOf" matched. - var anyEnabled bool - if len(protection.RequiredPullRequestReviews.DismissalRestrictions.Users) > 0 { - anyEnabled = true - var users []string - for _, user := range protection.RequiredPullRequestReviews.DismissalRestrictions.Users { - users = append(users, *user.Login) - } - if pullRequestReviewEnforcement.DismissalRestrictionsRequest == nil { - pullRequestReviewEnforcement.DismissalRestrictionsRequest = &githubpkg.DismissalRestrictionsRequest{} - } - pullRequestReviewEnforcement.DismissalRestrictionsRequest.Users = &users - } - - if len(protection.RequiredPullRequestReviews.DismissalRestrictions.Teams) > 0 { - anyEnabled = true - var teams []string - for _, team := range protection.RequiredPullRequestReviews.DismissalRestrictions.Teams { - teams = append(teams, *team.Slug) - } - if pullRequestReviewEnforcement.DismissalRestrictionsRequest == nil { - pullRequestReviewEnforcement.DismissalRestrictionsRequest = &githubpkg.DismissalRestrictionsRequest{} - } - pullRequestReviewEnforcement.DismissalRestrictionsRequest.Teams = &teams - } - - if anyEnabled { - if pullRequestReviewEnforcement.DismissalRestrictionsRequest.Users == nil { - pullRequestReviewEnforcement.DismissalRestrictionsRequest.Users = &[]string{} - } - - if pullRequestReviewEnforcement.DismissalRestrictionsRequest.Teams == nil { - pullRequestReviewEnforcement.DismissalRestrictionsRequest.Teams = &[]string{} - } - - } - - branchProtectionRequest.RequiredPullRequestReviews = &pullRequestReviewEnforcement - } - - if protection.Restrictions != nil { - var restrictions githubpkg.BranchRestrictionsRequest - var anyEnabled bool - if len(protection.Restrictions.Users) > 0 { - anyEnabled = true - var users []string - for _, user := range protection.Restrictions.Users { - users = append(users, *user.Login) - } - restrictions.Users = users - } - - if len(protection.Restrictions.Teams) > 0 { - anyEnabled = true - var teams []string - for _, team := range protection.Restrictions.Teams { - teams = append(teams, *team.Slug) - } - restrictions.Teams = teams - } - - if len(protection.Restrictions.Apps) > 0 { - anyEnabled = true - var apps []string - for _, app := range protection.Restrictions.Apps { - apps = append(apps, *app.Slug) - } - restrictions.Apps = apps - } - - // make sure we don't send nil arrays ... - if anyEnabled { - if restrictions.Users == nil { - restrictions.Users = []string{} - } - - if restrictions.Teams == nil { - restrictions.Teams = []string{} - } - - if restrictions.Apps == nil { - restrictions.Apps = []string{} - } - } - - branchProtectionRequest.Restrictions = &restrictions - } - - return branchProtectionRequest, nil -} - -//mergeStatusChecks merges the current checks with the new ones and disable the ones that are specified -func mergeStatusChecks(currentCheck *githubpkg.RequiredStatusChecks, enableContexts, disableContexts []string) *githubpkg.RequiredStatusChecks { - - // seems github api is not happy with nils for arrays ;) - if len(enableContexts) == 0 { - enableContexts = []string{} - } - - if currentCheck == nil || len(currentCheck.Contexts) == 0 { - return &githubpkg.RequiredStatusChecks{ - Strict: true, - Contexts: enableContexts, - } - } - - finalContexts := []string{} - uniqueEnableContexts := map[string]bool{} - - for _, c := range currentCheck.Contexts { - // first disable the ones we're not interested into - found := false - if len(disableContexts) > 0 { - for _, disableContext := range disableContexts { - if disableContext == c { - found = true - break - } - } - } - - if found { - continue - } - - uniqueEnableContexts[c] = true - finalContexts = append(finalContexts, c) - } - - for _, c := range enableContexts { - if uniqueEnableContexts[c] { - continue - } - - uniqueEnableContexts[c] = true - finalContexts = append(finalContexts, c) - } - - currentCheck.Contexts = finalContexts - currentCheck.Strict = true - - return currentCheck -} - -//IsEnforceAdminEnabled checks if enforce admin option is enabled for the branch protection -func IsEnforceAdminEnabled(protection *githubpkg.Protection) bool { - if protection.EnforceAdmins == nil { - return false - } - - return protection.EnforceAdmins.Enabled -} - -// CleanGithubRepoName removes the orgname if existing in the string -func CleanGithubRepoName(githubRepoName string) string { - if strings.Contains(githubRepoName, "/") { - parts := strings.Split(githubRepoName, "/") - githubRepoName = parts[len(parts)-1] - } - return githubRepoName -} diff --git a/cla-backend-go/github/protected_branch_test.go b/cla-backend-go/github/protected_branch_test.go deleted file mode 100644 index 2193f2b98..000000000 --- a/cla-backend-go/github/protected_branch_test.go +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package github - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/go-openapi/swag" - - "github.com/golang/mock/gomock" - - "github.com/bmizerany/assert" - githubsdk "github.com/google/go-github/v33/github" -) - -// TestMergeStatusChecks tests the functionality of where we enable/disable checks -func TestMergeStatusChecks(t *testing.T) { - - testCases := []struct { - Name string - currentChecks *githubsdk.RequiredStatusChecks - expectedChecks *githubsdk.RequiredStatusChecks - enableContexts []string - disableContexts []string - }{ - { - Name: "all empty", - expectedChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{}, - }, - }, - { - Name: "empty state enable", - expectedChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"EasyCLA"}, - }, - enableContexts: []string{"EasyCLA"}, - }, - { - Name: "preserve existing enable more", - currentChecks: &githubsdk.RequiredStatusChecks{ - Contexts: []string{"travis-ci"}, - }, - expectedChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"travis-ci", "EasyCLA"}, - }, - enableContexts: []string{"EasyCLA"}, - }, - { - Name: "preserve existing disable some", - currentChecks: &githubsdk.RequiredStatusChecks{ - Contexts: []string{"travis-ci", "EasyCLA"}, - }, - expectedChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"travis-ci"}, - }, - disableContexts: []string{"EasyCLA"}, - }, - { - Name: "add and remove in same operation", - currentChecks: &githubsdk.RequiredStatusChecks{ - Contexts: []string{"travis-ci", "DCO", "EasyCLA"}, - }, - expectedChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"travis-ci", "EasyCLA", "CodeQL"}, - }, - enableContexts: []string{"CodeQL"}, - disableContexts: []string{"DCO"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.Name, func(tt *testing.T) { - result := mergeStatusChecks(tc.currentChecks, tc.enableContexts, tc.disableContexts) - assert.Equal(tt, tc.expectedChecks, result) - }) - } -} - -func TestEnableBranchProtection(t *testing.T) { - owner := "johnenable" - repo := "johnsrepoenable" - branchName := defaultBranchName - - testCases := []struct { - Name string - Checks []string - Protection *githubsdk.Protection - ProtectionRequest *githubsdk.ProtectionRequest - Err error - }{ - { - Name: "success", - Checks: []string{"easyCLA"}, - Protection: &githubsdk.Protection{}, - ProtectionRequest: &githubsdk.ProtectionRequest{ - EnforceAdmins: true, - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"easyCLA"}, - }, - }, - }, - { - Name: "preserve existing checks", - Checks: []string{"easyCLA"}, - Protection: &githubsdk.Protection{ - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: false, - Contexts: []string{"circle/ci"}, - }, - }, - ProtectionRequest: &githubsdk.ProtectionRequest{ - EnforceAdmins: true, - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"circle/ci", "easyCLA"}, - }, - }, - }, - { - Name: "preserve existing settings", - Checks: []string{"easyCLA"}, - Protection: &githubsdk.Protection{ - RequiredPullRequestReviews: &githubsdk.PullRequestReviewsEnforcement{ - RequireCodeOwnerReviews: true, - RequiredApprovingReviewCount: 2, - DismissalRestrictions: &githubsdk.DismissalRestrictions{ - Users: []*githubsdk.User{ - {Login: swag.String("alex")}, - }, - Teams: []*githubsdk.Team{ - {Slug: swag.String("alpha")}, - }, - }, - }, - Restrictions: &githubsdk.BranchRestrictions{ - Users: []*githubsdk.User{ - {Login: swag.String("john")}, - }, - Teams: []*githubsdk.Team{ - {Slug: swag.String("easyCLA-Team")}, - }, - }, - RequireLinearHistory: &githubsdk.RequireLinearHistory{Enabled: true}, - AllowForcePushes: &githubsdk.AllowForcePushes{Enabled: true}, - AllowDeletions: &githubsdk.AllowDeletions{Enabled: true}, - }, - ProtectionRequest: &githubsdk.ProtectionRequest{ - RequiredPullRequestReviews: &githubsdk.PullRequestReviewsEnforcementRequest{ - RequireCodeOwnerReviews: true, - RequiredApprovingReviewCount: 2, - DismissalRestrictionsRequest: &githubsdk.DismissalRestrictionsRequest{ - Users: &[]string{"alex"}, - Teams: &[]string{"alpha"}, - }, - }, - Restrictions: &githubsdk.BranchRestrictionsRequest{ - Users: []string{"john"}, - Teams: []string{"easyCLA-Team"}, - Apps: []string{}, - }, - EnforceAdmins: true, - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: true, - Contexts: []string{"easyCLA"}, - }, - RequireLinearHistory: swag.Bool(true), - AllowForcePushes: swag.Bool(true), - AllowDeletions: swag.Bool(true), - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.Name, func(tt *testing.T) { - ctrl := gomock.NewController(tt) - // Assert that Bar() is invoked. - defer ctrl.Finish() - - m := NewMockRepositories(ctrl) - m. - EXPECT(). - GetBranchProtection(gomock.Any(), owner, repo, branchName). - Return(tc.Protection, nil, nil) - m. - EXPECT(). - UpdateBranchProtection(gomock.Any(), owner, repo, branchName, tc.ProtectionRequest). - Return(nil, nil, nil) - - branchProtectionRepo := NewBranchProtectionRepository(m) - err := branchProtectionRepo.EnableBranchProtection(context.Background(), owner, repo, branchName, true, tc.Checks, nil) - if err != nil { - tt.Errorf("enable branch proteciton failed : %v", err) - } - }) - } - -} - -func TestNonBlockingRateLimitRepositories_GetBranchProtection(t *testing.T) { - owner := "johnblocking" - repo := "johnsrepoblocking" - branchName := defaultBranchName - - t.Run("no limit reached", func(tt *testing.T) { - ctrl := gomock.NewController(tt) - defer ctrl.Finish() - - protection := &githubsdk.Protection{ - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: false, - Contexts: []string{"circle/ci"}, - }, - } - - m := NewMockRepositories(ctrl) - m. - EXPECT(). - GetBranchProtection(gomock.Any(), owner, repo, branchName). - Return(protection, nil, nil) - - nonBlockLimitRepo := NewBranchProtectionRepository(m, EnableNonBlockingLimiter()) - p, err := nonBlockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) - if err != nil { - tt.Errorf("no error expected : %v", err) - } - assert.Equal(tt, protection, p) - }) - - t.Run("limit reached", func(tt *testing.T) { - ctrl := gomock.NewController(tt) - defer ctrl.Finish() - - protection := &githubsdk.Protection{ - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: false, - Contexts: []string{"circle/ci"}, - }, - } - - m := NewMockRepositories(ctrl) - m. - EXPECT(). - GetBranchProtection(gomock.Any(), owner, repo, branchName). - Return(protection, nil, nil).AnyTimes() - - nonBlockLimitRepo := NewBranchProtectionRepository(m, EnableNonBlockingLimiter()) - // call it 100 times in loop to make it fail - var expectedErr error - for i := 0; i < 100; i++ { - _, err := nonBlockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) - if err != nil { - expectedErr = err - break - } - } - - if expectedErr == nil { - tt.Fatalf("no error returned") - return - } - - if !errors.Is(expectedErr, ErrRateLimited) { - tt.Fatalf("was expecting ErrRateLimited got : %v", expectedErr) - return - } - }) -} - -func TestBlockingRateLimitRepositories_GetBranchProtection(t *testing.T) { - owner := "john" - repo := "johnsrepo" - branchName := defaultBranchName - - t.Run("no limit reached", func(tt *testing.T) { - ctrl := gomock.NewController(tt) - defer ctrl.Finish() - - protection := &githubsdk.Protection{ - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: false, - Contexts: []string{"circle/ci"}, - }, - } - - m := NewMockRepositories(ctrl) - m. - EXPECT(). - GetBranchProtection(gomock.Any(), owner, repo, branchName). - Return(protection, nil, nil) - - blockLimitRepo := NewBranchProtectionRepository(m, EnableBlockingLimiter()) - p, err := blockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) - if err != nil { - tt.Errorf("no error expected : %v", err) - } - assert.Equal(tt, protection, p) - }) - - t.Run("limit reached", func(tt *testing.T) { - ctrl := gomock.NewController(tt) - defer ctrl.Finish() - - protection := &githubsdk.Protection{ - RequiredStatusChecks: &githubsdk.RequiredStatusChecks{ - Strict: false, - Contexts: []string{"circle/ci"}, - }, - } - - m := NewMockRepositories(ctrl) - m. - EXPECT(). - GetBranchProtection(gomock.Any(), owner, repo, branchName). - Return(protection, nil, nil).AnyTimes() - - blockLimitRepo := NewBranchProtectionRepository(m, EnableBlockingLimiter()) - - // call it 100 times in loop to make it fail - var expectedErr error - start := time.Now() - for i := 0; i < 10; i++ { - _, err := blockLimitRepo.GetProtectedBranch(context.Background(), owner, repo, branchName) - if err != nil { - expectedErr = err - break - } - } - elapsed := time.Since(start) - - if expectedErr != nil { - tt.Fatalf("no error was expected got : %v", expectedErr) - return - } - - if elapsed < 4*time.Second { - tt.Fatalf("is rate limit enabled") - } - - }) -} diff --git a/cla-backend-go/repositories/handlers.go b/cla-backend-go/repositories/handlers.go index 365b7bfd4..a25b81ad7 100644 --- a/cla-backend-go/repositories/handlers.go +++ b/cla-backend-go/repositories/handlers.go @@ -25,7 +25,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv if !claUser.IsAuthorizedForProject(params.ProjectSFID) { return github_repositories.NewGetProjectGithubRepositoriesForbidden().WithPayload(&models.ErrorResponse{ Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Add GitHub Repository with Project scope of %s", + Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Add GitHub CombinedRepository with Project scope of %s", claUser.LFUsername, params.ProjectSFID), }) } @@ -44,7 +44,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv if !claUser.IsAuthorizedForProject(params.ProjectSFID) { return github_repositories.NewAddProjectGithubRepositoryForbidden().WithPayload(&models.ErrorResponse{ Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Add GitHub Repository with Project scope of %s", + Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Add GitHub CombinedRepository with Project scope of %s", claUser.LFUsername, params.ProjectSFID), }) } @@ -75,7 +75,7 @@ func Configure(api *operations.ClaAPI, service Service, eventService events.Serv if !claUser.IsAuthorizedForProject(params.ProjectSFID) { return github_repositories.NewDeleteProjectGithubRepositoryForbidden().WithPayload(&models.ErrorResponse{ Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Delete GitHub Repository with Project scope of %s", + Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Delete GitHub CombinedRepository with Project scope of %s", claUser.LFUsername, params.ProjectSFID), }) } diff --git a/cla-backend-go/repositories/mock/mock_repository.go b/cla-backend-go/repositories/mock/mock_repository.go index 07bca37b7..88862f212 100644 --- a/cla-backend-go/repositories/mock/mock_repository.go +++ b/cla-backend-go/repositories/mock/mock_repository.go @@ -54,6 +54,21 @@ func (mr *MockRepositoryMockRecorder) AddGithubRepository(ctx, externalProjectID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubRepository", reflect.TypeOf((*MockRepository)(nil).AddGithubRepository), ctx, externalProjectID, projectSFID, input) } +// UpdateGithubRepository mocks base method +func (m *MockRepository) UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateGithubRepository", ctx, repositoryID, input) + ret0, _ := ret[0].(*models.GithubRepository) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateGithubRepository indicates an expected call of UpdateGithubRepository +func (mr *MockRepositoryMockRecorder) UpdateGithubRepository(ctx, repositoryID, input interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubRepository", reflect.TypeOf((*MockRepository)(nil).UpdateGithubRepository), ctx, repositoryID, input) +} + // UpdateClaGroupID mocks base method func (m *MockRepository) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { m.ctrl.T.Helper() @@ -82,6 +97,20 @@ func (mr *MockRepositoryMockRecorder) EnableRepository(ctx, repositoryID interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepository", reflect.TypeOf((*MockRepository)(nil).EnableRepository), ctx, repositoryID) } +// EnableRepositoryWithCLAGroupID mocks base method +func (m *MockRepository) EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnableRepositoryWithCLAGroupID", ctx, repositoryID, claGroupID) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnableRepositoryWithCLAGroupID indicates an expected call of EnableRepositoryWithCLAGroupID +func (mr *MockRepositoryMockRecorder) EnableRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepositoryWithCLAGroupID", reflect.TypeOf((*MockRepository)(nil).EnableRepositoryWithCLAGroupID), ctx, repositoryID, claGroupID) +} + // DisableRepository mocks base method func (m *MockRepository) DisableRepository(ctx context.Context, repositoryID string) error { m.ctrl.T.Helper() @@ -139,6 +168,21 @@ func (mr *MockRepositoryMockRecorder) GetRepository(ctx, repositoryID interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepository", reflect.TypeOf((*MockRepository)(nil).GetRepository), ctx, repositoryID) } +// GetRepositoryByName mocks base method +func (m *MockRepository) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRepositoryByName", ctx, repositoryName) + ret0, _ := ret[0].(*models.GithubRepository) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRepositoryByName indicates an expected call of GetRepositoryByName +func (mr *MockRepositoryMockRecorder) GetRepositoryByName(ctx, repositoryName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryByName", reflect.TypeOf((*MockRepository)(nil).GetRepositoryByName), ctx, repositoryName) +} + // GetRepositoryByGithubID mocks base method func (m *MockRepository) GetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { m.ctrl.T.Helper() @@ -200,7 +244,7 @@ func (mr *MockRepositoryMockRecorder) GetCLAGroupRepositoriesGroupByOrgs(ctx, pr } // ListProjectRepositories mocks base method -func (m *MockRepository) ListProjectRepositories(ctx context.Context, externalProjectID, projectSFID string, enabled bool) (*models.ListGithubRepositories, error) { +func (m *MockRepository) ListProjectRepositories(ctx context.Context, externalProjectID, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListProjectRepositories", ctx, externalProjectID, projectSFID, enabled) ret0, _ := ret[0].(*models.ListGithubRepositories) diff --git a/cla-backend-go/repositories/mock/mock_service.go b/cla-backend-go/repositories/mock/mock_service.go index fbca307a3..e5c1a9ce0 100644 --- a/cla-backend-go/repositories/mock/mock_service.go +++ b/cla-backend-go/repositories/mock/mock_service.go @@ -54,15 +54,6 @@ func (mr *MockServiceMockRecorder) AddGithubRepository(ctx, externalProjectID, i return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubRepository", reflect.TypeOf((*MockService)(nil).AddGithubRepository), ctx, externalProjectID, input) } -// GetRepositoryByName mocks base method -func (m *MockService) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoryByName", ctx, repositoryName) - ret0, _ := ret[0].(*models.GithubRepository) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - // EnableRepository mocks base method func (m *MockService) EnableRepository(ctx context.Context, repositoryID string) error { m.ctrl.T.Helper() @@ -71,6 +62,12 @@ func (m *MockService) EnableRepository(ctx context.Context, repositoryID string) return ret0 } +// EnableRepository indicates an expected call of EnableRepository +func (mr *MockServiceMockRecorder) EnableRepository(ctx, repositoryID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepository", reflect.TypeOf((*MockService)(nil).EnableRepository), ctx, repositoryID) +} + // EnableRepositoryWithCLAGroupID mocks base method func (m *MockService) EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { m.ctrl.T.Helper() @@ -79,10 +76,10 @@ func (m *MockService) EnableRepositoryWithCLAGroupID(ctx context.Context, reposi return ret0 } -// EnableRepository indicates an expected call of EnableRepository -func (mr *MockServiceMockRecorder) EnableRepository(ctx, repositoryID interface{}) *gomock.Call { +// EnableRepositoryWithCLAGroupID indicates an expected call of EnableRepositoryWithCLAGroupID +func (mr *MockServiceMockRecorder) EnableRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepository", reflect.TypeOf((*MockService)(nil).EnableRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepositoryWithCLAGroupID", reflect.TypeOf((*MockService)(nil).EnableRepositoryWithCLAGroupID), ctx, repositoryID, claGroupID) } // DisableRepository mocks base method @@ -123,7 +120,7 @@ func (m *MockService) ListProjectRepositories(ctx context.Context, externalProje } // ListProjectRepositories indicates an expected call of ListProjectRepositories -func (mr *MockServiceMockRecorder) ListProjectRepositories(ctx, externalProjectID interface{}, enabled *bool) *gomock.Call { +func (mr *MockServiceMockRecorder) ListProjectRepositories(ctx, externalProjectID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectRepositories", reflect.TypeOf((*MockService)(nil).ListProjectRepositories), ctx, externalProjectID, enabled) } @@ -143,7 +140,7 @@ func (mr *MockServiceMockRecorder) GetRepository(ctx, repositoryID interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepository", reflect.TypeOf((*MockService)(nil).GetRepository), ctx, repositoryID) } -// GetRepository mocks base method +// GetRepositoryByProjectSFID mocks base method func (m *MockService) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRepositoryByProjectSFID", ctx, projectSFID, enabled) @@ -152,12 +149,27 @@ func (m *MockService) GetRepositoryByProjectSFID(ctx context.Context, projectSFI return ret0, ret1 } -// GetRepository indicates an expected call of GetRepository -func (mr *MockServiceMockRecorder) GetRepositoryByProjectSFID(ctx, projectSFID interface{}, enabled *bool) *gomock.Call { +// GetRepositoryByProjectSFID indicates an expected call of GetRepositoryByProjectSFID +func (mr *MockServiceMockRecorder) GetRepositoryByProjectSFID(ctx, projectSFID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryByProjectSFID", reflect.TypeOf((*MockService)(nil).GetRepositoryByProjectSFID), ctx, projectSFID, enabled) } +// GetRepositoryByName mocks base method +func (m *MockService) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRepositoryByName", ctx, repositoryName) + ret0, _ := ret[0].(*models.GithubRepository) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRepositoryByName indicates an expected call of GetRepositoryByName +func (mr *MockServiceMockRecorder) GetRepositoryByName(ctx, repositoryName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryByName", reflect.TypeOf((*MockService)(nil).GetRepositoryByName), ctx, repositoryName) +} + // DisableRepositoriesByProjectID mocks base method func (m *MockService) DisableRepositoriesByProjectID(ctx context.Context, projectID string) (int, error) { m.ctrl.T.Helper() @@ -255,3 +267,18 @@ func (mr *MockGithubOrgRepoMockRecorder) GetGithubOrganization(ctx, githubOrgani mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganization", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGithubOrganization), ctx, githubOrganizationName) } + +// GetGithubOrganizations mocks base method +func (m *MockGithubOrgRepo) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGithubOrganizations", ctx, projectSFID) + ret0, _ := ret[0].(*models.GithubOrganizations) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetGithubOrganizations indicates an expected call of GetGithubOrganizations +func (mr *MockGithubOrgRepoMockRecorder) GetGithubOrganizations(ctx, projectSFID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizations", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGithubOrganizations), ctx, projectSFID) +} diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index d16381a84..5d5638325 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -48,7 +48,7 @@ const ( // ErrRepositoryDoesNotExist ... var ErrRepositoryDoesNotExist = errors.New("repository does not exist") -// Repository defines functions of Repositories +// Repository defines functions of V3Repositories type Repository interface { AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) @@ -168,7 +168,7 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, "repositoryURL": repositoryURL, } - log.WithFields(f).Debugf("updating Repository : %s... ", repositoryID) + log.WithFields(f).Debugf("updating CombinedRepository : %s... ", repositoryID) repoModel, repoErr := r.GetRepository(ctx, repositoryID) if repoErr != nil { @@ -177,7 +177,7 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, } if repoModel == nil { - log.WithFields(f).Warnf("Repository does not exist for repo: %s ", repositoryID) + log.WithFields(f).Warnf("CombinedRepository does not exist for repo: %s ", repositoryID) return nil, ErrRepositoryDoesNotExist } diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index c45018913..1c0c9eeea 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1579,6 +1579,9 @@ paths: in: path type: string required: true + - name: branchName + in: query + type: string responses: '200': description: 'Success' @@ -3836,6 +3839,8 @@ definitions: github-repository-branch-protection-input: type: object properties: + branch_name: + type: string enforce_admin: type: boolean default: false diff --git a/cla-backend-go/tests/github_v4_test.go b/cla-backend-go/tests/github_v4_test.go index 1465288f6..29eba9348 100644 --- a/cla-backend-go/tests/github_v4_test.go +++ b/cla-backend-go/tests/github_v4_test.go @@ -9,6 +9,8 @@ import ( "strconv" "testing" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" + ini "github.com/communitybridge/easycla/cla-backend-go/init" "github.com/spf13/viper" @@ -46,10 +48,14 @@ func TestGetRepositoryIDFromName(t *testing.T) { assert.Fail(t, fmt.Sprintf("unable to convert installation ID to string: %s", config.GitHub.TestOrganizationInstallationID), int64Err) } + branchProtectionRepoV4, err := branch_protection.NewBranchProtectionRepositoryV4(installationID) + if err != nil { + assert.Fail(t, fmt.Sprintf("initializing branch protection v4 repo failed : %v", err)) + } expectedValue := config.GitHub.TestRepositoryID - actualValue, err := github.GetRepositoryIDFromName(ctx, installationID, config.GitHub.TestOrganization, config.GitHub.TestRepository) + actualValue, err := branchProtectionRepoV4.GetRepositoryIDFromName(ctx, config.GitHub.TestOrganization, config.GitHub.TestRepository) if err != nil { assert.Fail(t, fmt.Sprintf("unable to create GitHub v4 client from installation ID: %d", installationID), err) } - assert.Equal(t, expectedValue, actualValue, "Repository ID Lookup") + assert.Equal(t, expectedValue, actualValue, "CombinedRepository ID Lookup") } diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index c219dca8e..4f22d383f 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -42,6 +42,9 @@ const EasyCLA500InternalServerError = "EasyCLA - 500 Internal Server Error" // GitHubBotName is the name of the GitHub bot const GitHubBotName = "EasyCLA" +// GithubBranchProtectionPatternAll is Github Branch Protection Pattern that matches all branches +const GithubBranchProtectionPatternAll = "**/**" + // TheLinuxFoundation is the name of the super parent for many Salesforce Foundations/Project Groups const TheLinuxFoundation = "The Linux Foundation" diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index dcf6f6bf5..c7bc874bd 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -223,13 +223,13 @@ func (a *autoEnableServiceProvider) NotifyCLAManagerForRepos(claGroupID string, // autoEnabledRepositoryEmailContent prepares the email for autoEnabled repositories func autoEnabledRepositoryEmailContent(claGroupModel *models.ClaGroup, orgName string, managers []*models.ClaManagerUser, repos []*models.GithubRepository) (string, string, []string) { claGroupName := claGroupModel.ProjectName - subject := fmt.Sprintf("EasyCLA: Auto-Enable Repository for CLA Group: %s", claGroupName) - repoPronounUpper := "Repository" + subject := fmt.Sprintf("EasyCLA: Auto-Enable CombinedRepository for CLA Group: %s", claGroupName) + repoPronounUpper := "CombinedRepository" repoPronoun := "repository" pronoun := "this " + repoPronoun repoWasHere := repoPronoun + " was" if len(repos) > 1 { - repoPronounUpper = "Repositories" + repoPronounUpper = "V3Repositories" repoPronoun = "repositories" pronoun = "these " + repoPronoun repoWasHere = repoPronoun + " were" @@ -248,7 +248,7 @@ func autoEnabledRepositoryEmailContent(claGroupModel *models.ClaGroup, orgName s Since auto-enable was configured within EasyCLA for GitHub Organization, the %s will now start enforcing CLA checks.

    Please verify the repository settings to ensure EasyCLA is a required check for merging Pull Requests. -See: GitHub Repository -> Settings -> Branches -> Branch Protection Rules -> Add/Edit the default branch, +See: GitHub CombinedRepository -> Settings -> Branches -> Branch Protection Rules -> Add/Edit the default branch, and confirm that 'Require status checks to pass before merging' is enabled and that EasyCLA is a required check. Additionally, consider selecting the 'Include administrators' option to enforce all configured restrictions for contributors, maintainers, and administrators.

    diff --git a/cla-backend-go/v2/dynamo_events/github_organization.go b/cla-backend-go/v2/dynamo_events/github_organization.go index 5b99176be..e4c973822 100644 --- a/cla-backend-go/v2/dynamo_events/github_organization.go +++ b/cla-backend-go/v2/dynamo_events/github_organization.go @@ -6,8 +6,9 @@ package dynamo_events import ( "context" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" + "github.com/aws/aws-lambda-go/events" - githubutils "github.com/communitybridge/easycla/cla-backend-go/github" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -153,14 +154,12 @@ func (s *service) enableBranchProtectionForGithubOrg(ctx context.Context, newGit } log.WithFields(f).Debugf("creating a new GitHub client object for org: %s...", newGitHubOrg.OrganizationName) - gitHubClient, clientErr := githubutils.NewGithubAppClient(newGitHubOrg.OrganizationInstallationID) - if clientErr != nil { - log.WithFields(f).WithError(clientErr).Warnf("unable to create a new GitHub app client using the installation ID: %d", newGitHubOrg.OrganizationInstallationID) - return clientErr + branchProtectionRepo, err := branch_protection.NewBranchProtectionRepository(newGitHubOrg.OrganizationInstallationID, branch_protection.EnableBlockingLimiter()) + if err != nil { + log.WithFields(f).WithError(err).Warnf("initializing branch protection repository failed") + return err } - branchProtectionRepo := githubutils.NewBranchProtectionRepository(gitHubClient.Repositories, githubutils.EnableBlockingLimiter()) - var eg errgroup.Group // a pool of 5 concurrent workers var workerTokens = make(chan struct{}, 5) @@ -176,16 +175,10 @@ func (s *service) enableBranchProtectionForGithubOrg(ctx context.Context, newGit }() log.WithFields(f).Debugf("enabling branch protection for repository: %s", repo.RepositoryName) - log.WithFields(f).Debugf("looking up the default branch for the GitHub repository: %s...", repo.RepositoryName) - defaultBranch, branchErr := branchProtectionRepo.GetDefaultBranchForRepo(ctx, newGitHubOrg.OrganizationName, repo.RepositoryName) - if branchErr != nil { - return branchErr - } - log.WithFields(f).Debugf("enabling branch protection on the default branch %s for the GitHub repository: %s...", - defaultBranch, repo.RepositoryName) + utils.GithubBranchProtectionPatternAll, repo.RepositoryName) return branchProtectionRepo.EnableBranchProtection(ctx, newGitHubOrg.OrganizationName, repo.RepositoryName, - defaultBranch, true, []string{utils.GitHubBotName}, []string{}) + utils.GithubBranchProtectionPatternAll, true, []string{utils.GitHubBotName}, []string{}) }) } diff --git a/cla-backend-go/v2/dynamo_events/github_repository.go b/cla-backend-go/v2/dynamo_events/github_repository.go index 9fa9852e0..abc27f7d7 100644 --- a/cla-backend-go/v2/dynamo_events/github_repository.go +++ b/cla-backend-go/v2/dynamo_events/github_repository.go @@ -6,7 +6,8 @@ package dynamo_events import ( "context" - "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" + "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/sirupsen/logrus" @@ -110,25 +111,17 @@ func (s *service) EnableBranchProtectionServiceHandler(event events.DynamoDBEven log.WithFields(f).Debug("branch protection is enabled for this organization") ctx := context.Background() - log.WithFields(f).Debug("creating a new GitHub client object...") - gitHubClient, clientErr := github.NewGithubAppClient(gitHubOrg.OrganizationInstallationID) - if clientErr != nil { - return clientErr - } - - branchProtectionRepository := github.NewBranchProtectionRepository(gitHubClient.Repositories, github.EnableBlockingLimiter()) - - log.WithFields(f).Debug("looking up the default branch for the GitHub repository...") - defaultBranch, branchErr := branchProtectionRepository.GetDefaultBranchForRepo(ctx, gitHubOrg.OrganizationName, newRepoModel.RepositoryName) - if branchErr != nil { - return branchErr + branchProtectionRepository, err := branch_protection.NewBranchProtectionRepository(gitHubOrg.OrganizationInstallationID, branch_protection.EnableBlockingLimiter()) + if err != nil { + log.WithFields(f).WithError(err).Warnf("initializing branch protection repository failed") + return err } log.WithFields(f).Debugf("enabling branch protection on th default branch %s for the GitHub repository: %s...", - defaultBranch, newRepoModel.RepositoryName) + utils.GithubBranchProtectionPatternAll, newRepoModel.RepositoryName) return branchProtectionRepository.EnableBranchProtection(ctx, parentOrgName, newRepoModel.RepositoryName, - defaultBranch, true, []string{utils.GitHubBotName}, []string{}) + utils.GithubBranchProtectionPatternAll, true, []string{utils.GitHubBotName}, []string{}) } log.WithFields(f).Debug("github organization branch protection is not enabled - no action required") diff --git a/cla-backend-go/v2/metrics/repository.go b/cla-backend-go/v2/metrics/repository.go index 7876ec48e..7fcc31042 100644 --- a/cla-backend-go/v2/metrics/repository.go +++ b/cla-backend-go/v2/metrics/repository.go @@ -756,7 +756,7 @@ func (repo *repo) calculateMetrics() (*Metrics, error) { return nil, err } - log.Debug("Calculating Repository metrics...") + log.Debug("Calculating CombinedRepository metrics...") // calculate github repositories count // increment project repositories count err = repo.processRepositoriesTable(metrics) diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 66766b0a2..9ca7c850e 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -9,6 +9,8 @@ import ( "fmt" "strings" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" + "github.com/sirupsen/logrus" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -42,7 +44,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Get GitHub Repositories with Project scope of %s", + msg := fmt.Sprintf("user %s does not have access to Get GitHub V3Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewGetProjectGithubRepositoriesForbidden().WithPayload( @@ -94,7 +96,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Add GitHub Repositories with Project scope of %s", + msg := fmt.Sprintf("user %s does not have access to Add GitHub V3Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewAddProjectGithubRepositoryForbidden().WithPayload( @@ -172,7 +174,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Delete GitHub Repositories with Project scope of %s", + msg := fmt.Sprintf("user %s does not have access to Delete GitHub V3Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewDeleteProjectGithubRepositoryForbidden().WithPayload( @@ -230,14 +232,21 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Query Protected Branch GitHub Repositories with Project scope of %s", + msg := fmt.Sprintf("user %s does not have access to Query Protected Branch GitHub V3Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewGetProjectGithubRepositoryBranchProtectionForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) } - protectedBranch, err := service.GetProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID) + var branchName string + if params.BranchName == nil || *params.BranchName == "" { + branchName = branch_protection.DefaultBranchName + } else { + branchName = *params.BranchName + } + + protectedBranch, err := service.GetProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID, branchName) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { msg := fmt.Sprintf("unable to locatate branch protection projectSFID: %s, repository: %s", params.ProjectSFID, params.RepositoryID) @@ -284,14 +293,14 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Update Protected Branch GitHub Repositories with Project scope of %s", + msg := fmt.Sprintf("user %s does not have access to Update Protected Branch GitHub V3Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewUpdateProjectGithubRepositoryBranchProtectionForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) } - protectedBranch, err := service.UpdateProtectedBranch(ctx, params.RepositoryID, params.ProjectSFID, params.GithubRepositoryBranchProtectionInput) + protectedBranch, err := service.UpdateProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID, params.GithubRepositoryBranchProtectionInput) if err != nil { log.Warnf("update protected branch failed for repo %s : %v", params.RepositoryID, err) if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index f42da1796..f37f8a1f5 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -9,12 +9,12 @@ import ( "fmt" "strconv" - "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" - "github.com/go-openapi/swag" - githubsdk "github.com/google/go-github/v33/github" + "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + "github.com/go-openapi/swag" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -29,7 +29,7 @@ import ( v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" ) -// Service contains functions of GitHub Repositories service +// Service contains functions of GitHub V3Repositories service type Service interface { AddGithubRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) EnableRepository(ctx context.Context, repositoryID string) error @@ -38,7 +38,7 @@ type Service interface { GetRepository(ctx context.Context, repositoryID string) (*v1Models.GithubRepository, error) GetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) DisableCLAGroupRepositories(ctx context.Context, claGroupID string) error - GetProtectedBranch(ctx context.Context, projectSFID, repositoryID string) (*v2Models.GithubRepositoryBranchProtection, error) + GetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) UpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) } @@ -346,12 +346,13 @@ func (s *service) GetRepositoryByName(ctx context.Context, repositoryName string return s.repo.GetRepositoryByName(ctx, repositoryName) } -func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositoryID string) (*v2Models.GithubRepositoryBranchProtection, error) { +func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) { f := logrus.Fields{ "functionName": "repositories.service.GetProtectedBranch", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "repositoryID": repositoryID, + "branchName": branchName, } githubRepository, err := s.getGithubRepo(ctx, projectSFID, repositoryID) @@ -362,15 +363,14 @@ func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositor githubOrgName := githubRepository.RepositoryOrganizationName githubRepoName := githubRepository.RepositoryName - githubRepoName = github.CleanGithubRepoName(githubRepoName) + githubRepoName = branch_protection.CleanGithubRepoName(githubRepoName) - githubClient, err := s.getGithubClientForOrgName(ctx, githubOrgName) + branchProtectionRepository, err := s.getBranchProtectionRepositoryForOrgName(ctx, githubOrgName) if err != nil { return nil, err } - branchProtectionRepository := github.NewBranchProtectionRepository(githubClient.Repositories, github.EnableNonBlockingLimiter()) - owner, branchName, err := s.getGithubOwnerBranchName(ctx, branchProtectionRepository, githubOrgName, githubRepoName) + owner, err := s.getGithubOwner(ctx, branchProtectionRepository, githubOrgName, githubRepoName) if err != nil { return nil, err } @@ -381,7 +381,7 @@ func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositor branchProtection, err := branchProtectionRepository.GetProtectedBranch(ctx, owner, githubRepoName, branchName) if err != nil { - if errors.Is(err, github.ErrBranchNotProtected) { + if errors.Is(err, branch_protection.ErrBranchNotProtected) { return result, nil } log.WithFields(f).WithError(err).Warnf("getting the github protected branch for owner : %s, repo : %s and branch : %s failed : %v", owner, githubRepoName, branchName, err) @@ -389,7 +389,7 @@ func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositor } result.ProtectionEnabled = true - if github.IsEnforceAdminEnabled(branchProtection) { + if branch_protection.IsEnforceAdminEnabled(branchProtection) { result.EnforceAdmin = true } @@ -417,22 +417,26 @@ func (s *service) UpdateProtectedBranch(ctx context.Context, projectSFID, reposi githubOrgName := githubRepository.RepositoryOrganizationName githubRepoName := githubRepository.RepositoryName - githubRepoName = github.CleanGithubRepoName(githubRepoName) + githubRepoName = branch_protection.CleanGithubRepoName(githubRepoName) - githubClient, err := s.getGithubClientForOrgName(ctx, githubOrgName) + branchProtectionRepository, err := s.getBranchProtectionRepositoryForOrgName(ctx, githubOrgName) if err != nil { log.WithFields(f).WithError(err).Warn("problem locating github client for organization name") return nil, err } - branchProtectionRepository := github.NewBranchProtectionRepository(githubClient.Repositories, github.EnableNonBlockingLimiter()) - owner, branchName, err := s.getGithubOwnerBranchName(ctx, branchProtectionRepository, githubOrgName, githubRepoName) + branchName := input.BranchName + if branchName == "" { + branchName = branch_protection.DefaultBranchName + } + + owner, err := s.getGithubOwner(ctx, branchProtectionRepository, githubOrgName, githubRepoName) if err != nil { log.WithFields(f).WithError(err).Warn("problem locating github owner branch name") return nil, err } f["owner"] = owner - f["branchName"] = branchName + f["branchName"] = input.BranchName var requiredChecks []string var disabledChecks []string @@ -468,7 +472,7 @@ func (s *service) UpdateProtectedBranch(ctx context.Context, projectSFID, reposi return nil, err } - return s.GetProtectedBranch(ctx, projectSFID, repositoryID) + return s.GetProtectedBranch(ctx, projectSFID, repositoryID, branchName) } func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID string) (*v1Models.GithubRepository, error) { @@ -500,7 +504,7 @@ func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID s return githubRepository, nil } -func (s *service) getGithubClientForOrgName(ctx context.Context, githubOrgName string) (*githubsdk.Client, error) { +func (s *service) getBranchProtectionRepositoryForOrgName(ctx context.Context, githubOrgName string) (*branch_protection.BranchProtectionRepository, error) { f := logrus.Fields{ "functionName": "repositories.service.getGitHubClientForOrgName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -513,39 +517,29 @@ func (s *service) getGithubClientForOrgName(ctx context.Context, githubOrgName s return nil, err } - githubClient, err := github.NewGithubAppClient(githubOrg.OrganizationInstallationID) + branchProtectionRepo, err := branch_protection.NewBranchProtectionRepository(githubOrg.OrganizationInstallationID, branch_protection.EnableNonBlockingLimiter()) if err != nil { - log.WithFields(f).Warnf("creating the github client for installation id %d failed, error: %v", githubOrg.OrganizationInstallationID, err) return nil, err } - - return githubClient, nil + return branchProtectionRepo, nil } -func (s *service) getGithubOwnerBranchName(ctx context.Context, branchProtectionRepository *github.BranchProtectionRepository, githubOrgName, githubRepoName string) (string, string, error) { +func (s *service) getGithubOwner(ctx context.Context, branchProtectionRepository *branch_protection.BranchProtectionRepository, githubOrgName, githubRepoName string) (string, error) { owner, err := branchProtectionRepository.GetOwnerName(ctx, githubOrgName, githubRepoName) if err != nil { log.Warnf("getting the owner name for org : %s and repo : %s failed : %v", githubOrgName, githubRepoName, err) - return "", "", err + return "", err } if owner == "" { log.Warnf("GitHub returned empty owner name for org : %s and repo : %s", githubOrgName, githubRepoName) - return "", "", fmt.Errorf("empty owner name") - } - - log.Debugf("getGitHubOwnerBranchName : owner of the repo : %s found : %s", owner, githubRepoName) - branchName, err := branchProtectionRepository.GetDefaultBranchForRepo(ctx, owner, githubRepoName) - if err != nil { - log.Warnf("getting default GitHub branch failed for owner : %s and repo : %s : %v", owner, githubRepoName, err) - return "", "", err + return "", fmt.Errorf("empty owner name") } - - return owner, branchName, nil + return owner, nil } // getRequiredProtectedBranchCheckStatus -func (s *service) getRequiredProtectedBranchCheckStatus(protectedBranch *githubsdk.Protection, requiredChecks []string) []*v2Models.GithubRepositoryBranchProtectionStatusChecks { +func (s *service) getRequiredProtectedBranchCheckStatus(branchProtectionRule *branch_protection.BranchProtectionRule, requiredChecks []string) []*v2Models.GithubRepositoryBranchProtectionStatusChecks { f := logrus.Fields{ "functionName": "repositories.service.getRequiredProtectedBranchCheckStatus", } @@ -560,11 +554,11 @@ func (s *service) getRequiredProtectedBranchCheckStatus(protectedBranch *githubs }) resultMap[rc] = true } - if protectedBranch.RequiredStatusChecks == nil || len(protectedBranch.RequiredStatusChecks.Contexts) == 0 { + if len(branchProtectionRule.RequiredStatusCheckContexts) == 0 { return result } - for _, existingCheck := range protectedBranch.RequiredStatusChecks.Contexts { + for _, existingCheck := range branchProtectionRule.RequiredStatusCheckContexts { if !resultMap[existingCheck] { continue } @@ -576,7 +570,6 @@ func (s *service) getRequiredProtectedBranchCheckStatus(protectedBranch *githubs } } } - return result } From 043e33980c89181003c7713c329960cb0c8ba8fc Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 19 Mar 2021 01:08:39 +0300 Subject: [PATCH 0168/1276] Feature/lf name events (#2799) --- .../cmd/dynamo_events_lambda/main.go | 3 +- cla-backend-go/cmd/server.go | 6 +- cla-backend-go/events/event_data.go | 39 ++++++- cla-backend-go/events/service.go | 28 +++++ cla-backend-go/project/handlers.go | 3 +- cla-backend-go/signatures/handlers.go | 2 +- cla-backend-go/signatures/models.go | 6 + cla-backend-go/signatures/repository.go | 103 ++++++++++++++++-- cla-backend-go/signatures/service.go | 36 +++--- cla-backend-go/v2/company/service.go | 2 +- cla-backend-go/v2/signatures/handlers.go | 2 +- 11 files changed, 198 insertions(+), 32 deletions(-) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index d7a0624c8..8cdcfbe47 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -80,7 +80,6 @@ func init() { usersRepo := users.NewRepository(awsSession, stage) userRepo := user.NewDynamoRepository(awsSession, stage) companyRepo := company.NewRepository(awsSession, stage) - signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo) projectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) repositoriesRepo := repositories.NewRepository(awsSession, stage) gerritRepo := gerrits.NewRepository(awsSession, stage) @@ -120,6 +119,8 @@ func init() { projectClaGroupRepo, }) + signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo, eventsService) + usersService := users.NewService(usersRepo, eventsService) companyService := company.NewService(companyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) v2CompanyService := v2Company.NewService(companyService, signaturesRepo, projectRepo, usersRepo, companyRepo, projectClaGroupRepo, eventsService) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index a56c20175..ad3a6d9e8 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -236,10 +236,9 @@ func server(localMode bool) http.Handler { templateRepo := template.NewRepository(awsSession, stage) approvalListRepo := approval_list.NewRepository(awsSession, stage) v1CompanyRepo := v1Company.NewRepository(awsSession, stage) - signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo) + eventsRepo := events.NewRepository(awsSession, stage) projectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) v1CLAGroupRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, projectClaGroupRepo) - eventsRepo := events.NewRepository(awsSession, stage) metricsRepo := metrics.NewRepository(awsSession, stage, configFile.APIGatewayURL, projectClaGroupRepo) githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) claManagerReqRepo := cla_manager.NewRepository(awsSession, stage) @@ -252,6 +251,9 @@ func server(localMode bool) http.Handler { projectClaGroupRepo, }) + // Signature repository handler + signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService) + // Initialize the external platform services - these are external APIs that // we download the swagger specification, generate the models, and have //client helper functions diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 1bd7802a1..122d27850 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -80,6 +80,13 @@ type SignatureProjectInvalidatedEventData struct { InvalidatedCount int } +//SignatureInvalidatedApprovalRejectionEventData . . . +type SignatureInvalidatedApprovalRejectionEventData struct { + GHUsername string + Email string + SignatureID string +} + // UserCreatedEventData . . . type UserCreatedEventData struct{} @@ -359,25 +366,25 @@ type ClaManagerRoleDeletedData struct { // GetEventDetailsString . . . func (ed *CLAGroupEnrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("%s (%s/%s) enabled the the project %s (%s) from the CLA Group %s (%s).", - args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false + args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false } // GetEventDetailsString . . . func (ed *CLAGroupUnenrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("%s (%s/%s) unenrolled the the project %s (%s) from the CLA Group %s (%s).", - args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false + args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false } // GetEventDetailsString . . . func (ed *ProjectServiceCLAEnabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("%s (%s/%s) enabled the CLA Service for the project %s (%s)", - args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false + args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false } // GetEventDetailsString . . . func (ed *ProjectServiceCLADisabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("%s (%s/%s) disabled the CLA Service for the project %s (%s)", - args.UserName, args.UserModel.LfUsername, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false + args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false } // GetEventDetailsString . . . @@ -697,6 +704,18 @@ func (ed *SignatureProjectInvalidatedEventData) GetEventDetailsString(args *LogE return data, true } +// GetEventDetailsString . . . +func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + reason := "No reason" + if ed.Email != "" { + reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) + } else if ed.GHUsername != "" { + reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) + } + data := fmt.Sprintf("Signature ID: %s invalidated (approved set to false) due to %s ", ed.SignatureID, reason) + return data, true +} + // GetEventDetailsString . . . func (ed *ContributorNotifyCompanyAdminData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s notified Company Admin: %s by Email: %s for Company ID: %s, Name: %s.", @@ -1479,6 +1498,18 @@ func (ed *SignatureProjectInvalidatedEventData) GetEventSummaryString(args *LogE return data, true } +// GetEventSummaryString . . . +func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + reason := "No reason" + if ed.Email != "" { + reason = fmt.Sprintf("Email: %s approval removal ", ed.Email) + } else if ed.GHUsername != "" { + reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) + } + data := fmt.Sprintf("Signature ID: %s invalidated (approved set to false) due to %s ", ed.SignatureID, reason) + return data, true +} + // GetEventSummaryString . . . func (ed *ContributorNotifyCompanyAdminData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s notified the company admin %s by the email address %s", diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index b2ea502ce..8db6e86ec 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -275,6 +275,27 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { return nil } +func (s *service) loadLFUser(ctx context.Context, args *LogEventArgs) error { + f := logrus.Fields{ + "functionName": "v1.events.service.LFUser", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + if args == nil { + return errors.New(("unable to load lf user data - args is nil")) + } + + if args.LfUsername != "" { + lfUser, lfErr := user_service.GetClient().GetUserByUsername(args.LfUsername) + if lfErr != nil || lfUser == nil { + log.WithFields(f).Warnf("unable to fetch user by username: %s ", args.LfUsername) + return nil + } + args.LFUser = lfUser + } + return nil +} + func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ "functionName": "v1.events.service.loadUser", @@ -398,6 +419,13 @@ func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { return err } + log.WithFields(f).Debug("loading LF user details...") + err = s.loadLFUser(ctx, args) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to load LF user details...") + return err + } + return nil } diff --git a/cla-backend-go/project/handlers.go b/cla-backend-go/project/handlers.go index d0973c947..1d9ea2c6a 100644 --- a/cla-backend-go/project/handlers.go +++ b/cla-backend-go/project/handlers.go @@ -237,7 +237,8 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser // Invalidate project signatures log.WithFields(f).Debug("Invalidating signatures") - howMany, err = signatureService.InvalidateProjectRecords(ctx, params.ProjectID, claGroupModel.ProjectName) + note := fmt.Sprintf("Signature invalidated (approved set to false) due to CLA Group/Project: %s deletion", params.ProjectID) + howMany, err = signatureService.InvalidateProjectRecords(ctx, params.ProjectID, note) if err != nil { return project.NewDeleteProjectByIDBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) } diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index 50dfa8ebf..1afbb2642 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -346,7 +346,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d api.SignaturesGetProjectCompanyEmployeeSignaturesHandler = signatures.GetProjectCompanyEmployeeSignaturesHandlerFunc(func(params signatures.GetProjectCompanyEmployeeSignaturesParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - projectSignatures, err := service.GetProjectCompanyEmployeeSignatures(ctx, params) + projectSignatures, err := service.GetProjectCompanyEmployeeSignatures(ctx, params, nil) if err != nil { log.Warnf("error retrieving employee project signatures for project: %s, company: %s, error: %+v", params.ProjectID, params.CompanyID, err) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 6684e0be9..7cc53f4d2 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -10,3 +10,9 @@ type SignatureCompanyID struct { CompanySFID string CompanyName string } + +//ApprovalCriteria struct representing approval criteria values +type ApprovalCriteria struct { + UserEmail string + GitHubUsername string +} diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 2569cf9c2..501de32db 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -9,6 +9,7 @@ import ( "fmt" "sort" "strings" + "sync" "github.com/sirupsen/logrus" @@ -19,6 +20,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/company" + "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" @@ -51,7 +53,7 @@ type SignatureRepository interface { GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string) ([]models.GithubOrg, error) AddGithubOrganizationToWhitelist(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) DeleteGithubOrganizationFromWhitelist(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) - InvalidateProjectRecord(ctx context.Context, signatureID string, projectName string) error + InvalidateProjectRecord(ctx context.Context, signatureID, note string) error GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) @@ -61,12 +63,12 @@ type SignatureRepository interface { CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) - GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, pageSize int64) (*models.Signatures, error) + GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, pageSize int64) (*models.Signatures, error) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) ProjectSignatures(ctx context.Context, projectID string) (*models.Signatures, error) - UpdateApprovalList(ctx context.Context, projectID, companyID string, params *models.ApprovalList) (*models.Signature, error) + UpdateApprovalList(ctx context.Context, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) RemoveCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) @@ -87,16 +89,18 @@ type repository struct { dynamoDBClient *dynamodb.DynamoDB companyRepo company.IRepository usersRepo users.UserRepository + eventsService events.Service signatureTableName string } // NewRepository creates a new instance of the whitelist service -func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository) SignatureRepository { +func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service) SignatureRepository { return repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), companyRepo: companyRepo, usersRepo: usersRepo, + eventsService: eventsService, signatureTableName: fmt.Sprintf("cla-%s-signatures", stage), } } @@ -1320,7 +1324,7 @@ func (repo repository) ProjectSignatures(ctx context.Context, projectID string) } // InvalidateProjectRecord invalidates the specified project record by setting the signature_approved flag to false -func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID string, projectName string) error { +func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID, note string) error { f := logrus.Fields{ "functionName": "InvalidateProjectRecord", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1339,7 +1343,6 @@ func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID updateExpression = updateExpression + " #A = :a," expressionAttributeNames["#S"] = aws.String("note") - note := fmt.Sprintf("Signature invalidated (approved set to false) due to CLA Group/Project: %s deletion", projectName) expressionAttributeValues[":s"] = &dynamodb.AttributeValue{S: aws.String(note)} updateExpression = updateExpression + " #S = :s" @@ -1365,7 +1368,7 @@ func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID } // GetProjectCompanyEmployeeSignatures returns a list of employee signatures for the specified project and specified company -func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, pageSize int64) (*models.Signatures, error) { +func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, pageSize int64) (*models.Signatures, error) { f := logrus.Fields{ "functionName": "GetProjectCompanyEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1383,6 +1386,16 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, filter := expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true))). And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))) + if criteria.GitHubUsername != "" { + log.WithFields(f).Debugf("adding Githubusername criteria filter for :%s ", criteria.GitHubUsername) + filter = filter.And(expression.Name("user_github_username").Equal(expression.Value(criteria.GitHubUsername))) + } + + if criteria.UserEmail != "" { + log.WithFields(f).Debugf("adding useremail criteria filter for : %s ", criteria.UserEmail) + filter = filter.And(expression.Name("user_email").Equal(expression.Value(criteria.UserEmail))) + } + // Use the nice builder to create the expression expr, err := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter).WithProjection(buildProjection()).Build() if err != nil { @@ -1931,7 +1944,7 @@ func (repo repository) RemoveCLAManager(ctx context.Context, signatureID, claMan } // UpdateApprovalList updates the specified project/company signature with the updated approval list information -func (repo repository) UpdateApprovalList(ctx context.Context, projectID, companyID string, params *models.ApprovalList) (*models.Signature, error) { // nolint +func (repo repository) UpdateApprovalList(ctx context.Context, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) { // nolint f := logrus.Fields{ "functionName": "UpdateApprovalList", "projectID": projectID, @@ -1968,6 +1981,11 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan haveAdditions := false updateExpression := "" + employeeSignatureParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ + ProjectID: projectID, + CompanyID: companyID, + } + // If we have an add or remove email list...we need to run an update for this column if params.AddEmailApprovalList != nil || params.RemoveEmailApprovalList != nil { columnName := "email_whitelist" @@ -1988,6 +2006,40 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan expressionAttributeValues[":e"] = attrList updateExpression = updateExpression + " #E = :e, " } + + // if email removal update signature approvals + if params.RemoveEmailApprovalList != nil { + var wg sync.WaitGroup + wg.Add(len(params.RemoveEmailApprovalList)) + for _, email := range params.RemoveEmailApprovalList { + go func(email string) { + defer wg.Done() + criteria := &ApprovalCriteria{ + UserEmail: email, + } + log.WithFields(f).Debugf("Updating signature records for emailApprovalList: %+v ", params.RemoveEmailApprovalList) + signs, appErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) + if appErr != nil { + log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", appErr) + return + } + log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) + note := fmt.Sprintf("Signature invalidated (approved set to false) due to email approval list removal : %+v", params.RemoveEmailApprovalList) + signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) + if signErr != nil { + log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) + return + } + //Log Event + eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ + SignatureID: signs.Signatures[0].SignatureID, + Email: email, + } + repo.eventsService.LogEventWithContext(ctx, eventArgs) + }(email) + } + wg.Wait() + } } if params.AddDomainApprovalList != nil || params.RemoveDomainApprovalList != nil { @@ -2030,6 +2082,41 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan expressionAttributeValues[":g"] = attrList updateExpression = updateExpression + " #G = :g, " } + if params.RemoveGithubUsernameApprovalList != nil { + // if email removal update signature approvals + if params.RemoveGithubUsernameApprovalList != nil { + var wg sync.WaitGroup + wg.Add(len(params.RemoveGithubUsernameApprovalList)) + for _, ghUsername := range params.RemoveGithubUsernameApprovalList { + go func(ghUsername string) { + defer wg.Done() + criteria := &ApprovalCriteria{ + GitHubUsername: ghUsername, + } + log.WithFields(f).Debugf("Updating signature records for ghUsernameApporvalList: %+v ", params.RemoveGithubUsernameApprovalList) + signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) + if sigErr != nil { + log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) + return + } + log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) + note := fmt.Sprintf("Signature invalidated (approved set to false) due to ghUsernames approval list removal : %+v", params.RemoveGithubUsernameApprovalList) + signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) + if signErr != nil { + log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) + return + } + // Log Event + eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ + SignatureID: signs.Signatures[0].SignatureID, + GHUsername: ghUsername, + } + repo.eventsService.LogEventWithContext(ctx, eventArgs) + }(ghUsername) + } + wg.Wait() + } + } } if params.AddGithubOrgApprovalList != nil || params.RemoveGithubOrgApprovalList != nil { diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index a4b66037e..227140675 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -15,7 +15,6 @@ import ( "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/LF-Engineering/lfx-kit/auth" @@ -40,11 +39,11 @@ type SignatureService interface { CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, params signatures.GetProjectCompanySignaturesParams) (*models.Signatures, error) - GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams) (*models.Signatures, error) + GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams) (*models.Signatures, error) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams) (*models.Signatures, error) - InvalidateProjectRecords(ctx context.Context, projectID string, projectName string) (int, error) + InvalidateProjectRecords(ctx context.Context, projectID, note string) (int, error) GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string, githubAccessToken string) ([]models.GithubOrg, error) AddGithubOrganizationToWhitelist(ctx context.Context, signatureID string, whiteListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) @@ -142,7 +141,7 @@ func (s service) GetProjectCompanySignatures(ctx context.Context, params signatu } // GetProjectCompanyEmployeeSignatures returns the list of employee signatures associated with the specified project -func (s service) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams) (*models.Signatures, error) { +func (s service) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) { const defaultPageSize int64 = 10 var pageSize = defaultPageSize @@ -150,7 +149,7 @@ func (s service) GetProjectCompanyEmployeeSignatures(ctx context.Context, params pageSize = *params.PageSize } - projectSignatures, err := s.repo.GetProjectCompanyEmployeeSignatures(ctx, params, pageSize) + projectSignatures, err := s.repo.GetProjectCompanyEmployeeSignatures(ctx, params, criteria, pageSize) if err != nil { return nil, err } @@ -424,7 +423,18 @@ func (s service) UpdateApprovalList(ctx context.Context, authUser *auth.User, cl return nil, userErr } - updatedSig, err := s.repo.UpdateApprovalList(ctx, claGroupModel.ProjectID, companyModel.CompanyID, params) + eventArgs := &events.LogEventArgs{ + EventType: events.InvalidatedSignature, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, + } + updatedSig, err := s.repo.UpdateApprovalList(ctx, claGroupModel.ProjectID, companyModel.CompanyID, params, eventArgs) if err != nil { return updatedSig, err } @@ -445,11 +455,11 @@ func (s service) UpdateApprovalList(ctx context.Context, authUser *auth.User, cl } // Disassociate project signatures -func (s service) InvalidateProjectRecords(ctx context.Context, projectID string, projectName string) (int, error) { +func (s service) InvalidateProjectRecords(ctx context.Context, projectID, note string) (int, error) { f := logrus.Fields{ "functionName": "InvalidateProjectRecords", "projectID": projectID, - "projectName": projectName} + } result, err := s.repo.ProjectSignatures(ctx, projectID) if err != nil { @@ -464,14 +474,14 @@ func (s service) InvalidateProjectRecords(ctx context.Context, projectID string, len(result.Signatures), projectID)) for _, signature := range result.Signatures { // Do this in parallel, as we could have a lot to invalidate - go func(sigID, projName string) { + go func(sigID, projectID string) { defer wg.Done() - updateErr := s.repo.InvalidateProjectRecord(ctx, sigID, projName) + updateErr := s.repo.InvalidateProjectRecord(ctx, sigID, note) if updateErr != nil { - log.WithFields(f).Warnf("Unable to update signature: %s with project name: %s, error: %v", - sigID, projName, updateErr) + log.WithFields(f).Warnf("Unable to update signature: %s with project ID: %s, error: %v", + sigID, projectID, updateErr) } - }(signature.SignatureID, projectName) + }(signature.SignatureID, projectID) } // Wait until all the workers are done diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index f1fadcb39..6872628fa 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1567,7 +1567,7 @@ func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, co CompanyID: companyID, ProjectID: claGroup.ProjectID, } - sigs, err := s.signatureRepo.GetProjectCompanyEmployeeSignatures(ctx, params, HugePageSize) + sigs, err := s.signatureRepo.GetProjectCompanyEmployeeSignatures(ctx, params, nil, HugePageSize) if err != nil { return nil, err } diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 10bb85a56..1181cda1c 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -550,7 +550,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj CompanyID: companyModel.CompanyID, // internal company id NextKey: params.NextKey, PageSize: params.PageSize, - }) + }, nil) if err != nil { log.WithFields(f).WithError(err).Warnf("error retrieving employee project signatures for project: %s, company: %s, error: %+v", params.ProjectSFID, params.CompanyID, err) From b9c120d630f00f025df1aec187346e160e8cffdc Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 18 Mar 2021 16:01:10 -0700 Subject: [PATCH 0169/1276] Updated Company and SFID RegEx on v2 APIs (#2801) - Resolves issue with company with name: Swisscom (Schweiz) AG Signed-off-by: David Deal --- cla-backend-go/swagger/cla.v2.yaml | 32 ++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 1c0c9eeea..1110543c1 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3556,8 +3556,7 @@ parameters: in: query type: string required: false - pattern: '^([\w\d\s\-\,\./]+){2,255}$' - #$ref: './common/properties/company-name.yaml' + pattern: '[^<>]*' # allow everything except greater than and less than symbols userName: name: userName description: The optional user name filter @@ -3593,12 +3592,14 @@ parameters: description: the Salesforce ID of the Foundation in: query type: string + pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id signingEntityName: name: signingEntityName description: The signing entity name of a Company (Salesforce and EasyCLA) in: query type: string required: true + pattern: '[^<>]*' # allow everything except greater than and less than symbols companyID: name: companyID description: The internal company ID representing signing entity name instance (EasyCLA) @@ -3610,8 +3611,7 @@ parameters: in: path type: string required: true - # \w - Any word character (alphanumeric & underscore), dashes, periods - pattern: '^(\w)([\w\-.])+$' + pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?4[a-fA-F0-9]{3}-?[89ab][a-fA-F0-9]{3}-?[a-fA-F0-9]{12}$' # uuidv4 minLength: 5 maxLength: 255 path-foundationSFID: @@ -3620,8 +3620,7 @@ parameters: in: path type: string required: true - # \w - Any word character (alphanumeric & underscore), dashes, periods - pattern: '^(\w)([\w\-.])+$' + pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id minLength: 5 maxLength: 255 path-projectSFID: @@ -3630,8 +3629,7 @@ parameters: in: path type: string required: true - # \w - Any word character (alphanumeric & underscore), dashes, periods - pattern: '^(\w)([\w\-.])+$' + pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id minLength: 5 maxLength: 255 path-userID: @@ -3656,27 +3654,23 @@ parameters: in: path type: string required: true - # \w - Any word character (alphanumeric & underscore), dashes, periods - pattern: '^(\w)([\w\-.])+$' - minLength: 5 - maxLength: 255 + pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?4[a-fA-F0-9]{3}-?[89ab][a-fA-F0-9]{3}-?[a-fA-F0-9]{12}$' # uuidv4 path-companySFID: name: companySFID description: salesforce id of the company in: path type: string required: true - # \w - Any word character (alphanumeric & underscore), dashes, periods - pattern: '^(\w)([\w\-.])+$' - minLength: 5 - maxLength: 255 + pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id path-companyName: name: companyName description: the company name in: path type: string required: true - pattern: '^([\w\d\s\-\,\./]+){2,255}$' + pattern: '[^<>]*' # allow everything except greater than and less than symbols + minLength: 2 + maxLength: 100 path-signingEntityName: name: signingEntityName type: string @@ -3693,8 +3687,7 @@ parameters: in: path type: string required: true - # \w - Any word character (alphanumeric & underscore), dashes, periods - pattern: '^(\w)([\w\-.])+$' + pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?4[a-fA-F0-9]{3}-?[89ab][a-fA-F0-9]{3}-?[a-fA-F0-9]{12}$' # uuidv4 minLength: 5 maxLength: 255 companySFID: @@ -3702,6 +3695,7 @@ parameters: description: salesforce id of the company in: query type: string + pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id gerritHost: name: gerritHost description: host of the gerrit server From 2446d45e766cd62202fa9e08acd30aee4bc07da4 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 19 Mar 2021 02:01:29 +0300 Subject: [PATCH 0170/1276] [#2781]Feature/Signature Invalidation upon Approval removal (#2786) --- cla-backend-go/events/event_data.go | 7 ++++++- cla-backend-go/events/service.go | 21 --------------------- cla-backend-go/project/handlers.go | 3 ++- cla-backend-go/signatures/repository.go | 17 +++++++++++++---- cla-backend-go/signatures/service.go | 4 +++- cla-backend-go/utils/cla_user.go | 25 +++++++++++++++++++++++++ 6 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 cla-backend-go/utils/cla_user.go diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 122d27850..cc7c273ed 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -5,6 +5,9 @@ package events import ( "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/utils" ) // EventData returns event data string which is used for event logging and containsPII field @@ -85,6 +88,8 @@ type SignatureInvalidatedApprovalRejectionEventData struct { GHUsername string Email string SignatureID string + CLAManager *models.User + CLAGroupID string } // UserCreatedEventData . . . @@ -712,7 +717,7 @@ func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventDetailsString( } else if ed.GHUsername != "" { reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) } - data := fmt.Sprintf("Signature ID: %s invalidated (approved set to false) due to %s ", ed.SignatureID, reason) + data := fmt.Sprintf("Signature ID: %s invalidated by %s (approved set to false) due to %s ", utils.GetBestUsername(ed.CLAManager), ed.SignatureID, reason) return data, true } diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 8db6e86ec..a4cd66efe 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -355,27 +355,6 @@ func (s *service) loadUser(ctx context.Context, args *LogEventArgs) error { return nil } -func (s *service) loadLFUser(ctx context.Context, args *LogEventArgs) error { - f := logrus.Fields{ - "functionName": "v1.events.service.loadLFUser", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - } - - if args.LFUser != nil { - return nil - } - - if args.LfUsername != "" { - lfUser, lfUserErr := user_service.GetClient().GetUserByUsername(args.LfUsername) - if lfUserErr != nil || lfUser == nil { - log.WithFields(f).Warnf("unable to fetch LF User by username: %s", args.LfUsername) - return nil - } - args.LFUser = lfUser - } - return nil -} - // loadDetails fetches and sets additional information into the data model required to fill out the event log entry func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ diff --git a/cla-backend-go/project/handlers.go b/cla-backend-go/project/handlers.go index 1d9ea2c6a..35f9acf67 100644 --- a/cla-backend-go/project/handlers.go +++ b/cla-backend-go/project/handlers.go @@ -237,7 +237,8 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser // Invalidate project signatures log.WithFields(f).Debug("Invalidating signatures") - note := fmt.Sprintf("Signature invalidated (approved set to false) due to CLA Group/Project: %s deletion", params.ProjectID) + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to CLA Group/Project: %s deletion", claUser.LFUsername, params.ProjectID) + howMany, err = signatureService.InvalidateProjectRecords(ctx, params.ProjectID, note) if err != nil { return project.NewDeleteProjectByIDBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err)) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 501de32db..9318de921 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -68,7 +68,7 @@ type SignatureRepository interface { GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) ProjectSignatures(ctx context.Context, projectID string) (*models.Signatures, error) - UpdateApprovalList(ctx context.Context, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) + UpdateApprovalList(ctx context.Context, claManager *models.User, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) RemoveCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) @@ -1944,7 +1944,8 @@ func (repo repository) RemoveCLAManager(ctx context.Context, signatureID, claMan } // UpdateApprovalList updates the specified project/company signature with the updated approval list information -func (repo repository) UpdateApprovalList(ctx context.Context, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) { // nolint +func (repo repository) UpdateApprovalList(ctx context.Context, claManager *models.User, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) { // nolint + f := logrus.Fields{ "functionName": "UpdateApprovalList", "projectID": projectID, @@ -2024,17 +2025,22 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan return } log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) - note := fmt.Sprintf("Signature invalidated (approved set to false) due to email approval list removal : %+v", params.RemoveEmailApprovalList) + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to email approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveEmailApprovalList) + signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) if signErr != nil { log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) return } + //Log Event eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ SignatureID: signs.Signatures[0].SignatureID, Email: email, + CLAManager: claManager, + CLAGroupID: signs.Signatures[0].ProjectID, } + repo.eventsService.LogEventWithContext(ctx, eventArgs) }(email) } @@ -2100,16 +2106,19 @@ func (repo repository) UpdateApprovalList(ctx context.Context, projectID, compan return } log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) - note := fmt.Sprintf("Signature invalidated (approved set to false) due to ghUsernames approval list removal : %+v", params.RemoveGithubUsernameApprovalList) + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to ghUsernames approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveGithubUsernameApprovalList) signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) if signErr != nil { log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) return } + // Log Event eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ SignatureID: signs.Signatures[0].SignatureID, GHUsername: ghUsername, + CLAManager: claManager, + CLAGroupID: signs.Signatures[0].ProjectID, } repo.eventsService.LogEventWithContext(ctx, eventArgs) }(ghUsername) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 227140675..d0ed0542f 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -434,7 +434,9 @@ func (s service) UpdateApprovalList(ctx context.Context, authUser *auth.User, cl UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, } - updatedSig, err := s.repo.UpdateApprovalList(ctx, claGroupModel.ProjectID, companyModel.CompanyID, params, eventArgs) + + updatedSig, err := s.repo.UpdateApprovalList(ctx, userModel, claGroupModel.ProjectID, companyModel.CompanyID, params, eventArgs) + if err != nil { return updatedSig, err } diff --git a/cla-backend-go/utils/cla_user.go b/cla-backend-go/utils/cla_user.go new file mode 100644 index 000000000..8fc062b19 --- /dev/null +++ b/cla-backend-go/utils/cla_user.go @@ -0,0 +1,25 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "github.com/communitybridge/easycla/cla-backend-go/gen/models" +) + +// GetBestUsername gets best username of CLA User +func GetBestUsername(user *models.User) string { + if user.Username != "" { + return user.Username + } + + if user.GithubUsername != "" { + return user.GithubUsername + } + + if user.LfUsername != "" { + return user.LfUsername + } + + return "User Name Unknown" +} From 853d27e841c71138e85250a3422d6072ede06248 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 19 Mar 2021 02:38:06 +0300 Subject: [PATCH 0171/1276] [#2795] Feature/Approval Email to Contributor (#2802) - Updated email content with gerrit steps Signed-off-by: wanyaland --- cla-backend-go/signatures/service.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index d0ed0542f..0a384ef4f 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -844,7 +844,13 @@ func sendRequestAccessEmailToContributorRecipient(authUser *auth.User, companyMo

    This is a notification email from EasyCLA regarding the project %s.

    You have been %s %s the Approval List of %s for %s by CLA Manager %s. This means that %s.

    -

    If you had previously submitted a pull request to EasyCLA Test Group that had failed, you can now go back to it, re-click the “Not Covered” button in the EasyCLA message in your pull request, and then follow these steps

    +

    If you are a GitHub user and If you had previously submitted a pull request to EasyCLA Test Group that had failed, you can now go back to it, re-click the “Not Covered” button in the EasyCLA message in your pull request, and then follow these steps

    +
      +
    1. Select “Corporate Contributor”.
    2. +
    3. Select your company from the organization drop down list
    4. +
    5. Click Proceed
    6. +
    +

    If you are a Gerrit user and if you had previously submitted a pull request to EasyCLA Test Group that had failed, then navigate to Agreements Settings page on Gerrit, click on "New Contributor Agreement" link under Agreements section, select the radio button corresponding to Corporate CLA, click on "Please review the agreement" link, and then follow these steps

    1. Select “Corporate Contributor”.
    2. Select your company from the organization drop down list
    3. From 520fc7b546738d6ef674d6d3f3fd9ac08da51303 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 18 Mar 2021 17:11:28 -0700 Subject: [PATCH 0172/1276] Resolves 2796 - Set Enable Services = CLA for Project Parent (#2803) - Added logic to set the enabled services for parent with the appropriate configuration is set - Added new configuration for this flag (cla-enable-services-for-parent-{stage}) Signed-off-by: David Deal --- cla-backend-go/config/config.go | 3 +++ cla-backend-go/config/ssm.go | 10 ++++++++ cla-backend-go/v2/cla_groups/helpers.go | 32 +++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index b0541c7e7..74fe8086b 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -24,6 +24,9 @@ type Config struct { // API GW APIGatewayURL string `json:"api_gateway_url"` + // EnableCLAServiceForParent is a configuration flag to indicate if we should set the enable_services=[CLA] attribute on the parent project object in the project service when a child project is associated with a CLA group. This determines the v2 project console experience/behavior." + EnableCLAServiceForParent bool `json:"enable_cla_service_for_parent"` + // SFDC // GitHub diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index ea3d85c22..683b794a4 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -93,6 +93,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-lfx-metrics-report-sqs-region-%s", stage), fmt.Sprintf("cla-lfx-metrics-report-sqs-url-%s", stage), fmt.Sprintf("cla-lfx-metrics-report-enabled-%s", stage), + fmt.Sprintf("cla-enable-services-for-parent-%s", stage), } // For each key to lookup @@ -208,6 +209,15 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint } else { config.MetricsReport.Enabled = boolVal } + case fmt.Sprintf("cla-enable-services-for-parent-%s", stage): + boolVal, err := strconv.ParseBool(resp.value) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to convert %s value to a boolean - setting value to false in the configuration", + fmt.Sprintf("cla-enable-services-for-parent-%s", stage)) + config.EnableCLAServiceForParent = false + } else { + config.EnableCLAServiceForParent = boolVal + } } } diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index cb5555221..1b96b6234 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -10,6 +10,8 @@ import ( "strings" "sync" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -495,10 +497,10 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, cla // Execute as a go routine go func(psClient *v2ProjectService.Client, claGroupID, projectSFID string) { defer wg.Done() + log.WithFields(f).Debugf("enabling project CLA service for project: %s...", projectSFID) enableProjectErr := psClient.EnableCLA(projectSFID) if enableProjectErr != nil { - log.WithFields(f).WithError(enableProjectErr). - Warnf("unable to enable CLA service for project: %s, error: %+v", projectSFID, enableProjectErr) + log.WithFields(f).WithError(enableProjectErr).Warnf("unable to enable CLA service for project: %s, error: %+v", projectSFID, enableProjectErr) errorList = append(errorList, enableProjectErr) } else { log.WithFields(f).Debugf("enabled CLA service for project: %s", projectSFID) @@ -510,6 +512,32 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, cla LfUsername: authUser.UserName, EventData: &events.ProjectServiceCLAEnabledData{}, }) + + // If we should enable the CLA Service for the parent + if config.GetConfig().EnableCLAServiceForParent { + log.WithFields(f).Debugf("enable parent project CLA service when child is enrolled flag is enabled") + parentProjectSFID, parentLookupErr := psc.GetParentProject(projectSFID) + if parentLookupErr != nil || parentProjectSFID == "" { + log.WithFields(f).WithError(parentLookupErr).Warnf("unable to lookup parent project SFID for project: %s", projectSFID) + } else { + log.WithFields(f).Debugf("enabling parent project CLA service for project SFID: %s...", parentProjectSFID) + enableProjectErr := psClient.EnableCLA(parentProjectSFID) + if enableProjectErr != nil { + log.WithFields(f).WithError(enableProjectErr).Warnf("unable to enable CLA service for project: %s, error: %+v", parentProjectSFID, enableProjectErr) + errorList = append(errorList, enableProjectErr) + } else { + log.WithFields(f).Debugf("enabled CLA service for parent project: %s", parentProjectSFID) + // add event log entry + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ProjectServiceCLAEnabled, + ProjectID: parentProjectSFID, + CLAGroupID: claGroupID, + LfUsername: authUser.UserName, + EventData: &events.ProjectServiceCLAEnabledData{}, + }) + } + } + } } }(psc, claGroupID, projectSFID) } From 309e3b7c4a2f8477cf7c9854b78836b830191ab3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 18 Mar 2021 17:35:29 -0700 Subject: [PATCH 0173/1276] Added Additional Project Service Logging (#2804) Signed-off-by: David Deal --- cla-backend-go/v2/project-service/client.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 54ecb21cc..a8e438beb 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -36,14 +36,15 @@ var ( projectServiceClient *Client // Short term cache - only for the lifetime of this lambda projectServiceModes = make(map[string]*models.ProjectOutputDetailed) + apiGWHost string ) // InitClient initializes the user_service client func InitClient(APIGwURL string) { - APIGwURL = strings.ReplaceAll(APIGwURL, "https://", "") + apiGWHost = strings.ReplaceAll(APIGwURL, "https://", "") projectServiceClient = &Client{ cl: client.NewHTTPClientWithConfig(strfmt.Default, &client.TransportConfig{ - Host: APIGwURL, + Host: apiGWHost, BasePath: "project-service", Schemes: []string{"https"}, }), @@ -70,6 +71,7 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed f := logrus.Fields{ "functionName": "v2.project-service.client.GetProject", "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } // Lookup in cache first @@ -106,6 +108,7 @@ func (pmm *Client) GetProjectByName(projectName string) (*models.ProjectListSear f := logrus.Fields{ "functionName": "v2.project-service.client.GetProjectByName", "projectName": projectName, + "apiGWHost": apiGWHost, } tok, err := token.GetToken() if err != nil { @@ -130,6 +133,7 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { f := logrus.Fields{ "functionName": "v2.project-service.client.GetParentProject", "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } // Lookup in cache first @@ -167,6 +171,7 @@ func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { f := logrus.Fields{ "functionName": "v2.project-service.client.IsTheLinuxFoundation", "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } log.WithFields(f).Debug("querying project...") @@ -190,6 +195,7 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) f := logrus.Fields{ "functionName": "v2.project-service.client.IsParentTheLinuxFoundation", "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } log.WithFields(f).Debug("querying project...") @@ -223,6 +229,7 @@ func (pmm *Client) EnableCLA(projectSFID string) error { f := logrus.Fields{ "functionName": "v2.project-service.client.EnableCLA", "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } tok, err := token.GetToken() @@ -273,6 +280,7 @@ func (pmm *Client) DisableCLA(projectSFID string) error { f := logrus.Fields{ "functionName": "v2.project-service.client.DisableCLA", "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } tok, err := token.GetToken() From eb7becafb593a44603a55e828e597c648bffaad3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 19 Mar 2021 10:14:27 -0700 Subject: [PATCH 0174/1276] Added New Platform API Gateway Key (#2805) Signed-off-by: David Deal --- cla-backend-go/cmd/server.go | 8 ++++---- cla-backend-go/config/config.go | 4 +++- cla-backend-go/config/ssm.go | 3 +++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index ad3a6d9e8..ca56957f6 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -257,10 +257,10 @@ func server(localMode bool) http.Handler { // Initialize the external platform services - these are external APIs that // we download the swagger specification, generate the models, and have //client helper functions - user_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) - project_service.InitClient(configFile.APIGatewayURL) - organization_service.InitClient(configFile.APIGatewayURL, eventsService) - acs_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) + user_service.InitClient(configFile.PlatformAPIGatewayURL, configFile.AcsAPIKey) + project_service.InitClient(configFile.PlatformAPIGatewayURL) + organization_service.InitClient(configFile.PlatformAPIGatewayURL, eventsService) + acs_service.InitClient(configFile.PlatformAPIGatewayURL, configFile.AcsAPIKey) usersService := users.NewService(usersRepo, eventsService) healthService := health.New(Version, Commit, Branch, BuildDate) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 74fe8086b..704982d4e 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -21,8 +21,10 @@ type Config struct { // Auth0Platform config Auth0Platform Auth0Platform `json:"auth0_platform"` - // API GW + // APIGatewayURL is the API gateway URL - old variable which is set by the old cla-auth0-gateway SSM key APIGatewayURL string `json:"api_gateway_url"` + // PlatformAPIGatewayURL is the platform API gateway URL + PlatformAPIGatewayURL string `json:"platform_api_gateway_url"` // EnableCLAServiceForParent is a configuration flag to indicate if we should set the enable_services=[CLA] attribute on the parent project object in the project service when a child project is associated with a CLA group. This determines the v2 project console experience/behavior." EnableCLAServiceForParent bool `json:"enable_cla_service_for_parent"` diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 683b794a4..19e5c358a 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -94,6 +94,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-lfx-metrics-report-sqs-url-%s", stage), fmt.Sprintf("cla-lfx-metrics-report-enabled-%s", stage), fmt.Sprintf("cla-enable-services-for-parent-%s", stage), + fmt.Sprintf("cla-platform-api-gw-%s", stage), } // For each key to lookup @@ -182,6 +183,8 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.Auth0Platform.URL = resp.value case fmt.Sprintf("cla-auth0-platform-api-gw-%s", stage): config.APIGatewayURL = resp.value + case fmt.Sprintf("cla-platform-api-gw-%s", stage): + config.PlatformAPIGatewayURL = resp.value case fmt.Sprintf("cla-lf-group-client-id-%s", stage): config.LFGroup.ClientID = resp.value case fmt.Sprintf("cla-lf-group-client-secret-%s", stage): From 33ef67907025c7c28cfac7f12e8ddf8b6030e0d5 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 19 Mar 2021 17:05:38 -0700 Subject: [PATCH 0175/1276] Updated Gerrit Query (#2807) - Updated gerrit query to perform a query instead of a scan - Updated gerrit query filter - only search gerrits by CLA Group ID - ignore projectSFID - Updated logging Signed-off-by: David Deal --- cla-backend-go/gerrits/repository.go | 46 +++++----- cla-backend-go/gerrits/service.go | 13 +-- cla-backend-go/v2/gerrits/handlers.go | 116 +++++++++++++++++--------- 3 files changed, 111 insertions(+), 64 deletions(-) diff --git a/cla-backend-go/gerrits/repository.go b/cla-backend-go/gerrits/repository.go index 57c2c29a8..98b37b1aa 100644 --- a/cla-backend-go/gerrits/repository.go +++ b/cla-backend-go/gerrits/repository.go @@ -30,6 +30,7 @@ import ( // errors var ( ErrGerritNotFound = errors.New("gerrit not found") + HugePageSize = int64(10000) ) // Repository defines functions of V3Repositories @@ -61,7 +62,7 @@ type repo struct { // AddGerrit creates a new gerrit instance func (repo *repo) AddGerrit(ctx context.Context, input *models.Gerrit) (*models.Gerrit, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.AddGerrit", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } gerritID, err := uuid.NewV4() @@ -103,7 +104,7 @@ func (repo *repo) AddGerrit(ctx context.Context, input *models.Gerrit) (*models. // GetGerrit returns the gerrit instances based on the ID func (repo *repo) GetGerrit(ctx context.Context, gerritID string) (*models.Gerrit, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.GetGerrit", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritID": gerritID, } @@ -137,7 +138,7 @@ func (repo *repo) GetGerrit(ctx context.Context, gerritID string) (*models.Gerri func (repo repo) GetGerritsByID(ctx context.Context, ID string, IDType string) (*models.GerritList, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.GetGerritsByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ID": ID, "IDType": IDType, @@ -200,7 +201,7 @@ func (repo repo) GetGerritsByID(ctx context.Context, ID string, IDType string) ( func (repo repo) GetGerritsByProjectSFID(ctx context.Context, projectSFID string) (*models.GerritList, error) { f := logrus.Fields{ - "functionName": "GetGerritsByProjectSFID", + "functionName": "v1.gerrits.repository.GetGerritsByProjectSFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -259,34 +260,41 @@ func (repo repo) GetGerritsByProjectSFID(ctx context.Context, projectSFID string } // GetClaGroupGerrits returns the CLA Group gerrit instances based on the CLA Group ID and the project SFID -func (repo repo) GetClaGroupGerrits(ctx context.Context, projectID string, projectSFID *string) (*models.GerritList, error) { +func (repo repo) GetClaGroupGerrits(ctx context.Context, claGroupID string, projectSFID *string) (*models.GerritList, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.GetClaGroupGerrits", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectID": projectID, + "claGroupID": claGroupID, "projectSFID": projectSFID, } resultList := make([]*models.Gerrit, 0) - filter := expression.Name("project_id").Equal(expression.Value(projectID)) - if projectSFID != nil { - filter = filter.And(expression.Name("project_sfid").Equal(expression.Value(*projectSFID))) - } - expr, err := expression.NewBuilder().WithFilter(filter).Build() + condition := expression.Key("project_id").Equal(expression.Value(claGroupID)) + // No reason to add this additional filter for the v2 API + //if projectSFID != nil { + // filter = filter.And(expression.Name("project_sfid").Equal(expression.Value(*projectSFID))) + //} + + expr, err := expression.NewBuilder().WithKeyCondition(condition).Build() if err != nil { - log.WithFields(f).Warnf("error building expression for gerrit instances scan, error: %v", err) + log.WithFields(f).Warnf("error building expression for gerrit instances query, error: %v", err) return nil, err } + // Assemble the query input parameters - scanInput := &dynamodb.ScanInput{ + queryInput := &dynamodb.QueryInput{ ExpressionAttributeNames: expr.Names(), ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), FilterExpression: expr.Filter(), + ProjectionExpression: expr.Projection(), TableName: aws.String(repo.tableName), + IndexName: aws.String("gerrit-project-id-index"), + Limit: aws.Int64(HugePageSize), } for { - results, err := repo.dynamoDBClient.Scan(scanInput) + results, err := repo.dynamoDBClient.Query(queryInput) if err != nil { log.WithFields(f).WithError(err).Warnf("error retrieving gerrit instances, error: %v", err) return nil, err @@ -305,7 +313,7 @@ func (repo repo) GetClaGroupGerrits(ctx context.Context, projectID string, proje } if len(results.LastEvaluatedKey) != 0 { - scanInput.ExclusiveStartKey = results.LastEvaluatedKey + queryInput.ExclusiveStartKey = results.LastEvaluatedKey } else { break } @@ -322,7 +330,7 @@ func (repo repo) GetClaGroupGerrits(ctx context.Context, projectID string, proje // DeleteGerrit removes the gerrit instance based on the gerrit ID func (repo *repo) DeleteGerrit(ctx context.Context, gerritID string) error { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.DeleteGerrit", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritID": gerritID, } @@ -347,7 +355,7 @@ func (repo *repo) DeleteGerrit(ctx context.Context, gerritID string) error { func (repo *repo) ExistsByName(ctx context.Context, gerritName string) ([]*models.Gerrit, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.ExistsByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritName": gerritName, } @@ -419,7 +427,7 @@ func (repo *repo) ExistsByName(ctx context.Context, gerritName string) ([]*model func (repo *repo) ExistsByID(ctx context.Context, gerritID string) ([]*models.Gerrit, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.repository.ExistsByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritID": gerritID, } diff --git a/cla-backend-go/gerrits/service.go b/cla-backend-go/gerrits/service.go index 180f1fbbf..bb3a73646 100644 --- a/cla-backend-go/gerrits/service.go +++ b/cla-backend-go/gerrits/service.go @@ -49,7 +49,7 @@ func NewService(repo Repository, lfg *LFGroup) Service { func (s service) AddGerrit(ctx context.Context, claGroupID string, projectSFID string, params *models.AddGerritInput, claGroupModel *models.ClaGroup) (*models.Gerrit, error) { f := logrus.Fields{ - "functionName": "gerrits.AddGerrit", + "functionName": "v1.gerrits.AddGerrit", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "projectSFID": projectSFID, @@ -164,7 +164,7 @@ func (s service) GetGerritsByProjectSFID(ctx context.Context, projectSFID string func (s service) GetClaGroupGerrits(ctx context.Context, claGroupID string, projectSFID *string) (*models.GerritList, error) { f := logrus.Fields{ - "functionName": "gerrits.GetClaGroupGerrits", + "functionName": "v1.gerrits.GetClaGroupGerrits", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "projectSFID": *projectSFID, @@ -175,6 +175,7 @@ func (s service) GetClaGroupGerrits(ctx context.Context, claGroupID string, proj return nil, err } + log.WithFields(f).Debugf("discovered %d gerrits", len(responseModel.List)) // Add the repo list to the response model for _, gerrit := range responseModel.List { log.WithFields(f).Debugf("Processing gerrit URL: %s", gerrit.GerritURL) @@ -221,7 +222,7 @@ func extractGerritHost(gerritHost string, f logrus.Fields) (string, error) { func (s service) GetGerritRepos(ctx context.Context, gerritHost string) (*models.GerritRepoList, error) { f := logrus.Fields{ - "functionName": "gerrits.GetGerritRepos", + "functionName": "v1.gerrits.GetGerritRepos", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritName": gerritHost, } @@ -314,7 +315,7 @@ func buildContributorAgreementDetails(serverInfo *ServerInfo) []*models.GerritRe // listGerritRepos returns a list of gerrit repositories for the given gerrit host func listGerritRepos(ctx context.Context, gerritHost string) (map[string]GerritRepoInfo, error) { f := logrus.Fields{ - "functionName": "gerrits.listGerritRepos", + "functionName": "v1.gerrits.listGerritRepos", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritHost": gerritHost, } @@ -354,7 +355,7 @@ func listGerritRepos(ctx context.Context, gerritHost string) (map[string]GerritR // getGerritConfig returns the gerrit configuration for the specified host func getGerritConfig(ctx context.Context, gerritHost string) (*ServerInfo, error) { f := logrus.Fields{ - "functionName": "gerrits.getGerritConfig", + "functionName": "v1.gerrits.getGerritConfig", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritHost": gerritHost, } @@ -394,7 +395,7 @@ func getGerritConfig(ctx context.Context, gerritHost string) (*ServerInfo, error // getGerritAPIPath returns the path to the API based on the gerrit host func getGerritAPIPath(ctx context.Context, gerritHost string) (string, error) { f := logrus.Fields{ - "functionName": "gerrits.getGerritAPIPath", + "functionName": "v1.gerrits.getGerritAPIPath", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritHost": gerritHost, } diff --git a/cla-backend-go/v2/gerrits/handlers.go b/cla-backend-go/v2/gerrits/handlers.go index 364daef5e..af3fcdd8d 100644 --- a/cla-backend-go/v2/gerrits/handlers.go +++ b/cla-backend-go/v2/gerrits/handlers.go @@ -8,8 +8,12 @@ import ( "fmt" "strings" + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" @@ -22,6 +26,8 @@ import ( "github.com/jinzhu/copier" ) +const decodeErrorMsg = "unable to decode response as a v2 model" + type ProjectService interface { //nolint GetCLAGroupByID(ctx context.Context, claGroupID string) (*v1Models.ClaGroup, error) } @@ -33,35 +39,47 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsDeleteGerritHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSFID, + "claGroupID": params.ClaGroupID, + "gerritID": params.GerritID, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + log.WithFields(f).Debugf("querying for gerrits using gerrit ID: %s", params.GerritID) gerrit, err := v1Service.GetGerrit(ctx, params.GerritID) if err != nil { + msg := fmt.Sprintf("unable to locate gerrit by ID: %s", params.GerritID) + log.WithFields(f).Warn(msg) if err == v1Gerrits.ErrGerritNotFound { - return gerrits.NewDeleteGerritNotFound().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + return gerrits.NewDeleteGerritNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) } - return gerrits.NewDeleteGerritInternalServerError().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + return gerrits.NewDeleteGerritInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) } + if gerrit.ProjectSFID != params.ProjectSFID || gerrit.ProjectID != params.ClaGroupID { - return gerrits.NewDeleteGerritBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "EasyCLA - 403 Bad Request - projectSFID or claGroupID does not match with provided gerrit record", - XRequestID: reqID, - }) + msg := fmt.Sprintf("projectSFID %s or claGroupID %s does not match with provided gerrit record", params.ProjectSFID, params.ClaGroupID) + log.WithFields(f).Warn(msg) + return gerrits.NewDeleteGerritBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } + // verify user have access to the project if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - return gerrits.NewDeleteGerritForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to DeleteGerrit with Project scope of %s", - authUser.UserName, gerrit.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("user %s does not have access to DeleteGerrit with Project scope of %s", + authUser.UserName, gerrit.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewDeleteGerritForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } // delete the gerrit err = v1Service.DeleteGerrit(ctx, params.GerritID) if err != nil { - return gerrits.NewDeleteGerritBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + msg := "unable to delete gerrit instance" + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewDeleteGerritForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } // record the event @@ -148,39 +166,52 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsListGerritsHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSFID, + "claGroupID": params.ClaGroupID, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } // verify user have access to the project if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - return gerrits.NewListGerritsForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to ListGerrits with Project scope of %s", - authUser.UserName, params.ProjectSFID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("user %s does not have access to list gerrits with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewListGerritsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } + log.WithFields(f).Debug("checking if project CLA Group mapping...") ok, err := projectsClaGroupsRepo.IsAssociated(params.ProjectSFID, params.ClaGroupID) if err != nil { - return gerrits.NewListGerritsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + msg := fmt.Sprintf("unable to determine project CLA group association for project: %s and CLA Group: %s", params.ProjectSFID, params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewListGerritsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } + if !ok { - return gerrits.NewListGerritsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "provided cla-group and project are not associated with each other", - XRequestID: reqID, - }) + msg := fmt.Sprintf("provided CLA Group %s and project %s are not associated with each other", params.ProjectSFID, params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewListGerritsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } + log.WithFields(f).Debug("querying for gerrits...") result, err := v1Service.GetClaGroupGerrits(ctx, params.ClaGroupID, ¶ms.ProjectSFID) if err != nil { + msg := fmt.Sprintf("problem fetching gerrit repositories using CLA Group: %s with project SFID: %s", params.ClaGroupID, params.ProjectSFID) + log.WithFields(f).Warn(msg) return gerrits.NewListGerritsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } + log.WithFields(f).Debugf("discovered %d gerrits", len(result.List)) var response models.GerritList err = copier.Copy(&response, result) if err != nil { - return gerrits.NewListGerritsInternalServerError().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + log.WithFields(f).WithError(err).Warn(decodeErrorMsg) + return gerrits.NewListGerritsInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, decodeErrorMsg, err)) } + return gerrits.NewListGerritsOK().WithXRequestID(reqID).WithPayload(&response) }) @@ -189,35 +220,42 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsGetGerritReposHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "gerritHost": params.GerritHost.String(), + } // No specific permissions required // Validate input if params.GerritHost == nil { - return gerrits.NewGetGerritReposBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "missing gerritHost query parameter - expecting gerrit hostname", - XRequestID: reqID, - }) + msg := "missing gerrit host query parameter - expecting gerrit hostname" + log.WithFields(f).Warn(msg) + return gerrits.NewGetGerritReposBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } if len(strings.TrimSpace(params.GerritHost.String())) == 0 { - return gerrits.NewGetGerritReposBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "invalid gerritHost query parameter - expecting gerrit hostname", - XRequestID: reqID, - }) + msg := "invalid gerritHost query parameter - expecting gerrit hostname" + log.WithFields(f).Warn(msg) + return gerrits.NewGetGerritReposBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } + log.WithFields(f).Debugf("querying for gerrits using hostname: %s...", params.GerritHost.String()) result, err := v1Service.GetGerritRepos(ctx, params.GerritHost.String()) if err != nil { - return gerrits.NewGetGerritReposBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + msg := fmt.Sprintf("problem fetching gerrit repositories using gerrit host: %s", params.GerritHost.String()) + log.WithFields(f).Warn(msg) + return gerrits.NewGetGerritReposBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } var response models.GerritRepoList err = copier.Copy(&response, result) if err != nil { - return gerrits.NewAddGerritInternalServerError().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + log.WithFields(f).WithError(err).Warn(decodeErrorMsg) + return gerrits.NewAddGerritInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, decodeErrorMsg, err)) } return gerrits.NewGetGerritReposOK().WithXRequestID(reqID).WithPayload(&response) From 0d88b3d6d997bd795b96407080d4725c5dec9038 Mon Sep 17 00:00:00 2001 From: makkalot Date: Tue, 23 Mar 2021 13:03:59 +0200 Subject: [PATCH 0176/1276] remove CLA Manager console link from icla email Signed-off-by: makkalot --- cla-backend/cla/models/docusign_models.py | 27 +++++++++++++------ .../cla/tests/unit/test_docusign_models.py | 6 +++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index bea387f4d..836187325 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -2220,14 +2220,25 @@ def document_signed_email_content(icla: bool, project: Project, signature: Signa recipient_name = "CLA Manager" subject = f'EasyCLA: CLA Signed for {project.get_project_name()}' - body = f''' -

      Hello {recipient_name},

      -

      This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

      -

      The CLA has now been signed. You can download the signed CLA as a PDF - - here, or from within the EasyCLA CLA Manager console . -

      - ''' + + if icla: + body = f''' +

      Hello {recipient_name},

      +

      This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

      +

      The CLA has now been signed. You can download the signed CLA as a PDF + + here. +

      + ''' + else: + body = f''' +

      Hello {recipient_name},

      +

      This is a notification email from EasyCLA regarding the project {project.get_project_name()}.

      +

      The CLA has now been signed. You can download the signed CLA as a PDF + + here, or from within the EasyCLA CLA Manager console . +

      + ''' body = append_email_help_sign_off_content(body, project.get_version()) return subject, body diff --git a/cla-backend/cla/tests/unit/test_docusign_models.py b/cla-backend/cla/tests/unit/test_docusign_models.py index fc95cefbd..aaa6021aa 100644 --- a/cla-backend/cla/tests/unit/test_docusign_models.py +++ b/cla-backend/cla/tests/unit/test_docusign_models.py @@ -826,6 +826,7 @@ def test_document_signed_email_content(): assert "Hello john" in body assert "EasyCLA regarding the project JohnsProject" in body assert "The CLA has now been signed." in body + assert "alt=\"CCLA Document Link\"" in body # try with different recipient names user.set_user_name(None) @@ -858,7 +859,12 @@ def test_document_signed_email_content(): user=user ) + assert "Signed for JohnsProject" in subject assert "Hello Contributor" in body + assert "EasyCLA regarding the project JohnsProject" in body + assert "The CLA has now been signed." in body + assert "alt=\"ICLA Document Link\"" in body + assert "EasyCLA CLA Manager console" not in body def test_cla_signatory_email_content(): From 12691ddc231f2ca06f41adc6eba1c12dd67bcfb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 11:25:27 -0700 Subject: [PATCH 0177/1276] Bump elliptic from 6.5.3 to 6.5.4 in /cla-frontend-contributor-console/edge (#2764) Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4. - [Release notes](https://github.com/indutny/elliptic/releases) - [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../edge/yarn.lock | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock index 726831108..3bc944542 100644 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ b/cla-frontend-contributor-console/edge/yarn.lock @@ -396,10 +396,10 @@ block-stream@*: dependencies: inherits "~2.0.0" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bn.js@^5.1.1: version "5.1.2" @@ -437,7 +437,7 @@ braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -910,17 +910,17 @@ ecc-jsbn@~0.1.1: safer-buffer "^2.1.0" elliptic@^6.0.0, elliptic@^6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emojis-list@^3.0.0: version "3.0.0" @@ -1430,7 +1430,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -2026,7 +2026,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= From 6794f1eb129078186e7873a483e011f96fd11a05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 11:25:56 -0700 Subject: [PATCH 0178/1276] Bump elliptic from 6.5.3 to 6.5.4 in /cla-frontend-project-console/src (#2763) Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4. - [Release notes](https://github.com/indutny/elliptic/releases) - [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/yarn.lock | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 099e00971..610bff4d9 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -491,10 +491,10 @@ block-stream@*: dependencies: inherits "~2.0.0" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== body-parser@1.19.0: version "1.19.0" @@ -557,7 +557,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -1161,17 +1161,17 @@ electron-to-chromium@^1.3.30: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.185.tgz#36368514eb719632a91435f3c9f57c98b47d81de" elliptic@^6.0.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emojis-list@^2.0.0: version "2.1.0" @@ -1828,7 +1828,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -1935,7 +1935,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2561,7 +2561,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= From 998f7fa320047365793908c2ce73910a1706386a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 11:26:34 -0700 Subject: [PATCH 0179/1276] Bump elliptic from 6.5.3 to 6.5.4 in /cla-frontend-contributor-console/src (#2761) Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4. - [Release notes](https://github.com/indutny/elliptic/releases) - [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/yarn.lock | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 58f389e40..1cd7df705 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -473,10 +473,10 @@ block-stream@*: dependencies: inherits "~2.0.0" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== body-parser@1.19.0: version "1.19.0" @@ -539,7 +539,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -1143,17 +1143,17 @@ electron-to-chromium@^1.3.30: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.185.tgz#36368514eb719632a91435f3c9f57c98b47d81de" elliptic@^6.0.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emojis-list@^2.0.0: version "2.1.0" @@ -1805,7 +1805,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -1912,7 +1912,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2506,7 +2506,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= From 8313e5c437d4f166e316317ba136a6085af052c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 11:27:27 -0700 Subject: [PATCH 0180/1276] Bump elliptic from 6.5.3 to 6.5.4 in /cla-frontend-corporate-console/src (#2762) Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4. - [Release notes](https://github.com/indutny/elliptic/releases) - [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/yarn.lock | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index eaafcb25d..cbcf7ced9 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -469,10 +469,10 @@ block-stream@*: dependencies: inherits "~2.0.0" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== body-parser@1.19.0: version "1.19.0" @@ -535,7 +535,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -1130,17 +1130,17 @@ electron-to-chromium@^1.3.30: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.185.tgz#36368514eb719632a91435f3c9f57c98b47d81de" elliptic@^6.0.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emojis-list@^2.0.0: version "2.1.0" @@ -1775,7 +1775,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -1870,7 +1870,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2455,7 +2455,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= From 6f083950332ad9c8d2c0768a4d4cf3bba454495b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 12:00:23 -0700 Subject: [PATCH 0181/1276] Bump lxml from 4.6.2 to 4.6.3 in /cla-backend (#2809) Bumps [lxml](https://github.com/lxml/lxml) from 4.6.2 to 4.6.3. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-4.6.2...lxml-4.6.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index ec9c6d220..eec1d6348 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -25,7 +25,7 @@ Jinja2==2.11.3 jmespath==0.9.4 lazy-object-proxy==1.4.3 Logbook==1.5.3 -lxml==4.6.2 +lxml==4.6.3 more-itertools==8.0.2 nose2==0.9.1 oauthlib==3.1.0 From 5a3d63ffebca3a0afd7bbe79247d8d8e745089c4 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 24 Mar 2021 03:06:40 +0300 Subject: [PATCH 0182/1276] [#2792] Feature/Invalidate Contributors (#2810) - Handled signature invalidations for ghorg and domain approval removals - Implemented ghorg client for getting users under ghorg - Handled ICLA and ECLA signature updates Signed-off-by: wanyaland --- .../cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/github/github_org.go | 30 +++ cla-backend-go/go.sum | 2 + cla-backend-go/signatures/models.go | 14 ++ cla-backend-go/signatures/repository.go | 211 +++++++++++++++++- cla-backend-go/utils/constants.go | 15 ++ 7 files changed, 273 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 8cdcfbe47..01ed9b077 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -119,7 +119,7 @@ func init() { projectClaGroupRepo, }) - signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo, eventsService) + signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo) usersService := users.NewService(usersRepo, eventsService) companyService := company.NewService(companyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index ca56957f6..581e55a9f 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -252,7 +252,7 @@ func server(localMode bool) http.Handler { }) // Signature repository handler - signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService) + signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo) // Initialize the external platform services - these are external APIs that // we download the swagger specification, generate the models, and have diff --git a/cla-backend-go/github/github_org.go b/cla-backend-go/github/github_org.go index 3ba8774fc..fb8df7581 100644 --- a/cla-backend-go/github/github_org.go +++ b/cla-backend-go/github/github_org.go @@ -44,3 +44,33 @@ func GetOrganization(ctx context.Context, organizationName string) (*github.Orga } return org, nil } + +//GetOrganizationMembers gets members in organization +func GetOrganizationMembers(ctx context.Context, orgName string, installationID int64) ([]string, error) { + f := logrus.Fields{ + "functionName": "GetOrganizationMembers", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + client, err := NewGithubAppClient(installationID) + if err != nil { + msg := fmt.Sprintf("unable to create a github client, error: %+v", err) + log.WithFields(f).WithError(err).Warn(msg) + return nil, errors.New(msg) + } + + users, resp, err := client.Organizations.ListMembers(ctx, orgName, nil) + + if resp.StatusCode < 200 || resp.StatusCode > 299 || err != nil { + msg := fmt.Sprintf("List Org Members failed for Organization: %s with no success response code %d. error = %s", orgName, resp.StatusCode, err.Error()) + log.WithFields(f).Warnf(msg) + return nil, errors.New(msg) + } + + var ghUsernames []string + for _, user := range users { + log.WithFields(f).Debugf("user :%s found for organization: %s", *user.Login, orgName) + ghUsernames = append(ghUsernames, *user.Login) + } + return ghUsernames, nil +} diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 02cb0d7bd..19499d7aa 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -95,6 +95,8 @@ github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNuti github.com/communitybridge/easycla v1.0.123 h1:Lh5i/9aajrTYItxNpVCmi9T1yyIfnQIOk0tC2Wtslvk= github.com/communitybridge/easycla v1.0.133 h1:aJulQGLLRISCMsZcCP4aIE8xGtHoBNm/EmA00n3NYVA= github.com/communitybridge/easycla v1.0.135 h1:Dvn8jX+7BAnpmA+jvdK0n5ajWP8SoH5vvopt7whZDEU= +github.com/communitybridge/easycla v1.0.145 h1:ikhBSsOeEL2u3/EoyDsufh/j3HkjfFTiXAk1d61GoS8= +github.com/communitybridge/easycla v2.0.10+incompatible h1:6eRJ5fxrMxRZHBkg8piYo+zHTcSowMrP85nZXzp5mpA= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 7cc53f4d2..dcc2b2e2a 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -16,3 +16,17 @@ type ApprovalCriteria struct { UserEmail string GitHubUsername string } + +//ApprovalList ... +type ApprovalList struct { + Criteria string + ApprovalList []string + Action string + ClaGroupID string + CompanyID string + DomainApprovals []string + GHOrgApprovals []string + GitHubUsernameApprovals []string + EmailApprovals []string + GHUsernames []string +} diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 9318de921..edf7a89b5 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -21,6 +21,9 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/communitybridge/easycla/cla-backend-go/github_organizations" + "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" @@ -90,17 +93,21 @@ type repository struct { companyRepo company.IRepository usersRepo users.UserRepository eventsService events.Service + repositoriesRepo repositories.Repository + ghOrgRepo github_organizations.Repository signatureTableName string } // NewRepository creates a new instance of the whitelist service -func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service) SignatureRepository { +func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.Repository) SignatureRepository { return repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), companyRepo: companyRepo, usersRepo: usersRepo, eventsService: eventsService, + repositoriesRepo: repositoriesRepo, + ghOrgRepo: ghOrgRepo, signatureTableName: fmt.Sprintf("cla-%s-signatures", stage), } } @@ -1975,6 +1982,22 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model companyID, projectID, signed, approved) } + // Get CCLA signature - For Approval List info + cclaSignature, err := repo.GetCorporateSignature(ctx, projectID, companyID) + if err != nil { + msg := "unable to get corporate signature" + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + + // Keep track of existing company approvals + approvalList := ApprovalList{ + DomainApprovals: cclaSignature.DomainApprovalList, + GHOrgApprovals: cclaSignature.GithubOrgApprovalList, + GitHubUsernameApprovals: cclaSignature.GithubUsernameApprovalList, + EmailApprovals: cclaSignature.EmailApprovalList, + } + // Just grab and use the first one - need to figure out conflict resolution if more than one sig := sigs.Signatures[0] expressionAttributeNames := map[string]*string{} @@ -2049,6 +2072,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if params.AddDomainApprovalList != nil || params.RemoveDomainApprovalList != nil { + columnName := "domain_whitelist" attrList := buildApprovalAttributeList(ctx, sig.DomainApprovalList, params.AddDomainApprovalList, params.RemoveDomainApprovalList) // If no entries after consolidating all the updates, we need to remove the column @@ -2067,6 +2091,18 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model expressionAttributeValues[":d"] = attrList updateExpression = updateExpression + " #D = :d, " } + if params.RemoveDomainApprovalList != nil { + var invalidateErr error + approvalList.Criteria = utils.EmailDomainCriteria + approvalList.ApprovalList = params.RemoveDomainApprovalList + approvalList.Action = utils.RemoveApprovals + invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) + if invalidateErr != nil { + msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + } } if params.AddGithubUsernameApprovalList != nil || params.RemoveGithubUsernameApprovalList != nil { @@ -2147,6 +2183,57 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model expressionAttributeValues[":go"] = attrList updateExpression = updateExpression + " #GO = :go, " } + + if params.RemoveGithubOrgApprovalList != nil { + var invalidateErr error + approvalList.Criteria = utils.GitHubOrgCriteria + approvalList.ApprovalList = params.RemoveGithubOrgApprovalList + approvalList.Action = utils.RemoveApprovals + // Get repositories by CLAGroup + repositories, err := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) + if err != nil { + msg := fmt.Sprintf("unable to fetch repositories for claGroupID: %s ", projectID) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + var ghOrgRepositories []*models.GithubRepository + var ghOrgs []*models.GithubOrganization + for _, repository := range repositories { + // Check for matching organization name in repositories table against approvalList removal GH Orgs + if utils.StringInSlice(repository.RepositoryOrganizationName, approvalList.ApprovalList) { + ghOrgRepositories = append(ghOrgRepositories, repository) + } + } + + for _, ghOrgRepo := range ghOrgRepositories { + ghOrg, err := repo.ghOrgRepo.GetGithubOrganization(ctx, ghOrgRepo.RepositoryOrganizationName) + if err != nil { + msg := fmt.Sprintf("unable to get gh org by name: %s ", ghOrgRepo.RepositoryOrganizationName) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + ghOrgs = append(ghOrgs, ghOrg) + } + + var ghUsernames []string + for _, ghOrg := range ghOrgs { + ghOrgUsers, err := github.GetOrganizationMembers(ctx, ghOrg.OrganizationName, ghOrg.OrganizationInstallationID) + if err != nil { + msg := fmt.Sprintf("unable to fetch ghOrgUsers for org: %s ", ghOrg.OrganizationName) + log.WithFields(f).Warnf(msg) + return nil, errors.New(msg) + } + ghUsernames = append(ghUsernames, ghOrgUsers...) + } + approvalList.GHUsernames = utils.RemoveDuplicates(ghUsernames) + + invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) + if invalidateErr != nil { + msg := fmt.Sprintf("unable to invalidate signatures based on Approval List: %+v ", approvalList) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + } } // Ensure at least one value is set for us to update @@ -2206,6 +2293,128 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return updatedSig.Signatures[0], nil } +// invalidateSignatures is a helper function that invalidates signature records based on approval list +func (repo repository) invalidateSignatures(ctx context.Context, approvalList *ApprovalList, claManager *models.User) error { + f := logrus.Fields{ + "functionName": "invalidateSignatures", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": &approvalList, + } + + // Get ICLAs + iclas, err := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil) + if err != nil { + log.WithFields(f).Warn("unable to get iclas") + return err + } + + // Get ECLAs + companyProjectParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ + CompanyID: approvalList.CompanyID, + ProjectID: approvalList.ClaGroupID, + } + eclas, err := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, nil, int64(10)) + if err != nil { + log.WithFields(f).Warnf("unable to get cclas for company: %s and project: %s ", approvalList.CompanyID, approvalList.ClaGroupID) + return err + } + + var iclaWg, eclaWg sync.WaitGroup + + //Iterate iclas + iclaWg.Add(len(iclas.List)) + log.WithFields(f).Debug("invalidating signature icla records... ") + + for _, icla := range iclas.List { + go func(icla *models.IclaSignature) { + defer iclaWg.Done() + signature, err := repo.GetSignature(ctx, icla.SignatureID) + if err != nil { + log.WithFields(f).Warnf("unable to fetch signature for ID: %s ", icla.SignatureID) + return + } + // Grab user record + if signature.SignatureReferenceID == "" { + log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", signature) + return + } + verifyErr := repo.verifyUserApprovals(ctx, signature.SignatureReferenceID, signature.SignatureID, claManager, approvalList) + if verifyErr != nil { + log.WithFields(f).Warnf("unable to verify user: %s ", signature.SignatureReferenceID) + return + } + }(icla) + } + iclaWg.Wait() + + log.WithFields(f).Debug("invalidating signature ecla records... ") + // Iterate eclas + eclaWg.Add(len(eclas.Signatures)) + for _, ecla := range eclas.Signatures { + go func(ecla *models.Signature) { + defer eclaWg.Done() + // Grab user record + if ecla.SignatureReferenceID == "" { + log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", ecla) + return + } + verifyErr := repo.verifyUserApprovals(ctx, ecla.SignatureReferenceID, ecla.SignatureID, claManager, approvalList) + if verifyErr != nil { + log.WithFields(f).Warnf("unable to verify user: %s ", ecla.SignatureReferenceID) + return + } + }(ecla) + } + eclaWg.Wait() + + return nil +} + +// verify UserApprovals checks user +func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatureID string, claManager *models.User, approvalList *ApprovalList) error { + f := logrus.Fields{ + "functionName": "verifyUserApprovals", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "userID": userID, + } + + user, err := repo.usersRepo.GetUser(userID) + if err != nil { + log.WithFields(f).Warnf("unable to get user record for ID: %s ", userID) + return err + } + + if approvalList.Criteria == utils.EmailDomainCriteria { + // Handle Domains + if utils.StringInSlice(getBestEmail(user), approvalList.DomainApprovals) { + if !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) && !utils.StringInSlice(getBestEmail(user), approvalList.EmailApprovals) { + //Invalidate record + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), utils.EmailDomainCriteria) + err := repo.InvalidateProjectRecord(ctx, signatureID, note) + if err != nil { + log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) + return err + } + } + } + } else if approvalList.Criteria == utils.GitHubOrgCriteria { + // Handle GH Org Approvals + if utils.StringInSlice(user.GithubUsername, approvalList.GHUsernames) { + if !utils.StringInSlice(getBestEmail(user), approvalList.EmailApprovals) && !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) { + //Invalidate record + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), utils.GitHubOrgCriteria) + err := repo.InvalidateProjectRecord(ctx, signatureID, note) + if err != nil { + log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) + return err + } + } + } + } + + return nil +} + // removeColumn is a helper function to remove a given column when we need to zero out the column value - typically the approval list func (repo repository) removeColumn(ctx context.Context, signatureID, columnName string) (*models.Signature, error) { f := logrus.Fields{ diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 4f22d383f..2336e11db 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -149,3 +149,18 @@ const EmailLabel = "Email Address" //UserLabel represents the LF/EasyCLA username const UserLabel = "Username" + +//EmailDomainCriteria represents approval based on email domain +const EmailDomainCriteria = "Email Domain Criteria" + +//EmailCriteria represents approvals based on email addresses +const EmailCriteria = "Email Criteria" + +//GitHubOrgCriteria represents approvals based on GH org membership +const GitHubOrgCriteria = "GitHub Org Criteria" + +//AddApprovals is an action for adding approvals +const AddApprovals = "AddApprovals" + +//RemoveApprovals is an action for removing approvals +const RemoveApprovals = "RemoveApprovals" From 8a66c9fad0d9382f9c4c3b5fd153bd95e27b7216 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 24 Mar 2021 10:43:32 -0700 Subject: [PATCH 0183/1276] [#2973] Added Gerrit Add/Remove User from Gerrit Logic (#2811) - Added GetUsersOfGroup service API - Added AddUserToGroup service API to add users to a given CLA Group/Gerrit instance - Added RemoveUserFromGroup service API to remove users to a given CLA Group/Gerrit instance - Added logging - Added HTTP API calls to the identity system - Added event logging Signed-off-by: David Deal --- cla-backend-go/cmd/server.go | 9 +- cla-backend-go/docraptor/client.go | 10 +- cla-backend-go/events/event_data.go | 56 ++++ cla-backend-go/events/event_types.go | 2 + cla-backend-go/gerrits/lf_group.go | 312 +++++++++++++++++- cla-backend-go/gerrits/repository.go | 11 +- cla-backend-go/gerrits/service.go | 225 ++++++++++++- cla-backend-go/project/helpers.go | 2 +- cla-backend-go/project/repository.go | 2 +- cla-backend-go/signatures/handlers.go | 37 ++- cla-backend-go/swagger/cla.v2.yaml | 205 ++++++++++++ .../swagger/common/gerrit-group-response.yaml | 38 +++ .../swagger/common/gerrit-user-list.yaml | 10 + cla-backend-go/utils/errors.go | 19 ++ cla-backend-go/v2/gerrits/handlers.go | 195 ++++++++++- 15 files changed, 1078 insertions(+), 55 deletions(-) create mode 100644 cla-backend-go/swagger/common/gerrit-group-response.yaml create mode 100644 cla-backend-go/swagger/common/gerrit-user-list.yaml diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 581e55a9f..4356536f7 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -285,10 +285,11 @@ func server(localMode bool) http.Handler { autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, eventsService, autoEnableService) gerritService := gerrits.NewService(gerritRepo, &gerrits.LFGroup{ - LfBaseURL: configFile.LFGroup.ClientURL, - ClientID: configFile.LFGroup.ClientID, - ClientSecret: configFile.LFGroup.ClientSecret, - RefreshToken: configFile.LFGroup.RefreshToken, + LfBaseURL: configFile.LFGroup.ClientURL, + ClientID: configFile.LFGroup.ClientID, + ClientSecret: configFile.LFGroup.ClientSecret, + RefreshToken: configFile.LFGroup.RefreshToken, + EventsService: eventsService, }) v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, projectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) diff --git a/cla-backend-go/docraptor/client.go b/cla-backend-go/docraptor/client.go index 419ec26e5..89ffd83da 100644 --- a/cla-backend-go/docraptor/client.go +++ b/cla-backend-go/docraptor/client.go @@ -49,7 +49,7 @@ func NewDocraptorClient(key string, testMode bool) (Client, error) { // CreatePDF accepts an HTML document and returns a PDF func (dc Client) CreatePDF(html string, claType string) (io.ReadCloser, error) { f := logrus.Fields{ - "functionName": "CreatePDF", + "functionName": "v1.docraptor.client.CreatePDF", "claType": claType, } @@ -69,9 +69,15 @@ func (dc Client) CreatePDF(html string, claType string) (io.ReadCloser, error) { log.WithFields(f).Debug("Generating PDF using docraptor...") resp, err := http.Post(dc.url, "application/json", bytes.NewBuffer(documentBytes)) if err != nil { - log.WithFields(f).Warnf("problem with API call to docraptor, error: %+v", err) + log.WithFields(f).WithError(err).Warn("problem with API call to docraptor") return nil, err } + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warn("error closing response body") + } + }() return resp.Body, nil } diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index cc7c273ed..75e7ab1f7 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -73,6 +73,18 @@ type GerritDeletedEventData struct { GerritRepositoryName string } +// GerritUserAddedEventData . . . +type GerritUserAddedEventData struct { + Username string + GroupName string +} + +// GerritUserRemovedEventData . . . +type GerritUserRemovedEventData struct { + Username string + GroupName string +} + // GitHubProjectDeletedEventData . . . type GitHubProjectDeletedEventData struct { DeletedCount int @@ -695,6 +707,18 @@ func (ed *GerritDeletedEventData) GetEventDetailsString(args *LogEventArgs) (str return data, true } +// GetEventDetailsString . . . +func (ed *GerritUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The username %s was add to the gerrit group %s by the user %s.", ed.Username, ed.GroupName, args.UserName) + return data, true +} + +// GetEventDetailsString . . . +func (ed *GerritUserRemovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The username %s was removed from the gerrit group %s by the user %s.", ed.Username, ed.GroupName, args.UserName) + return data, true +} + // GetEventDetailsString . . . func (ed *GitHubProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d GitHub Repositories were deleted due to CLA Group/Project: [%s] deletion.", @@ -1482,6 +1506,38 @@ func (ed *GerritDeletedEventData) GetEventSummaryString(args *LogEventArgs) (str return data, true } +// GetEventSummaryString . . . +func (ed *GerritUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The username %s was add to the gerrit group %s", ed.Username, ed.GroupName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString . . . +func (ed *GerritUserRemovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The username %s was removed from the gerrit group %s", ed.Username, ed.GroupName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventSummaryString . . . func (ed *GitHubProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d GitHub repositories were deleted due to CLA Group/project deletion", diff --git a/cla-backend-go/events/event_types.go b/cla-backend-go/events/event_types.go index d9753be8d..ae6f84e73 100644 --- a/cla-backend-go/events/event_types.go +++ b/cla-backend-go/events/event_types.go @@ -40,6 +40,8 @@ const ( GerritRepositoryAdded = "gerrit_repository.added" GerritRepositoryDeleted = "gerrit_repository.deleted" + GerritUserAdded = "gerrit_user.added" + GerritUserRemoved = "gerrit_user.deleted" GitHubOrganizationAdded = "github_organization.added" GitHubOrganizationDeleted = "github_organization.deleted" diff --git a/cla-backend-go/gerrits/lf_group.go b/cla-backend-go/gerrits/lf_group.go index 687401e66..f3b987b72 100644 --- a/cla-backend-go/gerrits/lf_group.go +++ b/cla-backend-go/gerrits/lf_group.go @@ -5,11 +5,21 @@ package gerrits import ( "bytes" + "context" "encoding/json" "fmt" "io/ioutil" "net/http" "time" + + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/communitybridge/easycla/cla-backend-go/events" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" ) // constants @@ -19,10 +29,11 @@ const ( // LFGroup contains access information of lf LDAP group type LFGroup struct { - LfBaseURL string - ClientID string - ClientSecret string - RefreshToken string + LfBaseURL string + ClientID string + ClientSecret string + RefreshToken string + EventsService events.Service } // LDAPGroup model @@ -30,18 +41,24 @@ type LDAPGroup struct { Title string `json:"title"` } -func (lfg *LFGroup) getAccessToken() (string, error) { +func (lfg *LFGroup) getAccessToken(ctx context.Context) (string, error) { + f := logrus.Fields{ + "functionName": "v1.gerrits.lf_group.getAccessToken", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } requestBody, err := json.Marshal(map[string]string{ "grant_type": "refresh_token", "refresh_token": lfg.RefreshToken, "scope": "manage_groups", }) if err != nil { + log.WithFields(f).WithError(err).Warn("problem encoding access token request") return "", err } OauthURL := fmt.Sprintf("%s/oauth2/token", lfg.LfBaseURL) req, err := http.NewRequest("POST", OauthURL, bytes.NewBuffer(requestBody)) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating a new request to URL: %s", OauthURL) return "", err } req.SetBasicAuth(lfg.ClientID, lfg.ClientSecret) @@ -52,32 +69,53 @@ func (lfg *LFGroup) getAccessToken() (string, error) { } res, err := client.Do(req) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem sending a request to URL: %s", OauthURL) return "", err } - defer res.Body.Close() + + defer func() { + closeErr := res.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warn("error closing response body") + } + }() + body, err := ioutil.ReadAll(res.Body) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading the response from URL: %s", OauthURL) return "", err } + var out struct { AccessToken string `json:"access_token"` } + err = json.Unmarshal(body, &out) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem unmarshalling the response from URL: %s", OauthURL) return "", err } + return out.AccessToken, nil } // GetGroup returns LF LDAP group -func (lfg *LFGroup) GetGroup(groupID string) (*LDAPGroup, error) { - accessToken, err := lfg.getAccessToken() +func (lfg *LFGroup) GetGroup(ctx context.Context, groupID string) (*LDAPGroup, error) { + f := logrus.Fields{ + "functionName": "v1.gerrits.lf_group.GetGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "groupID": groupID, + } + + accessToken, err := lfg.getAccessToken(ctx) if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading access token") return nil, err } getGroupURL := fmt.Sprintf("%s/rest/auth0/og/%s", lfg.LfBaseURL, groupID) req, err := http.NewRequest("GET", getGroupURL, nil) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating a new request to URL: %s", getGroupURL) return nil, err } req.Header.Add("Content-Type", "application/json") @@ -88,17 +126,273 @@ func (lfg *LFGroup) GetGroup(groupID string) (*LDAPGroup, error) { } res, err := client.Do(req) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem invoking request to URL: %s", getGroupURL) return nil, err } - defer res.Body.Close() + + defer func() { + closeErr := res.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warn("error closing response body") + } + }() + body, err := ioutil.ReadAll(res.Body) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading the response from URL: %s", getGroupURL) return nil, err } + var out LDAPGroup err = json.Unmarshal(body, &out) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem unmarshalling the response from URL: %s", getGroupURL) return nil, err } + return &out, nil } + +// GetUsersOfGroup returns a list of members from a group +func (lfg *LFGroup) GetUsersOfGroup(ctx context.Context, authUser *auth.User, claGroupID, groupName string) (*v2Models.GerritGroupResponse, error) { + f := logrus.Fields{ + "functionName": "v1.gerrits.lf_group.GetUsersOfGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "groupName": groupName, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + log.WithFields(f).Debug("getting users of group...") + + // Fetch a token for authorization + accessToken, err := lfg.getAccessToken(ctx) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading access token") + return nil, err + } + + // Build the URL path - can take the groupName or numeric value + // API Docs: https://confluence.linuxfoundation.org/display/IPM/Drupal+Identity+REST+for+Auth0 + url := fmt.Sprintf("%s/rest/auth0/og/%s", lfg.LfBaseURL, groupName) + + // Setup the request + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating a new request to URL: %s", url) + return nil, err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", "Bearer "+accessToken) + client := http.Client{ + Timeout: DefaultHTTPTimeout, + } + + // Invoke the request + resp, err := client.Do(req) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem invoking request to URL: %s", url) + return nil, err + } + + // Cleanup after + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warn("error closing response body") + } + }() + + // Check the response code to see how it went - response payload is undefined - just looking for a successful response status code + if resp.StatusCode >= 200 && resp.StatusCode <= 299 { + log.WithFields(f).Debugf("successfully fetched members from group: %s", groupName) + + var result v2Models.GerritGroupResponse + //err = json.NewDecoder(resp.Body).Decode(&result) + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading response for url: %s", url) + return nil, err + } + + log.WithFields(f).Debugf("response body: %+v", string(body)) + err = json.Unmarshal(body, &result) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem unmarshalling response for url: %s", url) + return nil, err + } + log.WithFields(f).Debugf("response body: %+v", result) + + return &result, nil + } + + log.WithFields(f).Warnf("error fetching users from group: %s - response status: %d", groupName, resp.StatusCode) + return nil, nil +} + +// AddUserToGroup adds the specified user to the group +func (lfg *LFGroup) AddUserToGroup(ctx context.Context, authUser *auth.User, claGroupID, groupName, userName string) error { + f := logrus.Fields{ + "functionName": "v1.gerrits.lf_group.AddUserToGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "groupName": groupName, + "userName": userName, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + log.WithFields(f).Debug("adding user to group...") + + // Fetch a token for authorization + accessToken, err := lfg.getAccessToken(ctx) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading access token") + return err + } + + // Build the URL path - can take the groupName or numeric value + // API Docs: https://confluence.linuxfoundation.org/display/IPM/Drupal+Identity+REST+for+Auth0 + url := fmt.Sprintf("%s/rest/auth0/og/%s", lfg.LfBaseURL, groupName) + + // Build the request payload + payload := map[string]interface{}{ + "username": userName, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + log.WithFields(f).Warnf("unable to encode payload for the request to URL: %s", url) + return err + } + + // Setup the request + req, err := http.NewRequest("PUT", url, bytes.NewBuffer(payloadBytes)) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating a new request to URL: %s", url) + return err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", "Bearer "+accessToken) + client := http.Client{ + Timeout: DefaultHTTPTimeout, + } + + // Invoke the request + resp, err := client.Do(req) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem invoking request to URL: %s", url) + return err + } + + // Cleanup after + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warn("error closing response body") + } + }() + + // Check the response code to see how it went - response payload is undefined - just looking for a successful response status code + if resp.StatusCode >= 200 && resp.StatusCode <= 299 { + log.WithFields(f).Debugf("successfully added user: %s to group: %s", userName, groupName) + // Create a log event indicating our success + lfg.EventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.GerritUserAdded, + LfUsername: authUser.UserName, + UserName: authUser.UserName, + CLAGroupID: claGroupID, + EventData: &events.GerritUserAddedEventData{ + Username: userName, + GroupName: groupName, + }, + }) + } else { + log.WithFields(f).Warnf("error adding added user: %s to group: %s - response status: %d", userName, groupName, resp.StatusCode) + } + + return nil +} + +// RemoveUserFromGroup removes the specified user from the group +func (lfg *LFGroup) RemoveUserFromGroup(ctx context.Context, authUser *auth.User, claGroupID, groupName, userName string) error { + f := logrus.Fields{ + "functionName": "v1.gerrits.lf_group.RemoveUserFromGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "groupName": groupName, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + log.WithFields(f).Debug("removing user from group...") + + // Fetch a token for authorization + accessToken, err := lfg.getAccessToken(ctx) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading access token") + return err + } + + // Build the URL path - can take the groupName or numeric value + // API Docs: https://confluence.linuxfoundation.org/display/IPM/Drupal+Identity+REST+for+Auth0 + url := fmt.Sprintf("%s/rest/auth0/og/%s", lfg.LfBaseURL, groupName) + + // Build the request payload + payload := map[string]interface{}{ + "username": userName, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + log.WithFields(f).Warnf("unable to encode payload for the request to URL: %s", url) + return err + } + + // Setup the request + req, err := http.NewRequest("DELETE", url, bytes.NewBuffer(payloadBytes)) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating a new request to URL: %s", url) + return err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", "Bearer "+accessToken) + client := http.Client{ + Timeout: DefaultHTTPTimeout, + } + + // Invoke the request + resp, err := client.Do(req) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem invoking request to URL: %s", url) + return err + } + + // Cleanup after + defer func() { + closeErr := resp.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warn("error closing response body") + } + }() + + // Check the response code to see how it went - response payload is undefined - just looking for a successful response status code + if resp.StatusCode >= 200 && resp.StatusCode <= 299 { + log.WithFields(f).Debugf("successfully removed user: %s from group: %s", userName, groupName) + // Create a log event indicating our success + lfg.EventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.GerritUserRemoved, + LfUsername: authUser.UserName, + UserName: authUser.UserName, + CLAGroupID: claGroupID, + EventData: &events.GerritUserRemovedEventData{ + Username: userName, + GroupName: groupName, + }, + }) + } else { + log.WithFields(f).Warnf("error removing user: %s from group: %s - response status: %d", userName, groupName, resp.StatusCode) + } + + return nil +} diff --git a/cla-backend-go/gerrits/repository.go b/cla-backend-go/gerrits/repository.go index 98b37b1aa..e053af72e 100644 --- a/cla-backend-go/gerrits/repository.go +++ b/cla-backend-go/gerrits/repository.go @@ -39,7 +39,7 @@ type Repository interface { GetGerrit(ctx context.Context, gerritID string) (*models.Gerrit, error) GetGerritsByID(ctx context.Context, ID string, IDType string) (*models.GerritList, error) GetGerritsByProjectSFID(ctx context.Context, projectSFID string) (*models.GerritList, error) - GetClaGroupGerrits(ctx context.Context, projectID string, projectSFID *string) (*models.GerritList, error) + GetClaGroupGerrits(ctx context.Context, claGroupID string) (*models.GerritList, error) ExistsByName(ctx context.Context, gerritName string) ([]*models.Gerrit, error) DeleteGerrit(ctx context.Context, gerritID string) error } @@ -259,21 +259,16 @@ func (repo repo) GetGerritsByProjectSFID(ctx context.Context, projectSFID string return &models.GerritList{List: resultList}, nil } -// GetClaGroupGerrits returns the CLA Group gerrit instances based on the CLA Group ID and the project SFID -func (repo repo) GetClaGroupGerrits(ctx context.Context, claGroupID string, projectSFID *string) (*models.GerritList, error) { +// GetClaGroupGerrits returns the CLA Group gerrit instances based on the CLA Group ID +func (repo repo) GetClaGroupGerrits(ctx context.Context, claGroupID string) (*models.GerritList, error) { f := logrus.Fields{ "functionName": "v1.gerrits.repository.GetClaGroupGerrits", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, - "projectSFID": projectSFID, } resultList := make([]*models.Gerrit, 0) condition := expression.Key("project_id").Equal(expression.Value(claGroupID)) - // No reason to add this additional filter for the v2 API - //if projectSFID != nil { - // filter = filter.And(expression.Name("project_sfid").Equal(expression.Value(*projectSFID))) - //} expr, err := expression.NewBuilder().WithKeyCondition(condition).Build() if err != nil { diff --git a/cla-backend-go/gerrits/service.go b/cla-backend-go/gerrits/service.go index bb3a73646..41606da14 100644 --- a/cla-backend-go/gerrits/service.go +++ b/cla-backend-go/gerrits/service.go @@ -11,6 +11,8 @@ import ( "net/url" "strings" + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/go-openapi/strfmt" "github.com/go-resty/resty/v2" @@ -19,6 +21,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) @@ -28,10 +31,15 @@ type Service interface { AddGerrit(ctx context.Context, claGroupID string, projectSFID string, input *models.AddGerritInput, claGroupModel *models.ClaGroup) (*models.Gerrit, error) GetGerrit(ctx context.Context, gerritID string) (*models.Gerrit, error) GetGerritsByProjectSFID(ctx context.Context, projectSFID string) (*models.GerritList, error) - GetClaGroupGerrits(ctx context.Context, claGroupID string, projectSFID *string) (*models.GerritList, error) + GetClaGroupGerrits(ctx context.Context, claGroupID string) (*models.GerritList, error) GetGerritRepos(ctx context.Context, gerritName string) (*models.GerritRepoList, error) DeleteClaGroupGerrits(ctx context.Context, claGroupID string) (int, error) DeleteGerrit(ctx context.Context, gerritID string) error + GetUsersOfGroup(ctx context.Context, authUser *auth.User, claGroupID, claType string) (*v2Models.GerritGroupResponse, error) + AddUserToGroup(ctx context.Context, authUser *auth.User, claGroupID, userName, claType string) error + AddUsersToGroup(ctx context.Context, authUser *auth.User, claGroupID string, userNameList []string, claType string) error + RemoveUserFromGroup(ctx context.Context, authUser *auth.User, claGroupID, userName, claType string) error + RemoveUsersFromGroup(ctx context.Context, authUser *auth.User, claGroupID string, userNameList []string, claType string) error } type service struct { @@ -49,7 +57,7 @@ func NewService(repo Repository, lfg *LFGroup) Service { func (s service) AddGerrit(ctx context.Context, claGroupID string, projectSFID string, params *models.AddGerritInput, claGroupModel *models.ClaGroup) (*models.Gerrit, error) { f := logrus.Fields{ - "functionName": "v1.gerrits.AddGerrit", + "functionName": "v1.gerrits.service.AddGerrit", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "projectSFID": projectSFID, @@ -122,7 +130,7 @@ func (s service) AddGerrit(ctx context.Context, claGroupID string, projectSFID s var groupNameCcla, groupNameIcla string if params.GroupIDIcla != "" { - group, err := s.lfGroup.GetGroup(params.GroupIDIcla) + group, err := s.lfGroup.GetGroup(ctx, params.GroupIDIcla) if err != nil { message := fmt.Sprintf("unable to get LDAP ICLA Group: %s", params.GroupIDIcla) log.WithFields(f).WithError(err).Warnf(message) @@ -131,7 +139,7 @@ func (s service) AddGerrit(ctx context.Context, claGroupID string, projectSFID s groupNameIcla = group.Title } if params.GroupIDCcla != "" { - group, err := s.lfGroup.GetGroup(params.GroupIDCcla) + group, err := s.lfGroup.GetGroup(ctx, params.GroupIDCcla) if err != nil { message := fmt.Sprintf("unable to get LDAP CCLA Group: %s", params.GroupIDCcla) log.WithFields(f).WithError(err).Warnf(message) @@ -162,14 +170,13 @@ func (s service) GetGerritsByProjectSFID(ctx context.Context, projectSFID string return s.repo.GetGerritsByProjectSFID(ctx, projectSFID) } -func (s service) GetClaGroupGerrits(ctx context.Context, claGroupID string, projectSFID *string) (*models.GerritList, error) { +func (s service) GetClaGroupGerrits(ctx context.Context, claGroupID string) (*models.GerritList, error) { f := logrus.Fields{ - "functionName": "v1.gerrits.GetClaGroupGerrits", + "functionName": "v1.gerrits.service.GetClaGroupGerrits", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, - "projectSFID": *projectSFID, } - responseModel, err := s.repo.GetClaGroupGerrits(ctx, claGroupID, projectSFID) + responseModel, err := s.repo.GetClaGroupGerrits(ctx, claGroupID) if err != nil { log.WithFields(f).Warnf("problem getting CLA Group gerrits, error: %+v", err) return nil, err @@ -222,7 +229,7 @@ func extractGerritHost(gerritHost string, f logrus.Fields) (string, error) { func (s service) GetGerritRepos(ctx context.Context, gerritHost string) (*models.GerritRepoList, error) { f := logrus.Fields{ - "functionName": "v1.gerrits.GetGerritRepos", + "functionName": "v1.gerrits.service.GetGerritRepos", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gerritName": gerritHost, } @@ -243,12 +250,19 @@ func (s service) GetGerritRepos(ctx context.Context, gerritHost string) (*models } func (s service) DeleteClaGroupGerrits(ctx context.Context, claGroupID string) (int, error) { - gerrits, err := s.repo.GetClaGroupGerrits(ctx, claGroupID, nil) + f := logrus.Fields{ + "functionName": "v1.gerrits.service.DeleteClaGroupGerrits", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + } + + gerrits, err := s.repo.GetClaGroupGerrits(ctx, claGroupID) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem fetching gerrits for CLA Group: %s", claGroupID) return 0, err } if len(gerrits.List) > 0 { - log.Debugf(fmt.Sprintf("Deleting gerrits for cla-group :%s ", claGroupID)) + log.WithFields(f).Debugf(fmt.Sprintf("Deleting gerrits for cla-group :%s ", claGroupID)) for _, gerrit := range gerrits.List { err = s.repo.DeleteGerrit(ctx, gerrit.GerritID.String()) if err != nil { @@ -256,6 +270,7 @@ func (s service) DeleteClaGroupGerrits(ctx context.Context, claGroupID string) ( } } } + return len(gerrits.List), nil } @@ -263,6 +278,194 @@ func (s service) DeleteGerrit(ctx context.Context, gerritID string) error { return s.repo.DeleteGerrit(ctx, gerritID) } +// GetUsersOfGroup +func (s service) GetUsersOfGroup(ctx context.Context, authUser *auth.User, claGroupID, claType string) (*v2Models.GerritGroupResponse, error) { + f := logrus.Fields{ + "functionName": "v1.gerrits.service.GetUsersOfGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + log.WithFields(f).Debug("querying for CLA Group gerrits...") + g, gerritErr := s.GetClaGroupGerrits(ctx, claGroupID) + if gerritErr != nil { + log.WithFields(f).WithError(gerritErr).Warnf("unable to locate gerrits associated with CLA Group ID: %s", claGroupID) + return nil, gerritErr + } + + // Just load the first one... + if len(g.List) > 0 { + gerritModel := g.List[0] + var ldapGroupName string + switch claType { + case utils.ClaTypeICLA: + ldapGroupName = gerritModel.GroupNameIcla + case utils.ClaTypeECLA: + ldapGroupName = gerritModel.GroupNameCcla + default: + return nil, &utils.InvalidCLAType{ + CLAType: claType, + } + } + + log.WithFields(f).Debugf("querying for members of gerrit group: %s...", ldapGroupName) + g, gerritErr := s.lfGroup.GetUsersOfGroup(ctx, authUser, claGroupID, ldapGroupName) + if gerritErr != nil { + log.WithFields(f).WithError(gerritErr).Warnf("unable to locate gerrits associated with CLA Group ID: %s", claGroupID) + return nil, gerritErr + } + return g, nil + } + + return nil, nil +} + +// AddUserToGroup adds the specified user to the group +func (s service) AddUserToGroup(ctx context.Context, authUser *auth.User, claGroupID, userName, claType string) error { + f := logrus.Fields{ + "functionName": "v1.gerrits.service.AddUserToGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "userName": userName, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + log.WithFields(f).Debug("querying for CLA Group gerrits...") + g, gerritErr := s.GetClaGroupGerrits(ctx, claGroupID) + if gerritErr != nil { + log.WithFields(f).WithError(gerritErr).Warnf("unable to locate gerrits associated with CLA Group ID: %s", claGroupID) + return gerritErr + } + + for _, gerritModel := range g.List { + var ldapGroupName string + switch claType { + case utils.ClaTypeICLA: + ldapGroupName = gerritModel.GroupNameIcla + case utils.ClaTypeECLA: + ldapGroupName = gerritModel.GroupNameCcla + default: + return &utils.InvalidCLAType{ + CLAType: claType, + } + } + log.WithFields(f).Debugf("LDAP group name: %s", ldapGroupName) + addErr := s.lfGroup.AddUserToGroup(ctx, authUser, claGroupID, ldapGroupName, userName) + if addErr != nil { + log.WithFields(f).WithError(addErr).Warnf("unable to add user %s to group: %s for CLA Group: %s", userName, ldapGroupName, claGroupID) + return gerritErr + } + log.WithFields(f).Debugf("added user %s to group: %s for CLA Group: %s", userName, ldapGroupName, claGroupID) + + // Log Event + } + + return nil +} + +// AddUsersToGroup adds the specified users to the group +func (s service) AddUsersToGroup(ctx context.Context, authUser *auth.User, claGroupID string, userNameList []string, claType string) error { + f := logrus.Fields{ + "functionName": "v1.gerrits.service.AddUsersToGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "userNameList": strings.Join(userNameList, ","), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + var errorList []error + for _, userName := range userNameList { + err := s.AddUserToGroup(ctx, authUser, claGroupID, userName, claType) + if err != nil { + log.WithFields(f).WithError(err).Warnf("encountered an error when adding username: %s to the CLA Group: %s", userName, claGroupID) + errorList = append(errorList, err) + } + } + + if len(errorList) > 0 { + log.WithFields(f).Warnf("encountered %d errors when adding %d users to the CLA Group: %s", len(errorList), len(userNameList), claGroupID) + return errorList[0] + } + + return nil +} + +// RemoveUserFromGroup removes the specified user from the group +func (s service) RemoveUserFromGroup(ctx context.Context, authUser *auth.User, claGroupID, userName, claType string) error { + f := logrus.Fields{ + "functionName": "v1.gerrits.service.RemoveUserFromGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "userName": userName, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + log.WithFields(f).Debug("querying for CLA Group gerrits...") + g, gerritErr := s.GetClaGroupGerrits(ctx, claGroupID) + if gerritErr != nil { + log.WithFields(f).WithError(gerritErr).Warnf("unable to locate gerrits associated with CLA Group ID: %s", claGroupID) + return gerritErr + } + + for _, gerritModel := range g.List { + var ldapGroupName string + switch claType { + case utils.ClaTypeICLA: + ldapGroupName = gerritModel.GroupNameIcla + case utils.ClaTypeECLA: + ldapGroupName = gerritModel.GroupNameCcla + default: + return &utils.InvalidCLAType{ + CLAType: claType, + } + } + log.WithFields(f).Debugf("LDAP group name: %s", ldapGroupName) + addErr := s.lfGroup.RemoveUserFromGroup(ctx, authUser, claGroupID, ldapGroupName, userName) + if addErr != nil { + log.WithFields(f).WithError(addErr).Warnf("unable to remove user %s from group: %s for CLA Group: %s", userName, ldapGroupName, claGroupID) + return gerritErr + } + log.WithFields(f).Debugf("removed user %s from group: %s for CLA Group: %s", userName, ldapGroupName, claGroupID) + + // Log Event + } + + return nil +} + +// RemoveUsersFromGroup removes the specified users from the group +func (s service) RemoveUsersFromGroup(ctx context.Context, authUser *auth.User, claGroupID string, userNameList []string, claType string) error { + f := logrus.Fields{ + "functionName": "v1.gerrits.service.RemoveUsersFromGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "userNameList": strings.Join(userNameList, ","), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + } + + var errorList []error + for _, userName := range userNameList { + err := s.RemoveUserFromGroup(ctx, authUser, claGroupID, userName, claType) + if err != nil { + log.WithFields(f).WithError(err).Warnf("encountered an error when removing username: %s from the CLA Group: %s", userName, claGroupID) + errorList = append(errorList, err) + } + } + + if len(errorList) > 0 { + log.WithFields(f).Warnf("encountered %d errors when removing %d users from the CLA Group: %s", len(errorList), len(userNameList), claGroupID) + return errorList[0] + } + + return nil +} + // convertModel is a helper function to create a GerritRepoList response model func convertModel(responseModel map[string]GerritRepoInfo, serverInfo *ServerInfo) *models.GerritRepoList { var gerritRepos []*models.GerritRepo diff --git a/cla-backend-go/project/helpers.go b/cla-backend-go/project/helpers.go index 06a63329a..1269ac8c2 100644 --- a/cla-backend-go/project/helpers.go +++ b/cla-backend-go/project/helpers.go @@ -92,7 +92,7 @@ func (s service) fillRepoInfo(ctx context.Context, project *models.ClaGroup) { defer wg.Done() var err error var gerritsList *models.GerritList - gerritsList, err = s.gerritRepo.GetClaGroupGerrits(ctx, project.ProjectID, nil) + gerritsList, err = s.gerritRepo.GetClaGroupGerrits(ctx, project.ProjectID) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to get gerrit instances for cla group ID: %s.", project.ProjectID) return diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 7f83486d5..2b637d371 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -861,7 +861,7 @@ func (repo *repo) buildCLAGroupModel(ctx context.Context, dbModel DBProjectModel defer wg.Done() var err error var gerritsList *models.GerritList - gerritsList, err = repo.gerritRepo.GetClaGroupGerrits(ctx, dbModel.ProjectID, nil) + gerritsList, err = repo.gerritRepo.GetClaGroupGerrits(ctx, dbModel.ProjectID) if err != nil { log.Warnf("buildCLAGroupModel - unable to load Gerrit repositories by project ID: %s, error: %+v", dbModel.ProjectID, err) diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index 1afbb2642..6cfc1b0d2 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -29,25 +29,30 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d api.SignaturesGetSignedICLADocumentHandler = signatures.GetSignedICLADocumentHandlerFunc(func(params signatures.GetSignedICLADocumentParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + + f := logrus.Fields{ + "functionName": "v1.signatures.handler.SignaturesGetSignedICLADocumentHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": params.ClaGroupID, + "userID": params.UserID, + } + + log.WithFields(f).Debug("querying for individual signature...") signatureModel, sigErr := service.GetIndividualSignature(ctx, params.ClaGroupID, params.UserID) if sigErr != nil { - msg := fmt.Sprintf("EasyCLA - 500 Internal Server Error - error retrieving signature using ClaGroupID: %s, userID: %s, error: %+v", + msg := fmt.Sprintf("error retrieving signature using ClaGroupID: %s, userID: %s, error: %+v", params.ClaGroupID, params.UserID, sigErr) - log.Warn(msg) - return signatures.NewGetSignedICLADocumentInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "500", - Message: msg, - }) + log.WithFields(f).WithError(sigErr).Warn(msg) + return signatures.NewGetSignedICLADocumentInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, sigErr))) } if signatureModel == nil { - msg := fmt.Sprintf("EasyCLA - 404 Not Found - - error retrieving signature using claGroupID: %s, userID: %s", + msg := fmt.Sprintf("error retrieving signature using claGroupID: %s, userID: %s", params.ClaGroupID, params.UserID) - log.Warn(msg) - return signatures.NewGetSignedICLADocumentNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: msg, - }) + log.WithFields(f).Warn(msg) + return signatures.NewGetSignedICLADocumentNotFound().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseNotFound(reqID, msg))) } downloadURL := fmt.Sprintf("contract-group/%s/icla/%s/%s.pdf", @@ -55,13 +60,11 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d log.Debugf("Retrieving PDF from path: %s", downloadURL) downloadLink, s3Err := utils.GetDownloadLink(downloadURL) if s3Err != nil { - msg := fmt.Sprintf("EasyCLA - 500 Internal Server Error - unable to locate PDF from source using ClaGroupID: %s, userID: %s, s3 error: %+v", + msg := fmt.Sprintf("unable to locate PDF from source using ClaGroupID: %s, userID: %s, s3 error: %+v", params.ClaGroupID, params.UserID, s3Err) log.Warn(msg) - return signatures.NewGetSignedICLADocumentInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "500", - Message: msg, - }) + return signatures.NewGetSignedICLADocumentInternalServerError().WithXRequestID(reqID).WithPayload( + utils.ToV1ErrorResponse(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, sigErr))) } return middleware.ResponderFunc(func(rw http.ResponseWriter, p runtime.Producer) { diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 1110543c1..8d6aacfd9 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -2906,6 +2906,202 @@ paths: tags: - gerrits + /cla-group/{claGroupID}/project/{projectSFID}/gerrits/icla/user: + get: + summary: Get Gerrit ICLA Users + description: Gets the authorized individual CLA users from a gerrit instance for the CLA Group/Projecct + operationId: getGerritICLAUser + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-projectSFID" + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gerrit-group-response' + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gerrits + put: + summary: Add Gerrit ICLA Users + description: Adds one or more individual CLA users to the gerrit CLA Group/project + operationId: addGerritICLAUser + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-projectSFID" + - in: body + name: add-gerrit-user-input + schema: + $ref: '#/definitions/add-gerrit-user-input' + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gerrits + delete: + summary: Remove Gerrit ICLA Users + description: Removes one or more individual CLA users from a gerrit instance for the CLA Group/Project + operationId: removeGerritICLAUser + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-projectSFID" + - in: body + name: remove-gerrit-user-input + schema: + $ref: '#/definitions/remove-gerrit-user-input' + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gerrits + + /cla-group/{claGroupID}/project/{projectSFID}/gerrits/ecla/user: + get: + summary: Get Gerrit ECLA Users + description: Gets the authorized employee CLA users from a gerrit instance for the CLA Group/Projecct + operationId: getGerritECLAUser + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-projectSFID" + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gerrit-group-response' + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gerrits + put: + summary: Add Gerrit ECLA Users + description: Adds one or more employee CLA users to a gerrit instance for the CLA Group/Project + operationId: addGerritECLAUser + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-projectSFID" + - in: body + name: add-gerrit-user-input + schema: + $ref: '#/definitions/add-gerrit-user-input' + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gerrits + delete: + summary: Remove Gerrit ECLA Users + description: Removes one or more employee CLA users from a gerrit instance for the project + operationId: removeGerritECLAUser + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-projectSFID" + - in: body + name: remove-gerrit-user-input + schema: + $ref: '#/definitions/remove-gerrit-user-input' + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gerrits + /company/{companyID}: get: summary: Get Company By Internal ID @@ -3906,6 +4102,15 @@ definitions: add-gerrit-input: $ref: './common/add-gerrit-input.yaml' + gerrit-group-response: + $ref: './common/gerrit-group-response.yaml' + + add-gerrit-user-input: + $ref: './common/gerrit-user-list.yaml' + + remove-gerrit-user-input: + $ref: './common/gerrit-user-list.yaml' + gerrit-repo: $ref: './common/gerrit-repo.yaml' diff --git a/cla-backend-go/swagger/common/gerrit-group-response.yaml b/cla-backend-go/swagger/common/gerrit-group-response.yaml new file mode 100644 index 000000000..da4fac5d6 --- /dev/null +++ b/cla-backend-go/swagger/common/gerrit-group-response.yaml @@ -0,0 +1,38 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + title: + type: string + title: gerrit group title + description: the gerrit group title + nid: + type: string + 'type': + type: string + title: gerrit type + description: the gerrit type + members: + type: array + items: + type: object + properties: + mail: + type: string + description: the name member mail address + example: 'apache+servicesreleng@mail.linuxfoundation.org' + minLength: 2 + maxLength: 255 + uid: + type: string + description: the member id + example: '255863' + minLength: 2 + maxLength: 255 + username: + type: string + description: the member username + example: 'lfservices_releng' + minLength: 2 + maxLength: 255 diff --git a/cla-backend-go/swagger/common/gerrit-user-list.yaml b/cla-backend-go/swagger/common/gerrit-user-list.yaml new file mode 100644 index 000000000..fb5fd9f11 --- /dev/null +++ b/cla-backend-go/swagger/common/gerrit-user-list.yaml @@ -0,0 +1,10 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: array +items: + type: string + description: the user's user name + example: 'elonmusk' + minLength: 1 + maxLength: 50 diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index ca6b930e1..a54444e1d 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -347,3 +347,22 @@ func (e *CLAManagerError) Error() string { func (e *CLAManagerError) Unwrap() error { return e.Err } + +// InvalidCLAType is an error model for invalid CLA types, usually the CLA type is one of: utils.{ClaTypeICLA,ClaTypeECLA,ClaTypeCCLA} +type InvalidCLAType struct { + CLAType string + Err error +} + +// Error is an error string function for CLA Group not found errors +func (e *InvalidCLAType) Error() string { + if e.Err == nil { + return fmt.Sprintf("invalid CLA type: %s", e.CLAType) + } + return fmt.Sprintf("invalid CLA type: %s, %+v", e.CLAType, e.Err) +} + +// Unwrap method returns its contained error +func (e *InvalidCLAType) Unwrap() error { + return e.Err +} diff --git a/cla-backend-go/v2/gerrits/handlers.go b/cla-backend-go/v2/gerrits/handlers.go index af3fcdd8d..0f5de712b 100644 --- a/cla-backend-go/v2/gerrits/handlers.go +++ b/cla-backend-go/v2/gerrits/handlers.go @@ -33,7 +33,7 @@ type ProjectService interface { //nolint } // Configure the Gerrit api -func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectService ProjectService, eventService events.Service, projectsClaGroupsRepo projects_cla_groups.Repository) { +func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectService ProjectService, eventService events.Service, projectsClaGroupsRepo projects_cla_groups.Repository) { // nolint api.GerritsDeleteGerritHandler = gerrits.DeleteGerritHandlerFunc( func(params gerrits.DeleteGerritParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) @@ -197,7 +197,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS } log.WithFields(f).Debug("querying for gerrits...") - result, err := v1Service.GetClaGroupGerrits(ctx, params.ClaGroupID, ¶ms.ProjectSFID) + result, err := v1Service.GetClaGroupGerrits(ctx, params.ClaGroupID) if err != nil { msg := fmt.Sprintf("problem fetching gerrit repositories using CLA Group: %s with project SFID: %s", params.ClaGroupID, params.ProjectSFID) log.WithFields(f).Warn(msg) @@ -260,6 +260,197 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS return gerrits.NewGetGerritReposOK().WithXRequestID(reqID).WithPayload(&response) }) + + api.GerritsGetGerritICLAUserHandler = gerrits.GetGerritICLAUserHandlerFunc(func(params gerrits.GetGerritICLAUserParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsGetGerritICLAUserHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "claGroupID": params.ClaGroupID, + "projectSFID": params.ProjectSFID, + } + + // verify user have access to the project + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to get gerrit users with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewGetGerritICLAUserForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + log.WithFields(f).Debugf("getting user list to gerrit...") + responseModel, err := v1Service.GetUsersOfGroup(ctx, authUser, params.ClaGroupID, utils.ClaTypeICLA) + if err != nil { + msg := fmt.Sprintf("problem getting user list of CLA Group %s", params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewGetGerritICLAUserInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gerrits.NewGetGerritICLAUserOK().WithXRequestID(reqID).WithPayload(responseModel) + }) + + api.GerritsGetGerritECLAUserHandler = gerrits.GetGerritECLAUserHandlerFunc(func(params gerrits.GetGerritECLAUserParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsGetGerritECLAUserHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "claGroupID": params.ClaGroupID, + "projectSFID": params.ProjectSFID, + } + + // verify user have access to the project + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to get gerrit users with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewGetGerritECLAUserForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + log.WithFields(f).Debugf("getting user list to gerrit...") + responseModel, err := v1Service.GetUsersOfGroup(ctx, authUser, params.ClaGroupID, utils.ClaTypeECLA) + if err != nil { + msg := fmt.Sprintf("problem getting user list of CLA Group %s", params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewGetGerritECLAUserInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gerrits.NewGetGerritECLAUserOK().WithXRequestID(reqID).WithPayload(responseModel) + }) + + api.GerritsAddGerritICLAUserHandler = gerrits.AddGerritICLAUserHandlerFunc(func(params gerrits.AddGerritICLAUserParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsAddGerritICLAUserHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "claGroupID": params.ClaGroupID, + "projectSFID": params.ProjectSFID, + "gerritUsers": strings.Join(params.AddGerritUserInput, ","), + } + + // verify user have access to the project + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to add gerrit users with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewAddGerritICLAUserForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + log.WithFields(f).Debugf("adding user list to gerrit...") + err := v1Service.AddUsersToGroup(ctx, authUser, params.ClaGroupID, params.AddGerritUserInput, utils.ClaTypeICLA) + if err != nil { + msg := fmt.Sprintf("problem adding user list %s to CLA Group %s", strings.Join(params.AddGerritUserInput, ","), params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewAddGerritICLAUserInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gerrits.NewAddGerritICLAUserOK().WithXRequestID(reqID) + }) + + api.GerritsRemoveGerritICLAUserHandler = gerrits.RemoveGerritICLAUserHandlerFunc(func(params gerrits.RemoveGerritICLAUserParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsRemoveGerritICLAUserHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "claGroupID": params.ClaGroupID, + "projectSFID": params.ProjectSFID, + "gerritUsers": strings.Join(params.RemoveGerritUserInput, ","), + } + + // verify user have access to the project + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to remove gerrit users with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewRemoveGerritICLAUserForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + log.WithFields(f).Debugf("removing user list from gerrit...") + err := v1Service.RemoveUsersFromGroup(ctx, authUser, params.ClaGroupID, params.RemoveGerritUserInput, utils.ClaTypeICLA) + if err != nil { + msg := fmt.Sprintf("problem removing user list %s to CLA Group %s", strings.Join(params.RemoveGerritUserInput, ","), params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewRemoveGerritICLAUserInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gerrits.NewRemoveGerritICLAUserOK().WithXRequestID(reqID) + }) + + api.GerritsAddGerritECLAUserHandler = gerrits.AddGerritECLAUserHandlerFunc(func(params gerrits.AddGerritECLAUserParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsAddGerritECLAUserHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "claGroupID": params.ClaGroupID, + "projectSFID": params.ProjectSFID, + "gerritUsers": strings.Join(params.AddGerritUserInput, ","), + } + + // verify user have access to the project + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to add gerrit users with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewAddGerritECLAUserForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + log.WithFields(f).Debugf("adding user list to gerrit...") + err := v1Service.AddUsersToGroup(ctx, authUser, params.ClaGroupID, params.AddGerritUserInput, utils.ClaTypeECLA) + if err != nil { + msg := fmt.Sprintf("problem adding user list %s to CLA Group %s", strings.Join(params.AddGerritUserInput, ","), params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewAddGerritECLAUserInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gerrits.NewAddGerritECLAUserOK().WithXRequestID(reqID) + }) + + api.GerritsRemoveGerritECLAUserHandler = gerrits.RemoveGerritECLAUserHandlerFunc(func(params gerrits.RemoveGerritECLAUserParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.gerrits.handlers.GerritsRemoveGerritECLAUserHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, + "claGroupID": params.ClaGroupID, + "projectSFID": params.ProjectSFID, + "gerritUsers": strings.Join(params.RemoveGerritUserInput, ","), + } + + // verify user have access to the project + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to remove gerrit users with Project scope of %s", authUser.UserName, params.ProjectSFID) + log.WithFields(f).Warn(msg) + return gerrits.NewRemoveGerritECLAUserForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + log.WithFields(f).Debugf("removing user list from gerrit...") + err := v1Service.RemoveUsersFromGroup(ctx, authUser, params.ClaGroupID, params.RemoveGerritUserInput, utils.ClaTypeECLA) + if err != nil { + msg := fmt.Sprintf("problem removing user list %s to CLA Group %s", strings.Join(params.RemoveGerritUserInput, ","), params.ClaGroupID) + log.WithFields(f).WithError(err).Warn(msg) + return gerrits.NewRemoveGerritECLAUserInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gerrits.NewRemoveGerritECLAUserOK().WithXRequestID(reqID) + }) + } type codedResponse interface { From ddb717fdd8f3ac11faf28cd26df6b8f1ec2597c5 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 25 Mar 2021 01:28:34 +0300 Subject: [PATCH 0184/1276] [#2793] Feature/Invalidate Gerrit Contributors (#2813) - Invalidate gerrit contributors based on email|domain removals from approval lists Signed-off-by: wanyaland --- .../cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 18 ++-- cla-backend-go/signatures/models.go | 1 + cla-backend-go/signatures/repository.go | 99 ++++++++++++++++++- 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 01ed9b077..8dbf6d764 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -119,7 +119,7 @@ func init() { projectClaGroupRepo, }) - signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo) + signaturesRepo := signatures.NewRepository(awsSession, stage, companyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo, gerritService) usersService := users.NewService(usersRepo, eventsService) companyService := company.NewService(companyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 4356536f7..a7d5a4bf4 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -251,8 +251,16 @@ func server(localMode bool) http.Handler { projectClaGroupRepo, }) + gerritService := gerrits.NewService(gerritRepo, &gerrits.LFGroup{ + LfBaseURL: configFile.LFGroup.ClientURL, + ClientID: configFile.LFGroup.ClientID, + ClientSecret: configFile.LFGroup.ClientSecret, + RefreshToken: configFile.LFGroup.RefreshToken, + EventsService: eventsService, + }) + // Signature repository handler - signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo) + signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo, gerritService) // Initialize the external platform services - these are external APIs that // we download the swagger specification, generate the models, and have @@ -284,13 +292,7 @@ func server(localMode bool) http.Handler { v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, eventsService, autoEnableService) - gerritService := gerrits.NewService(gerritRepo, &gerrits.LFGroup{ - LfBaseURL: configFile.LFGroup.ClientURL, - ClientID: configFile.LFGroup.ClientID, - ClientSecret: configFile.LFGroup.ClientSecret, - RefreshToken: configFile.LFGroup.RefreshToken, - EventsService: eventsService, - }) + v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, projectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) sessionStore, err := dynastore.New(dynastore.Path("/"), dynastore.HTTPOnly(), dynastore.TableName(configFile.SessionStoreTableName), dynastore.DynamoDB(dynamodb.New(awsSession))) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index dcc2b2e2a..aad7ee21e 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -29,4 +29,5 @@ type ApprovalList struct { GitHubUsernameApprovals []string EmailApprovals []string GHUsernames []string + GerritICLAECLAs []string } diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index edf7a89b5..1e0e5e014 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -11,6 +11,7 @@ import ( "strings" "sync" + "github.com/LF-Engineering/lfx-kit/auth" "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/users" @@ -21,6 +22,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gerrits" "github.com/communitybridge/easycla/cla-backend-go/github" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" "github.com/communitybridge/easycla/cla-backend-go/repositories" @@ -95,11 +97,12 @@ type repository struct { eventsService events.Service repositoriesRepo repositories.Repository ghOrgRepo github_organizations.Repository + gerritService gerrits.Service signatureTableName string } // NewRepository creates a new instance of the whitelist service -func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.Repository) SignatureRepository { +func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.Repository, gerritService gerrits.Service) SignatureRepository { return repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), @@ -108,6 +111,7 @@ func NewRepository(awsSession *session.Session, stage string, companyRepo compan eventsService: eventsService, repositoriesRepo: repositoriesRepo, ghOrgRepo: ghOrgRepo, + gerritService: gerritService, signatureTableName: fmt.Sprintf("cla-%s-signatures", stage), } } @@ -2010,6 +2014,35 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model CompanyID: companyID, } + authUser := auth.User{ + Email: claManager.LfEmail, + UserName: claManager.LfUsername, + } + + log.WithFields(f).Debug("aggregating ICLA and ECLA gerrit users...") + gerritIclaUsers, err := repo.gerritService.GetUsersOfGroup(ctx, &authUser, projectID, utils.ClaTypeICLA) + + if err != nil { + msg := fmt.Sprintf("unable to fetch gerrit users for claGroup: %s , claType: %s ", projectID, utils.ClaTypeICLA) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + + gerritEclaUsers, err := repo.gerritService.GetUsersOfGroup(ctx, &authUser, projectID, utils.ClaTypeECLA) + + if err != nil { + msg := fmt.Sprintf("unable to fetch gerrit users for claGroup: %s , claType: %s ", projectID, utils.ClaTypeECLA) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + + // Keep track of gerrit users under a give CLA Group + var gerritICLAECLAs []string + + for _, member := range append(gerritEclaUsers.Members, gerritIclaUsers.Members...) { + gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + } + // If we have an add or remove email list...we need to run an update for this column if params.AddEmailApprovalList != nil || params.RemoveEmailApprovalList != nil { columnName := "email_whitelist" @@ -2065,6 +2098,26 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } repo.eventsService.LogEventWithContext(ctx, eventArgs) + + //update gerrit permissions + gerritUser, err := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) + if err != nil || gerritUser == nil { + msg := fmt.Sprintf("unable to get gerrit user by email : %s ", email) + log.WithFields(f).Warn(msg) + return + } + iclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, gerritUser.LfUsername, utils.ClaTypeICLA) + if iclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", gerritUser.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).Warn(msg) + return + } + eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, gerritUser.LfUsername, utils.ClaTypeECLA) + if eclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", gerritUser.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).Warn(msg) + return + } }(email) } wg.Wait() @@ -2096,6 +2149,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Criteria = utils.EmailDomainCriteria approvalList.ApprovalList = params.RemoveDomainApprovalList approvalList.Action = utils.RemoveApprovals + approvalList.GerritICLAECLAs = gerritICLAECLAs invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) if invalidateErr != nil { msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) @@ -2370,6 +2424,30 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A return nil } +// getGerritUsersByEmail searches gerrit instances for users with given email +func (repo repository) getGerritUserByEmail(ctx context.Context, email string, gerritICLAECLAs []string) (*models.User, error) { + f := logrus.Fields{ + "functionName": "getGerritUserByEmailDomain", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "emai": email, + } + + log.WithFields(f).Debugf("checking gerrit user for email: %s ", email) + if email != "" { + claUser, err := repo.usersRepo.GetUserByEmail(email) + if err != nil { + msg := fmt.Sprintf("unable to get easyclauser by email: %s ", email) + log.WithFields(f).Warn(msg) + return nil, err + } + if utils.StringInSlice(claUser.LfUsername, gerritICLAECLAs) { + return claUser, nil + } + } + + return nil, nil +} + // verify UserApprovals checks user func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatureID string, claManager *models.User, approvalList *ApprovalList) error { f := logrus.Fields{ @@ -2384,6 +2462,11 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur return err } + authUser := auth.User{ + Email: claManager.LfEmail, + UserName: claManager.LfUsername, + } + if approvalList.Criteria == utils.EmailDomainCriteria { // Handle Domains if utils.StringInSlice(getBestEmail(user), approvalList.DomainApprovals) { @@ -2395,6 +2478,20 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) return err } + + log.WithFields(f).Debugf("removing gerrit user:%s from claGroup: %s ...", user.LfUsername, approvalList.ClaGroupID) + iclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeICLA) + if iclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).Warn(msg) + return iclaErr + } + eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeECLA) + if eclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).Warn(msg) + return eclaErr + } } } } else if approvalList.Criteria == utils.GitHubOrgCriteria { From 73956bfb1faf7c028c07384a55f5ed42b52df2e1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 24 Mar 2021 16:30:02 -0700 Subject: [PATCH 0185/1276] Resolved [#2814] CCLA Signature Signed On Date Not Showing (#2815) - Added/updated signedOn date logic to use docusign date if available, if missing, use the created date (generally close to the same value) Signed-off-by: David Deal --- .github/ISSUE_TEMPLATE/bug_report.md | 8 ++++++++ cla-backend-go/signatures/converters.go | 9 ++++++++- cla-backend-go/utils/utils.go | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3bbf4d3cd..8f020ed2c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -32,12 +32,20 @@ If applicable, add screenshots to help explain your problem. Please complete the following information: * Environment: + - [ ] ALL - [ ] DEV - [ ] STAGING - [ ] PROD * Browser: - [ ] Chrome/Brave - [ ] Firefox + - [ ] Opera + - [ ] Vivaldi + - [ ] LibreWolf + - [ ] SRware Iron + - [ ] Dissenter + - [ ] Slimjet + - [ ] Midori - [ ] Edge - [ ] Lynx * Version: v1.0.XX diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go index 3639a6b13..21821850e 100644 --- a/cla-backend-go/signatures/converters.go +++ b/cla-backend-go/signatures/converters.go @@ -56,6 +56,13 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results claType = utils.ClaTypeICLA } + // Use the signedOn field if possible, for older signatures that are missing it, use the date created value as the default/fallback + signedOn := dbSignature.DateCreated + if dbSignature.SignedOn != "" { + signedOn = dbSignature.SignedOn + } + signedOn = utils.FormatTimeString(signedOn) + sig := &models.Signature{ SignatureID: dbSignature.SignatureID, ClaType: claType, @@ -81,7 +88,7 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results UserName: dbSignature.UserName, UserLFID: dbSignature.UserLFUsername, UserGHID: dbSignature.UserGithubUsername, - SignedOn: dbSignature.SignedOn, + SignedOn: signedOn, SignatoryName: dbSignature.SignatoryName, UserDocusignName: dbSignature.UserDocusignName, UserDocusignDateSigned: dbSignature.UserDocusignDateSigned, diff --git a/cla-backend-go/utils/utils.go b/cla-backend-go/utils/utils.go index b4f84a098..22a3e8e0c 100644 --- a/cla-backend-go/utils/utils.go +++ b/cla-backend-go/utils/utils.go @@ -12,6 +12,9 @@ import ( "time" "unicode/utf8" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/sirupsen/logrus" + "github.com/gofrs/uuid" "github.com/aws/aws-sdk-go/aws" @@ -40,6 +43,22 @@ func TimeToString(t time.Time) string { return t.UTC().Format(time.RFC3339) } +// FormatTimeString converts the time string into the "standard" RFC3339 format +func FormatTimeString(timeStr string) string { + f := logrus.Fields{ + "functionName": "utils.utils.FormatTimeString", + "timeStr": timeStr, + } + + t, err := ParseDateTime(timeStr) + if err != nil { + log.WithFields(f).Warnf("unable to convert the time string: %s into a standard form.", timeStr) + return timeStr + } + + return t.UTC().Format(time.RFC3339) +} + // CurrentTime returns the current UTC time and current Time in the RFC3339 format func CurrentTime() (time.Time, string) { t := time.Now().UTC() From a55cf95d2b80e830440a43b57d2d8b8332925c2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Mar 2021 16:44:35 -0700 Subject: [PATCH 0186/1276] Bump rsa from 4.0 to 4.1 in /cla-backend (#2812) Bumps [rsa](https://github.com/sybrenstuvel/python-rsa) from 4.0 to 4.1. - [Release notes](https://github.com/sybrenstuvel/python-rsa/releases) - [Changelog](https://github.com/sybrenstuvel/python-rsa/blob/main/CHANGELOG.md) - [Commits](https://github.com/sybrenstuvel/python-rsa/compare/version-4.0...version-4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index eec1d6348..82e845d8c 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -46,7 +46,7 @@ python-dateutil==2.8.0 python-jose==3.0.1 requests==2.22.0 requests-oauthlib==1.2.0 -rsa==4.0 +rsa==4.1 s3transfer==0.2.1 sentinels==1.0.0 six==1.13.0 From a9b1caa53d9a3527edb24329a635b23153084f79 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 24 Mar 2021 17:44:25 -0700 Subject: [PATCH 0187/1276] Resolved Foundation Query Issues (#2816) - When searching by foundation, we need to first consult the Project CLA Group Mapping table entries for the CLA Groups - then load each CLA group. Previous logic was only looking at the CLA Group table foundation SFID (v1) value. Signed-off-by: David Deal --- cla-backend-go/project/repository.go | 5 ++- cla-backend-go/project/service.go | 3 +- cla-backend-go/v2/cla_groups/service.go | 52 +++++++++++++------------ 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 2b637d371..24c9d31dd 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -306,8 +306,9 @@ func (repo *repo) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSF utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "foundationSFID": foundationSFID, "loadRepoDetails": loadRepoDetails, - "tableName": repo.claGroupTable} - log.WithFields(f).Debugf("loading project by foundation SFID") + "tableName": repo.claGroupTable, + } + log.WithFields(f).Debugf("loading CLA Group by foundation SFID - using foundation_sfid field...") // This is the key we want to match condition := expression.Key("foundation_sfid").Equal(expression.Value(foundationSFID)) diff --git a/cla-backend-go/project/service.go b/cla-backend-go/project/service.go index 3819eae79..570e72639 100644 --- a/cla-backend-go/project/service.go +++ b/cla-backend-go/project/service.go @@ -341,7 +341,7 @@ func (s service) SignedAtFoundationLevel(ctx context.Context, foundationSFID str if pcgErr != nil { return false, pcgErr } - log.WithFields(f).Debugf("loaded %d CLA Group entries", len(entries)) + log.WithFields(f).Debugf("loaded %d CLA Group entries signed at foundation level...", len(entries)) // Check for number of claGroups for foundation foundationLevelCLAGroup := false @@ -352,6 +352,7 @@ func (s service) SignedAtFoundationLevel(ctx context.Context, foundationSFID str } } + log.WithFields(f).Debugf("returning %t for signed at foundation level for: %s", foundationLevelCLAGroup, foundationSFID) return foundationLevelCLAGroup, nil } diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index cfd2b74e5..d7891a9c1 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -13,8 +13,6 @@ import ( "github.com/aws/aws-sdk-go/aws" - "golang.org/x/sync/errgroup" - "github.com/LF-Engineering/lfx-kit/auth" v1ClaManager "github.com/communitybridge/easycla/cla-backend-go/cla_manager" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -444,17 +442,22 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje } else if sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup { log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") - projectCLAGroups, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) + projectCLAGroupMappings, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) if lookupErr != nil { log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) return nil, &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} } - log.WithFields(f).Debugf("discovered %d projects based on foundation SFID...", len(projectCLAGroups)) + log.WithFields(f).Debugf("discovered %d projects based on foundation SFID...", len(projectCLAGroupMappings)) claGroupsMap := map[string]bool{} + type CLAGroupResult struct { + claGroupModel *v1Models.ClaGroup + Error error + } + claGroupResultChannel := make(chan *CLAGroupResult, len(projectCLAGroupMappings)) + // Load these CLA Group records in parallel - var eg errgroup.Group - for _, projectCLAGroup := range projectCLAGroups { + for _, projectCLAGroup := range projectCLAGroupMappings { // ensure that following goroutine gets a copy of projectSFID projectCLAGroupClaGroupID := projectCLAGroup.ClaGroupID // No need to re-process the same CLA group @@ -465,34 +468,35 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje // Add entry into our map - so we know not to re-process this CLA Group claGroupsMap[projectCLAGroupClaGroupID] = true - // Invoke the go routine - any errors will be handled below - eg.Go(func() error { + // Load each CLA Group - save results to our channel + go func(ctx context.Context, projectCLAGroupClaGroupID string) { log.WithFields(f).Debugf("loading CLA Group by ID: %s", projectCLAGroupClaGroupID) claGroupModel, claGroupLookupErr := s.v1ProjectService.GetCLAGroupByID(ctx, projectCLAGroupClaGroupID) if claGroupLookupErr != nil { log.WithFields(f).Warnf("problem locating project by id: %s, error: %+v", projectCLAGroupClaGroupID, claGroupLookupErr) - return &utils.SFProjectNotFound{ProjectSFID: projectCLAGroupClaGroupID, Err: claGroupLookupErr} + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: nil, + Error: &utils.SFProjectNotFound{ProjectSFID: projectCLAGroupClaGroupID, Err: claGroupLookupErr}, + } } - v1ClaGroups.Projects = append(v1ClaGroups.Projects, *claGroupModel) - return nil - }) + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: claGroupModel, + Error: nil, + } + }(ctx, projectCLAGroupClaGroupID) } - // Wait for the go routines to finish + // Wait for the go routines to finish and load up the results log.WithFields(f).Debug("waiting for CLA Groups to load...") - if loadErr := eg.Wait(); loadErr != nil { - return nil, loadErr - } - - v1CLAGroupsData, v1ClaGroupErr := s.v1ProjectService.GetClaGroupsByFoundationSFID(ctx, projectOrFoundationSFID, false) - if v1ClaGroupErr != nil { - log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", v1ClaGroupErr) - return nil, &utils.CLAGroupNotFound{CLAGroupID: projectOrFoundationSFID, Err: v1ClaGroupErr} + for range projectCLAGroupMappings { + response := <-claGroupResultChannel + if response.Error != nil { + log.WithFields(f).WithError(response.Error).Warnf("unable to load CLA Group") + return nil, response.Error + } + v1ClaGroups.Projects = append(v1ClaGroups.Projects, *response.claGroupModel) } - - v1ClaGroups = v1CLAGroupsData - } else { msg := fmt.Sprintf("unsupported foundation/project SFID type: %s", sfProjectModelDetails.ProjectType) log.WithFields(f).Warn(msg) From b7df8276d1f3e588acd6799cb5b74308150d20a3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Mar 2021 09:25:51 -0700 Subject: [PATCH 0188/1276] Resolved #2816 Issue with Foundation Query and Unique CLA Groups (#2819) - Resolved issue of looping over unique CLA Groups when querying for foundations. Bug caused timeout/channel blocking due to a mismatch of channels vs looping iterations. Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/helpers.go | 24 ++++++++++++++++++++++++ cla-backend-go/v2/cla_groups/service.go | 19 ++++++------------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 1b96b6234..57edc2ded 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -680,3 +680,27 @@ func isFoundationIDInList(foundationSFID string, projectsSFIDs []string) bool { } return false } + +// getUniqueCLAGroupIDs logic to extract the unique +func getUniqueCLAGroupIDs(projectCLAGroupMappings []*projects_cla_groups.ProjectClaGroup) []string { + // to ensure we get the distinct count + claGroupsMap := map[string]bool{} + for _, projectCLAGroupModel := range projectCLAGroupMappings { + // ensure that following goroutine gets a copy of projectSFID + projectCLAGroupClaGroupID := projectCLAGroupModel.ClaGroupID + // No need to re-process the same CLA group + if _, ok := claGroupsMap[projectCLAGroupClaGroupID]; ok { + continue + } + + // Add entry into our map - so we know not to re-process this CLA Group + claGroupsMap[projectCLAGroupClaGroupID] = true + } + + keys := make([]string, 0, len(claGroupsMap)) + for k := range claGroupsMap { + keys = append(keys, k) + } + + return keys +} diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index d7891a9c1..b9d5ebaf8 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -449,24 +449,17 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje } log.WithFields(f).Debugf("discovered %d projects based on foundation SFID...", len(projectCLAGroupMappings)) - claGroupsMap := map[string]bool{} + // Determine how many CLA Groups we have - we could have many and possibly return duplicates, we use this loop + uniqueCLAGroupList := getUniqueCLAGroupIDs(projectCLAGroupMappings) + type CLAGroupResult struct { claGroupModel *v1Models.ClaGroup Error error } - claGroupResultChannel := make(chan *CLAGroupResult, len(projectCLAGroupMappings)) + claGroupResultChannel := make(chan *CLAGroupResult, len(uniqueCLAGroupList)) // Load these CLA Group records in parallel - for _, projectCLAGroup := range projectCLAGroupMappings { - // ensure that following goroutine gets a copy of projectSFID - projectCLAGroupClaGroupID := projectCLAGroup.ClaGroupID - // No need to re-process the same CLA group - if _, ok := claGroupsMap[projectCLAGroupClaGroupID]; ok { - continue - } - - // Add entry into our map - so we know not to re-process this CLA Group - claGroupsMap[projectCLAGroupClaGroupID] = true + for _, projectCLAGroupClaGroupID := range uniqueCLAGroupList { // Load each CLA Group - save results to our channel go func(ctx context.Context, projectCLAGroupClaGroupID string) { @@ -489,7 +482,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje // Wait for the go routines to finish and load up the results log.WithFields(f).Debug("waiting for CLA Groups to load...") - for range projectCLAGroupMappings { + for range uniqueCLAGroupList { response := <-claGroupResultChannel if response.Error != nil { log.WithFields(f).WithError(response.Error).Warnf("unable to load CLA Group") From 2a2fccf3327a0ec3789b28a78e10405b18b2973b Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 26 Mar 2021 00:18:42 +0300 Subject: [PATCH 0189/1276] [#LFX-3147] Bug/ Update Approval List (#2820) - Resolved error when getting gerrit users for approval list update Signed-off-by: wanyaland --- cla-backend-go/signatures/repository.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 1e0e5e014..8977e0c1a 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2019,6 +2019,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model UserName: claManager.LfUsername, } + // Keep track of gerrit users under a give CLA Group + var gerritICLAECLAs []string + log.WithFields(f).Debug("aggregating ICLA and ECLA gerrit users...") gerritIclaUsers, err := repo.gerritService.GetUsersOfGroup(ctx, &authUser, projectID, utils.ClaTypeICLA) @@ -2028,6 +2031,12 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return nil, errors.New(msg) } + if gerritIclaUsers != nil { + for _, member := range gerritIclaUsers.Members { + gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + } + } + gerritEclaUsers, err := repo.gerritService.GetUsersOfGroup(ctx, &authUser, projectID, utils.ClaTypeECLA) if err != nil { @@ -2036,11 +2045,10 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return nil, errors.New(msg) } - // Keep track of gerrit users under a give CLA Group - var gerritICLAECLAs []string - - for _, member := range append(gerritEclaUsers.Members, gerritIclaUsers.Members...) { - gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + if gerritEclaUsers != nil { + for _, member := range gerritEclaUsers.Members { + gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + } } // If we have an add or remove email list...we need to run an update for this column From da4768e8e0d2a74248c7d46b461ca7a2e0f915fa Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Mar 2021 16:51:00 -0700 Subject: [PATCH 0190/1276] Resolved CLA Group Event Query NextKey Issue (#2821) - Updated next key logic, added support for CLA Group Next Key Signed-off-by: David Deal --- cla-backend-go/events/repository.go | 287 +++++++++++++++--------- cla-backend-go/swagger/cla.v2.yaml | 8 +- cla-backend/cla/models/dynamo_models.py | 11 + 3 files changed, 197 insertions(+), 109 deletions(-) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 544d3f14b..5d8cec251 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -71,6 +71,7 @@ type Repository interface { type repository struct { stage string dynamoDBClient *dynamodb.DynamoDB + eventsTable string } // NewRepository creates a new instance of the event repository @@ -78,6 +79,7 @@ func NewRepository(awsSession *session.Session, stage string) Repository { return &repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), + eventsTable: fmt.Sprintf("cla-%s-events", stage), } } @@ -89,7 +91,7 @@ func toDateFormat(t time.Time) string { // Create event will create event in database. func (repo *repository) CreateEvent(event *models.Event) error { f := logrus.Fields{ - "functionName": "events.repository.CreateEvent", + "functionName": "v1.events.repository.CreateEvent", } if event.UserID == "" { @@ -107,7 +109,7 @@ func (repo *repository) CreateEvent(event *models.Event) error { currentTime, currentTimeString := utils.CurrentTime() input := &dynamodb.PutItemInput{ Item: map[string]*dynamodb.AttributeValue{}, - TableName: aws.String(fmt.Sprintf("cla-%s-events", repo.stage)), + TableName: aws.String(repo.eventsTable), } eventDateAndContainsPII := fmt.Sprintf("%s#%t", toDateFormat(currentTime), event.ContainsPII) @@ -136,6 +138,8 @@ func (repo *repository) CreateEvent(event *models.Event) error { addAttribute(input.Item, "event_parent_project_name", event.EventParentProjectName) addAttribute(input.Item, "event_data", event.EventData) + // For filtering/searching + addAttribute(input.Item, "event_data_lower", strings.ToLower(event.EventData)) addAttribute(input.Item, "event_summary", event.EventSummary) addAttribute(input.Item, "event_time", currentTimeString) @@ -212,7 +216,7 @@ func createSearchEventFilter(pk string, sk string, params *eventOps.SearchEvents filter = addConditionToFilter(filter, filterExpression, &filterAdded) } if params.SearchTerm != nil { - filterExpression := expression.Name("event_data").Contains(*params.SearchTerm) + filterExpression := expression.Name("event_data_lower").Contains(strings.ToLower(*params.SearchTerm)) filter = addConditionToFilter(filter, filterExpression, &filterAdded) } if filterAdded { @@ -240,14 +244,17 @@ func addTimeExpression(keyCond expression.KeyConditionBuilder, params *eventOps. // SearchEvents returns list of events matching with filter criteria. func (repo *repository) SearchEvents(params *eventOps.SearchEventsParams, pageSize int64) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "v1.events.repository.SearchEvents", + "pageSize": pageSize, + } + if params.ProjectID == nil { return nil, errors.New("invalid request. projectID is compulsory") } var condition expression.KeyConditionBuilder var indexName, pk, sk string builder := expression.NewBuilder().WithProjection(buildProjection()) - // The table we're interested in - tableName := fmt.Sprintf("cla-%s-events", repo.stage) switch { case params.ProjectID != nil: @@ -276,7 +283,7 @@ func (repo *repository) SearchEvents(params *eventOps.SearchEventsParams, pageSi KeyConditionExpression: expr.KeyCondition(), ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), - TableName: aws.String(tableName), + TableName: aws.String(repo.eventsTable), IndexName: aws.String(indexName), Limit: aws.Int64(pageSize), // The maximum number of items to evaluate (not necessarily the number of matching items) } @@ -285,71 +292,107 @@ func (repo *repository) SearchEvents(params *eventOps.SearchEventsParams, pageSi } if params.NextKey != nil { - log.Debugf("Received a nextKey, value: %s", *params.NextKey) - queryInput.ExclusiveStartKey, err = fromString(*params.NextKey) + queryInput.ExclusiveStartKey, err = decodeNextKey(*params.NextKey) if err != nil { + log.WithFields(f).WithError(err).Warn("problem decoding next key value") return nil, err } + log.WithFields(f).Debugf("received a nextKey, value: %s - decoded: %+v", *params.NextKey, queryInput.ExclusiveStartKey) } - var lastEvaluatedKey string events := make([]*models.Event, 0) + var results *dynamodb.QueryOutput - for ok := true; ok; ok = lastEvaluatedKey != "" { - results, errQuery := repo.dynamoDBClient.Query(queryInput) + for { + // Perform the query... + var errQuery error + results, errQuery = repo.dynamoDBClient.Query(queryInput) if errQuery != nil { - log.Warnf("error retrieving events. error = %s", errQuery.Error()) + log.WithFields(f).WithError(errQuery).Warnf("error retrieving events") return nil, errQuery } + // Build the result models eventsList, modelErr := buildEventListModels(results) if modelErr != nil { + log.WithFields(f).WithError(modelErr).Warn("error convert event list models") return nil, modelErr } + // Trim to how many the caller asked for - just in case we go over events = append(events, eventsList...) - if len(results.LastEvaluatedKey) != 0 { - queryInput.ExclusiveStartKey = results.LastEvaluatedKey + if int64(len(events)) > pageSize { + events = events[:pageSize] } - lastEvaluatedKey, err = toString(results.LastEvaluatedKey) - if err != nil { - return nil, err + log.WithFields(f).Debugf("loaded %d events", len(events)) + + // We have more records if last evaluated key has a value + log.WithFields(f).Debugf("last evaluated key %+v", results.LastEvaluatedKey) + if len(results.LastEvaluatedKey) > 0 { + queryInput.ExclusiveStartKey = results.LastEvaluatedKey + } else { + break } + if int64(len(events)) >= pageSize { break } } - return &models.EventList{ - Events: events, - NextKey: lastEvaluatedKey, - }, nil + response := &models.EventList{ + Events: events, + } + + log.WithFields(f).Debugf("returning %d events - last key: %+v", len(events), results.LastEvaluatedKey) + if len(results.LastEvaluatedKey) > 0 { + log.WithFields(f).Debug("building next key...") + encodedString, err := buildNextKey(indexName, events[len(events)-1]) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to build nextKey") + } + response.NextKey = encodedString + log.WithFields(f).Debugf("lastEvaluatedKey encoded is: %s", encodedString) + } + + return response, nil } // queryEventsTable queries events table on index func (repo *repository) queryEventsTable(indexName string, condition expression.KeyConditionBuilder, filter *expression.ConditionBuilder, nextKey *string, pageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { f := logrus.Fields{ - "functionName": "events.queryEventsTable", + "functionName": "v1.events.repository.queryEventsTable", "indexName": indexName, - "nextKey": aws.StringValue(nextKey), - "pageSize": aws.Int64Value(pageSize), - "all": all, - "searchTerm": aws.StringValue(searchTerm), + //"nextKey": aws.StringValue(nextKey), + "pageSize": aws.Int64Value(pageSize), + "all": all, + "searchTerm": aws.StringValue(searchTerm), } - log.WithFields(f).Debug("querying events table") - builder := expression.NewBuilder() // .WithProjection(buildProjection()) + log.WithFields(f).Debug("querying events table...") + builder := expression.NewBuilder().WithKeyCondition(condition) - // The table we're interested in - tableName := fmt.Sprintf("cla-%s-events", repo.stage) + // If we have a search term... + if searchTerm != nil { + log.WithFields(f).Debugf("adding searchTerm: %s", *searchTerm) + eventLowerFilter := expression.Name("event_data_lower").Contains(strings.ToLower(*searchTerm)) + if filter == nil { + // Create a new filter + filter = &eventLowerFilter + } else { + // Add to existing filter + filter.And(eventLowerFilter) + } + } - builder = builder.WithKeyCondition(condition) + // Add the filter to the builder if filter != nil { builder = builder.WithFilter(*filter) } + // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { + log.WithFields(f).WithError(err).Warn("problem building events query") return nil, err } @@ -359,9 +402,9 @@ func (repo *repository) queryEventsTable(indexName string, condition expression. ExpressionAttributeValues: expr.Values(), KeyConditionExpression: expr.KeyCondition(), ProjectionExpression: expr.Projection(), - TableName: aws.String(tableName), + TableName: aws.String(repo.eventsTable), IndexName: aws.String(indexName), - ScanIndexForward: aws.Bool(false), + ScanIndexForward: aws.Bool(false), // Specifies the order for index traversal: If true (default), the traversal is performed in ascending order; if false, the traversal is performed in descending order. } if filter != nil { @@ -369,91 +412,85 @@ func (repo *repository) queryEventsTable(indexName string, condition expression. } if all { - pageSize = aws.Int64(HugePageSize) + queryInput.Limit = aws.Int64(HugePageSize) } else { if pageSize == nil { - pageSize = aws.Int64(DefaultPageSize) + queryInput.Limit = aws.Int64(DefaultPageSize) + } else { + if *pageSize > HugePageSize { + queryInput.Limit = aws.Int64(HugePageSize) + } + queryInput.Limit = pageSize } } + maxResults := *queryInput.Limit - if searchTerm != nil { - // since we are filtering data in client side, we should use large pageSize to avoid recursive query - queryInput.Limit = aws.Int64(HugePageSize) - } else { - queryInput.Limit = aws.Int64(*pageSize + 1) - } - - if nextKey != nil && !all { - // log.Debugf("Received a nextKey, value: %s", *nextKey) - queryInput.ExclusiveStartKey, err = fromString(*nextKey) + // If we have the next key, set the exclusive start key value + if nextKey != nil { + queryInput.ExclusiveStartKey, err = decodeNextKey(*nextKey) if err != nil { + log.WithFields(f).WithError(err).Warn("problem decoding next key value") return nil, err } + log.WithFields(f).Debugf("received a nextKey, value: %s - decoded: %+v", *nextKey, queryInput.ExclusiveStartKey) } - // log.WithField("queryInput", *queryInput).Debug("query") - var lastEvaluatedKey string events := make([]*models.Event, 0) + var results *dynamodb.QueryOutput - if searchTerm != nil { - searchTerm = aws.String(strings.ToLower(*searchTerm)) - } - - for ok := true; ok; ok = lastEvaluatedKey != "" { - results, errQuery := repo.dynamoDBClient.Query(queryInput) + for { + // Perform the query... + var errQuery error + results, errQuery = repo.dynamoDBClient.Query(queryInput) if errQuery != nil { - log.WithFields(f).WithError(errQuery).Warnf("error retrieving events. error = %s", errQuery.Error()) + log.WithFields(f).WithError(errQuery).Warn("error retrieving events") return nil, errQuery } + // Build the result models eventsList, modelErr := buildEventListModels(results) if modelErr != nil { + log.WithFields(f).WithError(modelErr).Warn("error convert event list models") return nil, modelErr } - if searchTerm != nil { - for _, event := range eventsList { - if !all { - if int64(len(events)) >= (*pageSize + 1) { - break - } - } - if strings.Contains(strings.ToLower(event.EventData), *searchTerm) { - events = append(events, event) - } - } - } else { - events = append(events, eventsList...) + + // Trim to how many the caller asked for - just in case we go over + events = append(events, eventsList...) + if int64(len(events)) > maxResults { + events = events[:maxResults] } + log.WithFields(f).Debugf("loaded %d events", len(events)) - if len(results.LastEvaluatedKey) != 0 { + // We have more records if last evaluated key has a value + log.WithFields(f).Debugf("last evaluated key %+v", results.LastEvaluatedKey) + if len(results.LastEvaluatedKey) > 0 { queryInput.ExclusiveStartKey = results.LastEvaluatedKey } else { break } - if !all { - if int64(len(events)) >= (*pageSize + 1) { - break - } + if int64(len(events)) >= maxResults { + break } } - if !all { - if int64(len(events)) > *pageSize { - events = events[0:*pageSize] - lastEvaluatedKey, err = buildNextKey(indexName, events[*pageSize-1]) + + if len(events) > 0 { + response := &models.EventList{ + Events: events, + } + + log.WithFields(f).Debugf("returning %d events - last key: %+v", len(events), results.LastEvaluatedKey) + if len(results.LastEvaluatedKey) > 0 { + log.WithFields(f).Debug("building next key...") + encodedString, err := buildNextKey(indexName, events[len(events)-1]) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to build nextKey. index = %s, event = %#v error = %s", indexName, events[*pageSize-1], err.Error()) + log.WithFields(f).WithError(err).Warn("unable to build nextKey") } - } else { - events = events[0:int64(len(events))] + response.NextKey = encodedString + log.WithFields(f).Debugf("lastEvaluatedKey encoded is: %s", encodedString) } - } - if len(events) > 0 { - return &models.EventList{ - Events: events, - NextKey: lastEvaluatedKey, - }, nil + return response, nil } // Just return an empty response - no events - just an empty list, and no nextKey @@ -485,14 +522,18 @@ func buildNextKey(indexName string, event *models.Event) (string, error) { case CompanyIDEventTypeIndex: nextKey["company_id"] = &dynamodb.AttributeValue{S: aws.String(event.EventCompanyID)} nextKey["event_type"] = &dynamodb.AttributeValue{S: aws.String(event.EventType)} + case EventCLAGroupIDEpochIndex: + nextKey["event_cla_group_id"] = &dynamodb.AttributeValue{S: aws.String(event.EventCLAGroupID)} + nextKey["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(event.EventTimeEpoch, 10))} } - return toString(nextKey) + + return encodeNextKey(nextKey) } // GetCompanyFoundationEvents returns the list of events for foundation and company func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, foundationSFID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { f := logrus.Fields{ - "functionName": "events.repository.GetCompanyFoundationEvents", + "functionName": "v1.events.repository.GetCompanyFoundationEvents", "companySFID": companySFID, "companyID": companyID, "foundationSFID": foundationSFID, @@ -511,7 +552,7 @@ func (repo *repository) GetCompanyFoundationEvents(companySFID, companyID, found // GetCompanyClaGroupEvents returns the list of events for cla group and the company func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGroupID string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { f := logrus.Fields{ - "functionName": "events.repository.GetCompanyClaGroupEvents", + "functionName": "v1.events.repository.GetCompanyClaGroupEvents", "companySFID": companySFID, "companyID": companyID, "claGroupID": claGroupID, @@ -530,7 +571,7 @@ func (repo *repository) GetCompanyClaGroupEvents(companySFID, companyID, claGrou // GetCompanyEvents returns the list of events for given company id and event types func (repo *repository) GetCompanyEvents(companyID, eventType string, nextKey *string, paramPageSize *int64, all bool) (*models.EventList, error) { f := logrus.Fields{ - "functionName": "events.repository.GetCompanyEvents", + "functionName": "v1.events.repository.GetCompanyEvents", "companyID": companyID, "nextKey": utils.StringValue(nextKey), "paramPageSize": utils.Int64Value(paramPageSize), @@ -546,7 +587,7 @@ func (repo *repository) GetCompanyEvents(companyID, eventType string, nextKey *s // GetFoundationEvents returns the list of foundation events func (repo *repository) GetFoundationEvents(foundationSFID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { f := logrus.Fields{ - "functionName": "events.repository.GetFoundationEvents", + "functionName": "v1.events.repository.GetFoundationEvents", "foundationSFID": foundationSFID, "nextKey": utils.StringValue(nextKey), "paramPageSize": utils.Int64Value(paramPageSize), @@ -561,7 +602,7 @@ func (repo *repository) GetFoundationEvents(foundationSFID string, nextKey *stri // GetClaGroupEvents returns the list of cla-group events func (repo *repository) GetClaGroupEvents(claGroupID string, nextKey *string, paramPageSize *int64, all bool, searchTerm *string) (*models.EventList, error) { f := logrus.Fields{ - "functionName": "events.repository.GetClaGroupEvents", + "functionName": "v1.events.repository.GetClaGroupEvents", "claGroupID": claGroupID, "nextKey": utils.StringValue(nextKey), "paramPageSize": utils.Int64Value(paramPageSize), @@ -573,8 +614,8 @@ func (repo *repository) GetClaGroupEvents(claGroupID string, nextKey *string, pa return repo.queryEventsTable(EventCLAGroupIDEpochIndex, keyCondition, nil, nextKey, paramPageSize, all, searchTerm) } -// toString encodes the map as a string -func toString(in map[string]*dynamodb.AttributeValue) (string, error) { +// encodeNextKey encodes the map as a string +func encodeNextKey(in map[string]*dynamodb.AttributeValue) (string, error) { if len(in) == 0 { return "", nil } @@ -585,35 +626,47 @@ func toString(in map[string]*dynamodb.AttributeValue) (string, error) { return base64.StdEncoding.EncodeToString(b), nil } -// fromString converts the string to a map -func fromString(str string) (map[string]*dynamodb.AttributeValue, error) { +// decodeNextKey decodes the next key value into a dynamodb attribute value +func decodeNextKey(str string) (map[string]*dynamodb.AttributeValue, error) { + f := logrus.Fields{ + "functionName": "v1.events.repository.decodeNextKey", + } + sDec, err := base64.StdEncoding.DecodeString(str) if err != nil { + log.WithFields(f).WithError(err).Warnf("error decoding string %s", str) return nil, err } + var m map[string]*dynamodb.AttributeValue err = json.Unmarshal(sDec, &m) if err != nil { + log.WithFields(f).WithError(err).Warnf("error unmarshalling string after decoding: %s", sDec) return nil, err } + return m, nil } // buildEventListModel converts the query results to a list event models func buildEventListModels(results *dynamodb.QueryOutput) ([]*models.Event, error) { + f := logrus.Fields{ + "functionName": "v1.events.repository.buildEventListModels", + } events := make([]*models.Event, 0) var items []Event err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &items) if err != nil { - log.Warnf("error unmarshalling events from database, error: %v", - err) + log.WithFields(f).WithError(err).Warn("error unmarshalling events from database") return nil, err } + for _, e := range items { events = append(events, e.toEvent()) } + return events, nil } @@ -639,6 +692,11 @@ func buildProjection() expression.ProjectionBuilder { } func (repo repository) GetRecentEvents(pageSize int64) (*models.EventList, error) { + f := logrus.Fields{ + "functionName": "v1.events.repository.GetRecentEvents", + "pageSize": pageSize, + } + ctime := time.Now() maxQueryDays := 30 events := make([]*models.Event, 0) @@ -646,6 +704,7 @@ func (repo repository) GetRecentEvents(pageSize int64) (*models.EventList, error day := toDateFormat(ctime) eventList, err := repo.getEventByDay(day, false, pageSize) if err != nil { + log.WithFields(f).WithError(err).Warn("error fetching events by day") return nil, err } events = append(events, eventList...) @@ -659,11 +718,16 @@ func (repo repository) GetRecentEvents(pageSize int64) (*models.EventList, error return &models.EventList{ Events: events, }, nil - } func (repo repository) getEventByDay(day string, containsPII bool, pageSize int64) ([]*models.Event, error) { - tableName := fmt.Sprintf("cla-%s-events", repo.stage) + f := logrus.Fields{ + "functionName": "v1.events.repository.getEventByDay", + "day": day, + "containsPII": containsPII, + "pageSize": pageSize, + } + var condition expression.KeyConditionBuilder builder := expression.NewBuilder().WithProjection(buildProjection()) @@ -676,8 +740,10 @@ func (repo repository) getEventByDay(day string, containsPII bool, pageSize int6 // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { + log.WithFields(f).WithError(err).Warn("error building events query") return nil, err } + // Assemble the query input parameters queryInput := &dynamodb.QueryInput{ ExpressionAttributeNames: expr.Names(), @@ -685,7 +751,7 @@ func (repo repository) getEventByDay(day string, containsPII bool, pageSize int6 KeyConditionExpression: expr.KeyCondition(), ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), - TableName: aws.String(tableName), + TableName: aws.String(repo.eventsTable), IndexName: aws.String(indexName), Limit: aws.Int64(pageSize), // The maximum number of items to evaluate (not necessarily the number of matching items) ScanIndexForward: aws.Bool(false), @@ -696,12 +762,13 @@ func (repo repository) getEventByDay(day string, containsPII bool, pageSize int6 for { results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { - log.Warnf("error retrieving events. error = %s", errQuery.Error()) + log.WithFields(f).Warnf("error retrieving events. error = %s", errQuery.Error()) return nil, errQuery } eventsList, modelErr := buildEventListModels(results) if modelErr != nil { + log.WithFields(f).Warn("error building event models") return nil, modelErr } @@ -721,9 +788,18 @@ func (repo repository) getEventByDay(day string, containsPII bool, pageSize int6 } func (repo repository) AddDataToEvent(eventID, parentProjectSFID, projectSFID, projectSFName, companySFID, projectID string) error { - tableName := fmt.Sprintf("cla-%s-events", repo.stage) + f := logrus.Fields{ + "functionName": "v1.events.repository.AddDataToEvent", + "eventID": eventID, + "parentProjectSFID": parentProjectSFID, + "projectSFID": projectSFID, + "projectSFName": projectSFName, + "companySFID": companySFID, + "projectID": projectID, + } + input := &dynamodb.UpdateItemInput{ - TableName: aws.String(tableName), + TableName: aws.String(repo.eventsTable), Key: map[string]*dynamodb.AttributeValue{ "event_id": { S: aws.String(eventID), @@ -758,6 +834,7 @@ func (repo repository) AddDataToEvent(eventID, parentProjectSFID, projectSFID, p ue.AddUpdateExpression("#company_sfid_project_id = :company_sfid_project_id", companySFID != "" && projectID != "") if ue.Expression == "" { // nothing to update + log.WithFields(f).Warn("not expression - nothing to update") return nil } input.UpdateExpression = aws.String(ue.Expression) @@ -765,9 +842,9 @@ func (repo repository) AddDataToEvent(eventID, parentProjectSFID, projectSFID, p input.ExpressionAttributeValues = ue.ExpressionAttributeValues _, updateErr := repo.dynamoDBClient.UpdateItem(input) if updateErr != nil { - log.Debugf("update input: %v", input) - log.Warnf("unable to add extra details to event : %s . error = %s", eventID, updateErr.Error()) + log.WithFields(f).WithError(updateErr).Warnf("unable to add extra details to event : %s . error = %s", eventID, updateErr.Error()) return updateErr } + return nil } diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 8d6aacfd9..fb5d6291e 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -959,8 +959,8 @@ paths: /events/foundation/{foundationSFID}: get: - summary: Get the events for the foundation - description: get all the events for the foundation + summary: Get foundation events + description: Returns events for the specified foundation operationId: getFoundationEvents parameters: - $ref: "#/parameters/path-foundationSFID" @@ -996,8 +996,8 @@ paths: /events/project/{projectSFID}: get: - summary: Get the events for the project - description: get all the events for the project + summary: Get project events + description: Returns events for the specified project operationId: getProjectEvents parameters: - $ref: "#/parameters/path-projectSFID" diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 6c16e0b67..f5de6db4b 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -4169,6 +4169,7 @@ class Meta: event_date = UnicodeAttribute(null=True) event_data = UnicodeAttribute(null=True) + event_data_lower = UnicodeAttribute(null=True) event_summary = UnicodeAttribute(null=True) event_date_and_contains_pii = UnicodeAttribute(null=True) @@ -4228,6 +4229,8 @@ def __init__( self.model.event_company_name_lower = self.model.event_company_name.lower() self.model.event_data = event_data + if self.model.event_data: + self.model.event_data_lower = self.model.event_data.lower() self.model.event_summary = event_summary self.model.contains_pii = contains_pii @@ -4284,6 +4287,9 @@ def get_event_user_id(self): def get_event_data(self): return self.model.event_data + def get_event_data_lower(self): + return self.model.event_data_lower + def get_event_summary(self): return self.model.event_summary @@ -4373,6 +4379,11 @@ def all_limit(self, limit: Optional[int] = None, last_evaluated_key: Optional[st def set_event_data(self, event_data: str): self.model.event_data = event_data + self.model.event_data_lower = event_data.lower() + + def set_event_data_lower(self, event_data: str): + if event_data: + self.model.event_data_lower = event_data.lower() def set_event_summary(self, event_summary: str): self.model.event_summary = event_summary From c07292196f5c27ffb14b95bf88fa4e085094c058 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 25 Mar 2021 17:51:52 -0700 Subject: [PATCH 0191/1276] Updatd PyYAML Lib Vulnerability (#2823) - Resolved CVE-2020-14343 moderate severity Vulnerable versions: < 5.4 Patched version: 5.4 A vulnerability was discovered in the PyYAML library in versions before 5.4, where it is susceptible to arbitrary code execution when it processes untrusted YAML files through the full_load method or with the FullLoader loader. Applications that use the library to process untrusted input may be vulnerable to this flaw. This flaw allows an attacker to execute arbitrary code on the system by abusing the python/object/new constructor. This flaw is due to an incomplete fix for CVE-2020-1747. Signed-off-by: David Deal --- cla-backend-go/swagger/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/swagger/requirements.txt b/cla-backend-go/swagger/requirements.txt index 3b2ca554f..2b8cda8f5 100644 --- a/cla-backend-go/swagger/requirements.txt +++ b/cla-backend-go/swagger/requirements.txt @@ -2,4 +2,4 @@ # SPDX-License-Identifier: MIT click==7.1.2 -PyYAML==5.3.1 +PyYAML>=5.4 From bd7c9293f3adc8f8bb71c7b88d1c02a3ccccf9a5 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 29 Mar 2021 18:38:40 +0300 Subject: [PATCH 0192/1276] [LFX 3147] Bug/Approval List Update (Ecla) (#2824) - Handled empty ecla list for approval list updates Signed-off-by: wanyaland --- cla-backend-go/signatures/repository.go | 30 +++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 8977e0c1a..63f8d0c45 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2088,21 +2088,27 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", appErr) return } - log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) - note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to email approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveEmailApprovalList) - signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) - if signErr != nil { - log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) - return + if len(signs.Signatures) == 0 { + log.WithFields(f).Debugf("company employee signatures do not exist for company:%s and project: %s ", companyID, projectID) } - //Log Event - eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ - SignatureID: signs.Signatures[0].SignatureID, - Email: email, - CLAManager: claManager, - CLAGroupID: signs.Signatures[0].ProjectID, + if len(signs.Signatures) > 0 { + log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to email approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveEmailApprovalList) + + signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) + if signErr != nil { + log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) + return + } + //Log Event + eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ + SignatureID: signs.Signatures[0].SignatureID, + Email: email, + CLAManager: claManager, + CLAGroupID: signs.Signatures[0].ProjectID, + } } repo.eventsService.LogEventWithContext(ctx, eventArgs) From cbd1bb30adfc85fa668ec28f892cc4c303d35222 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 29 Mar 2021 15:09:09 -0700 Subject: [PATCH 0193/1276] Added Python Event Function for Searching For Missing Data Lower Column (#2825) - Added search_missing_event_data_lower() function - Added type hints Signed-off-by: David Deal --- cla-backend/cla/models/dynamo_models.py | 86 +++++++++++++++++-------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index f5de6db4b..e89bf2e26 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -32,8 +32,8 @@ from cla.models import model_interfaces, key_value_store_interface, DoesNotExist from cla.models.event_types import EventType from cla.models.model_interfaces import User, Signature, ProjectCLAGroup, Repository, Gerrit -from cla.project_service import ProjectService from cla.models.model_utils import is_uuidv4 +from cla.project_service import ProjectService stage = os.environ.get("STAGE", "") cla_logo_url = os.environ.get("CLA_BUCKET_LOGO_URL", "") @@ -4281,79 +4281,85 @@ def load(self, event_id): raise cla.models.DoesNotExist("Event not found") self.model = event - def get_event_user_id(self): + def get_event_date_created(self) -> str: + return self.model.date_created + + def get_event_date_modified(self) -> str: + return self.model.date_modified + + def get_event_user_id(self) -> str: return self.model.event_user_id - def get_event_data(self): + def get_event_data(self) -> str: return self.model.event_data - def get_event_data_lower(self): + def get_event_data_lower(self) -> str: return self.model.event_data_lower - def get_event_summary(self): + def get_event_summary(self) -> str: return self.model.event_summary - def get_event_date(self): + def get_event_date(self) -> str: return self.model.event_date - def get_event_id(self): + def get_event_id(self) -> str: return self.model.event_id - def get_event_cla_group_id(self): + def get_event_cla_group_id(self) -> str: return self.model.event_cla_group_id - def get_event_cla_group_name(self): + def get_event_cla_group_name(self) -> str: return self.model.event_cla_group_name - def get_event_cla_group_name_lower(self): + def get_event_cla_group_name_lower(self) -> str: return self.model.event_cla_group_name_lower - def get_event_project_id(self): + def get_event_project_id(self) -> str: return self.model.event_project_id - def get_event_project_sfid(self): + def get_event_project_sfid(self) -> str: return self.model.event_project_sfid - def get_event_project_name(self): + def get_event_project_name(self) -> str: return self.model.event_project_name - def get_event_project_name_lower(self): + def get_event_project_name_lower(self) -> str: return self.model.event_project_name_lower - def get_event_parent_project_sfid(self): + def get_event_parent_project_sfid(self) -> str: return self.model.event_parent_project_sfid - def get_event_parent_project_name(self): + def get_event_parent_project_name(self) -> str: return self.model.event_parent_project_name - def get_event_type(self): + def get_event_type(self) -> str: return self.model.event_type - def get_event_time(self): + def get_event_time(self) -> str: return self.model.date_created - def get_event_time_epoch(self): + def get_event_time_epoch(self) -> int: return self.model.event_time_epoch - def get_event_company_id(self): + def get_event_company_id(self) -> str: return self.model.event_company_id - def get_event_company_sfid(self): + def get_event_company_sfid(self) -> str: return self.model.event_company_sfid - def get_event_company_name(self): + def get_event_company_name(self) -> str: return self.model.event_company_name - def get_event_company_name_lower(self): + def get_event_company_name_lower(self) -> str: return self.model.event_company_name_lower - def get_event_user_name(self): + def get_event_user_name(self) -> str: return self.model.event_user_name - def get_event_user_name_lower(self): + def get_event_user_name_lower(self) -> str: return self.model.event_user_name_lower - def get_company_id_external_project_id(self): + def get_company_id_external_project_id(self) -> str: return self.model.company_id_external_project_id def all(self, ids=None): @@ -4377,6 +4383,34 @@ def all_limit(self, limit: Optional[int] = None, last_evaluated_key: Optional[st ret.append(evt) return ret, result_iterator.last_evaluated_key, result_iterator.total_count + def search_missing_event_data_lower(self, limit: Optional[int] = None, last_evaluated_key: Optional[str] = None): + filter_condition = (EventModel.event_data_lower.does_not_exist()) + projection = ["event_id", "event_data", "event_data_lower"] + result_iterator = self.model.scan(limit=limit, + last_evaluated_key=last_evaluated_key, + filter_condition=filter_condition, + attributes_to_get=projection) + ret = [] + for signature in result_iterator: + evt = Event() + evt.model = signature + ret.append(evt) + return ret, result_iterator.last_evaluated_key, result_iterator.total_count + + # def search_by_year(self, year: str, limit: Optional[int] = None, last_evaluated_key: Optional[str] = None): + # filter_condition = (EventModel.event_date.contains(year)) + # projection = ["event_id", "event_date"] + # result_iterator = self.model.scan(limit=limit, + # last_evaluated_key=last_evaluated_key, + # filter_condition=filter_condition, + # attributes_to_get=projection) + # ret = [] + # for signature in result_iterator: + # evt = Event() + # evt.model = signature + # ret.append(evt) + # return ret, result_iterator.last_evaluated_key, result_iterator.total_count + def set_event_data(self, event_data: str): self.model.event_data = event_data self.model.event_data_lower = event_data.lower() From 434b34f923c54aa817f7179e77d6f405e3418672 Mon Sep 17 00:00:00 2001 From: makkalot Date: Tue, 30 Mar 2021 13:01:43 +0300 Subject: [PATCH 0194/1276] closing docraptor body is handled elsewhere, doing it here breaks the code Signed-off-by: makkalot --- cla-backend-go/docraptor/client.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cla-backend-go/docraptor/client.go b/cla-backend-go/docraptor/client.go index 89ffd83da..16a0ed202 100644 --- a/cla-backend-go/docraptor/client.go +++ b/cla-backend-go/docraptor/client.go @@ -72,12 +72,5 @@ func (dc Client) CreatePDF(html string, claType string) (io.ReadCloser, error) { log.WithFields(f).WithError(err).Warn("problem with API call to docraptor") return nil, err } - defer func() { - closeErr := resp.Body.Close() - if closeErr != nil { - log.WithFields(f).WithError(closeErr).Warn("error closing response body") - } - }() - return resp.Body, nil } From 9e1035bbea5e3ce814a216990f18839929512190 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 30 Mar 2021 20:11:51 +0300 Subject: [PATCH 0195/1276] [#LFX 3147] Bug/Approval List (#2828) - Ignore invalidate signatures errors raised when updating approval List Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/signatures/repository.go | 98 ++++++++++++------------- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 19499d7aa..9f4b4ebe8 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -97,6 +97,7 @@ github.com/communitybridge/easycla v1.0.133 h1:aJulQGLLRISCMsZcCP4aIE8xGtHoBNm/E github.com/communitybridge/easycla v1.0.135 h1:Dvn8jX+7BAnpmA+jvdK0n5ajWP8SoH5vvopt7whZDEU= github.com/communitybridge/easycla v1.0.145 h1:ikhBSsOeEL2u3/EoyDsufh/j3HkjfFTiXAk1d61GoS8= github.com/communitybridge/easycla v2.0.10+incompatible h1:6eRJ5fxrMxRZHBkg8piYo+zHTcSowMrP85nZXzp5mpA= +github.com/communitybridge/easycla v2.0.16+incompatible h1:I0hEApDh4IvlwRPyHV1LOsSYlSPbqBsGszjSTHwkdak= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 63f8d0c45..c5d2b074a 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2168,7 +2168,6 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if invalidateErr != nil { msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) log.WithFields(f).Warn(msg) - return nil, errors.New(msg) } } } @@ -2373,7 +2372,34 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A iclas, err := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil) if err != nil { log.WithFields(f).Warn("unable to get iclas") - return err + } + + if iclas != nil { + var iclaWg sync.WaitGroup + //Iterate iclas + iclaWg.Add(len(iclas.List)) + log.WithFields(f).Debug("invalidating signature icla records... ") + for _, icla := range iclas.List { + go func(icla *models.IclaSignature) { + defer iclaWg.Done() + signature, sigErr := repo.GetSignature(ctx, icla.SignatureID) + if sigErr != nil { + log.WithFields(f).Warnf("unable to fetch signature for ID: %s ", icla.SignatureID) + return + } + // Grab user record + if signature.SignatureReferenceID == "" { + log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", signature) + return + } + verifyErr := repo.verifyUserApprovals(ctx, signature.SignatureReferenceID, signature.SignatureID, claManager, approvalList) + if verifyErr != nil { + log.WithFields(f).Warnf("unable to verify user: %s ", signature.SignatureReferenceID) + return + } + }(icla) + } + iclaWg.Wait() } // Get ECLAs @@ -2387,54 +2413,28 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A return err } - var iclaWg, eclaWg sync.WaitGroup - - //Iterate iclas - iclaWg.Add(len(iclas.List)) - log.WithFields(f).Debug("invalidating signature icla records... ") - - for _, icla := range iclas.List { - go func(icla *models.IclaSignature) { - defer iclaWg.Done() - signature, err := repo.GetSignature(ctx, icla.SignatureID) - if err != nil { - log.WithFields(f).Warnf("unable to fetch signature for ID: %s ", icla.SignatureID) - return - } - // Grab user record - if signature.SignatureReferenceID == "" { - log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", signature) - return - } - verifyErr := repo.verifyUserApprovals(ctx, signature.SignatureReferenceID, signature.SignatureID, claManager, approvalList) - if verifyErr != nil { - log.WithFields(f).Warnf("unable to verify user: %s ", signature.SignatureReferenceID) - return - } - }(icla) - } - iclaWg.Wait() - - log.WithFields(f).Debug("invalidating signature ecla records... ") - // Iterate eclas - eclaWg.Add(len(eclas.Signatures)) - for _, ecla := range eclas.Signatures { - go func(ecla *models.Signature) { - defer eclaWg.Done() - // Grab user record - if ecla.SignatureReferenceID == "" { - log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", ecla) - return - } - verifyErr := repo.verifyUserApprovals(ctx, ecla.SignatureReferenceID, ecla.SignatureID, claManager, approvalList) - if verifyErr != nil { - log.WithFields(f).Warnf("unable to verify user: %s ", ecla.SignatureReferenceID) - return - } - }(ecla) + if eclas != nil { + var eclaWg sync.WaitGroup + log.WithFields(f).Debug("invalidating signature ecla records... ") + // Iterate eclas + eclaWg.Add(len(eclas.Signatures)) + for _, ecla := range eclas.Signatures { + go func(ecla *models.Signature) { + defer eclaWg.Done() + // Grab user record + if ecla.SignatureReferenceID == "" { + log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", ecla) + return + } + verifyErr := repo.verifyUserApprovals(ctx, ecla.SignatureReferenceID, ecla.SignatureID, claManager, approvalList) + if verifyErr != nil { + log.WithFields(f).Warnf("unable to verify user: %s ", ecla.SignatureReferenceID) + return + } + }(ecla) + } + eclaWg.Wait() } - eclaWg.Wait() - return nil } From 1ea50d32c01d425f24bffb959b36baa104f5797a Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 30 Mar 2021 20:35:50 +0300 Subject: [PATCH 0196/1276] refactor ListClaGroupsForFoundationOrProject (#2827) handling for goroutines Signed-off-by: makkalot --- cla-backend-go/v2/cla_groups/service.go | 293 ++++++++++++++---------- 1 file changed, 169 insertions(+), 124 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index b9d5ebaf8..d5aa44c65 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -10,6 +10,7 @@ import ( "sort" "strings" "sync" + "time" "github.com/aws/aws-sdk-go/aws" @@ -355,6 +356,11 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje "projectOrFoundationSFID": projectOrFoundationSFID, } + // setup some timeout for the whole operation + var cancelFunc context.CancelFunc + ctx, cancelFunc = context.WithTimeout(ctx, time.Second*20) + defer cancelFunc() + // Our list of CLA Groups associated with this foundation (could be > 1) or project (only 1) var v1ClaGroups = new(v1Models.ClaGroups) // Our response model for this function @@ -398,106 +404,90 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje // If it's a project... if utils.IsProjectCategory(sfProjectModelDetails, parentDetails) { - // Since this is a project and not a foundation, we'll want to set he parent foundation ID and name (which is - // our parent in this case) - log.WithFields(f).Debug("found 'project' in platform project service.") - if sfProjectModelDetails.ProjectOutput.Foundation != nil { - foundationID = sfProjectModelDetails.ProjectOutput.Foundation.ID - foundationName = sfProjectModelDetails.ProjectOutput.Foundation.Name - log.WithFields(f).Debugf("using parent foundation ID: %s and name: %s", foundationID, foundationName) - } else { - // Project with no parent - must be a standalone - use our ID and Name as the foundation - foundationID = sfProjectModelDetails.ID - foundationName = sfProjectModelDetails.Name - log.WithFields(f).Debugf("no parent - using project as foundation ID: %s and name: %s", foundationID, foundationName) + var appendErr error + foundationID, foundationName, appendErr = s.appendCLAGroupsForProject(ctx, f, projectOrFoundationSFID, sfProjectModelDetails, v1ClaGroups) + if appendErr != nil { + return nil, appendErr } - - log.WithFields(f).Debug("locating CLA Group mapping...") - projectCLAGroup, lookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(projectOrFoundationSFID) - if lookupErr != nil { - log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) - return nil, &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} + } else if sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup { + if err := s.appendCLAGroupsForFoundation(ctx, f, projectOrFoundationSFID, v1ClaGroups); err != nil { + return nil, err } + } else { + msg := fmt.Sprintf("unsupported foundation/project SFID type: %s", sfProjectModelDetails.ProjectType) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } - log.WithFields(f).Debugf("loading CLA Group by ID: %s", projectCLAGroup.ClaGroupID) - v1ClaGroupsByProject, claGroupLoadErr := s.v1ProjectService.GetCLAGroupByID(ctx, projectCLAGroup.ClaGroupID) - //v1ClaGroupsByProject, prjerr := s.v1ProjectService.GetClaGroupByProjectSFID(projectOrFoundationSFID, DontLoadDetails) - if claGroupLoadErr != nil { - log.WithFields(f).Warnf("problem loading CLA group by id, error: %+v", claGroupLoadErr) - return nil, &utils.CLAGroupNotFound{CLAGroupID: projectCLAGroup.ClaGroupID, Err: claGroupLoadErr} - } + log.WithFields(f).Debugf("Building response model for %d CLA Groups", len(v1ClaGroups.Projects)) - v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1ClaGroupsByProject) + claGroupIDList, err := s.buildClaGroupSummaryResponseModel(ctx, f, v1ClaGroups, foundationName, foundationID, responseModel) + if err != nil { + return nil, err + } - v1CLAGroupData, v1ClaGroupErr := s.v1ProjectService.GetClaGroupByProjectSFID(ctx, projectOrFoundationSFID, false) - if v1ClaGroupErr != nil { - log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", v1ClaGroupErr) - return nil, &utils.CLAGroupNotFound{CLAGroupID: projectOrFoundationSFID, Err: v1ClaGroupErr} - } + // One more pass to update the metrics - bulk lookup the metrics and update the response model + log.WithFields(f).Debugf("Loading metrics for %d CLA Groups...", len(claGroupIDList.List())) + s.loadMetrics(ctx, f, responseModel, claGroupIDList) - _, found := Find(v1ClaGroups.Projects, v1CLAGroupData.ProjectID) - if !found { - v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1CLAGroupData) + // Sort the response based on the Foundation and CLA group name + sort.Slice(responseModel.List, func(i, j int) bool { + switch strings.Compare(responseModel.List[i].FoundationName, responseModel.List[j].FoundationName) { + case -1: + return true + case 1: + return false } + return responseModel.List[i].ClaGroupName < responseModel.List[j].ClaGroupName + }) - } else if sfProjectModelDetails.ProjectType == utils.ProjectTypeProjectGroup { - log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") - projectCLAGroupMappings, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) - if lookupErr != nil { - log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) - return nil, &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} - } - log.WithFields(f).Debugf("discovered %d projects based on foundation SFID...", len(projectCLAGroupMappings)) + return responseModel, nil +} - // Determine how many CLA Groups we have - we could have many and possibly return duplicates, we use this loop - uniqueCLAGroupList := getUniqueCLAGroupIDs(projectCLAGroupMappings) +func (s *service) loadMetrics(ctx context.Context, f logrus.Fields, responseModel *models.ClaGroupListSummary, claGroupIDList *utils.StringSet) { + type MetricsResult struct { + index int + iclaSignatureCount int64 + cclaSignatureCount int64 + Error error + } + metricsResultChannel := make(chan *MetricsResult, len(responseModel.List)) - type CLAGroupResult struct { - claGroupModel *v1Models.ClaGroup - Error error - } - claGroupResultChannel := make(chan *CLAGroupResult, len(uniqueCLAGroupList)) - - // Load these CLA Group records in parallel - for _, projectCLAGroupClaGroupID := range uniqueCLAGroupList { - - // Load each CLA Group - save results to our channel - go func(ctx context.Context, projectCLAGroupClaGroupID string) { - log.WithFields(f).Debugf("loading CLA Group by ID: %s", projectCLAGroupClaGroupID) - claGroupModel, claGroupLookupErr := s.v1ProjectService.GetCLAGroupByID(ctx, projectCLAGroupClaGroupID) - if claGroupLookupErr != nil { - log.WithFields(f).Warnf("problem locating project by id: %s, error: %+v", projectCLAGroupClaGroupID, claGroupLookupErr) - claGroupResultChannel <- &CLAGroupResult{ - claGroupModel: nil, - Error: &utils.SFProjectNotFound{ProjectSFID: projectCLAGroupClaGroupID, Err: claGroupLookupErr}, - } - } + for idx, responseEntry := range responseModel.List { + go func(index int, responseEntry *models.ClaGroupSummary) { + log.WithFields(f).Debugf("fetching project signature metrics for CLA Group (%d): %s - %s", index, responseEntry.ClaGroupID, responseEntry.ClaGroupName) + iclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeICLA), SignatureType: aws.String(utils.SignatureTypeCLA)}) + if err != nil { + log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + } - claGroupResultChannel <- &CLAGroupResult{ - claGroupModel: claGroupModel, - Error: nil, - } - }(ctx, projectCLAGroupClaGroupID) - } + cclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeCCLA), SignatureType: aws.String(utils.SignatureTypeCCLA)}) + if err != nil { + log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + } - // Wait for the go routines to finish and load up the results - log.WithFields(f).Debug("waiting for CLA Groups to load...") - for range uniqueCLAGroupList { - response := <-claGroupResultChannel - if response.Error != nil { - log.WithFields(f).WithError(response.Error).Warnf("unable to load CLA Group") - return nil, response.Error + metricsResultChannel <- &MetricsResult{ + index: index, + iclaSignatureCount: iclaSignatureDetails.ResultCount, + cclaSignatureCount: cclaSignatureDetails.ResultCount, + Error: err, } - v1ClaGroups.Projects = append(v1ClaGroups.Projects, *response.claGroupModel) - } - } else { - msg := fmt.Sprintf("unsupported foundation/project SFID type: %s", sfProjectModelDetails.ProjectType) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) + }(idx, responseEntry) } - log.WithFields(f).Debugf("Building response model for %d CLA Groups", len(v1ClaGroups.Projects)) + log.WithFields(f).Debugf("Waiting for metrics responses for %d CLA Groups...", len(claGroupIDList.List())) + for range responseModel.List { + select { + case response := <-metricsResultChannel: + responseModel.List[response.index].TotalSignatures = response.cclaSignatureCount + response.iclaSignatureCount + case <-ctx.Done(): + log.WithError(ctx.Err()).Warnf("waiting for metrics failed with timeout") + return + } + } +} +func (s *service) buildClaGroupSummaryResponseModel(ctx context.Context, f logrus.Fields, v1ClaGroups *v1Models.ClaGroups, foundationName string, foundationID string, responseModel *models.ClaGroupListSummary) (*utils.StringSet, error) { claGroupIDList := utils.NewStringSet() // Build the response model for each CLA Group... @@ -506,11 +496,11 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje // Keep a list of the CLA Group IDs - we'll use it later to do a batch look in the metrics claGroupIDList.Add(v1ClaGroup.ProjectID) - currentICLADoc, docErr := v1Project.GetCurrentDocument(context.Background(), v1ClaGroup.ProjectIndividualDocuments) + currentICLADoc, docErr := v1Project.GetCurrentDocument(ctx, v1ClaGroup.ProjectIndividualDocuments) if docErr != nil { log.WithFields(f).WithError(docErr).Warn("problem determining current ICLA for this CLA Group") } - currentCCLADoc, docErr := v1Project.GetCurrentDocument(context.Background(), v1ClaGroup.ProjectCorporateDocuments) + currentCCLADoc, docErr := v1Project.GetCurrentDocument(ctx, v1ClaGroup.ProjectCorporateDocuments) if docErr != nil { log.WithFields(f).WithError(docErr).Warn("problem determining current CCLA for this CLA Group") } @@ -571,57 +561,112 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje // Add this CLA Group to our response model responseModel.List = append(responseModel.List, cg) } + return claGroupIDList, nil +} - // One more pass to update the metrics - bulk lookup the metrics and update the response model - log.WithFields(f).Debugf("Loading metrics for %d CLA Groups...", len(claGroupIDList.List())) - type MetricsResult struct { - index int - iclaSignatureCount int64 - cclaSignatureCount int64 - Error error +func (s *service) appendCLAGroupsForFoundation(ctx context.Context, f logrus.Fields, projectOrFoundationSFID string, v1ClaGroups *v1Models.ClaGroups) error { + log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") + projectCLAGroupMappings, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) + if lookupErr != nil { + log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) + return &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} } - metricsResultChannel := make(chan *MetricsResult, len(responseModel.List)) + log.WithFields(f).Debugf("discovered %d projects based on foundation SFID...", len(projectCLAGroupMappings)) - for idx, responseEntry := range responseModel.List { - go func(index int, responseEntry *models.ClaGroupSummary) { - log.WithFields(f).Debugf("fetching project signature metrics for CLA Group (%d): %s - %s", index, responseEntry.ClaGroupID, responseEntry.ClaGroupName) - iclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeICLA), SignatureType: aws.String(utils.SignatureTypeCLA)}) - if err != nil { - log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + // Determine how many CLA Groups we have - we could have many and possibly return duplicates, we use this loop + uniqueCLAGroupList := getUniqueCLAGroupIDs(projectCLAGroupMappings) + + type CLAGroupResult struct { + claGroupModel *v1Models.ClaGroup + Error error + } + + claGroupResultChannel := make(chan *CLAGroupResult, len(uniqueCLAGroupList)) + + // Load these CLA Group records in parallel + for _, projectCLAGroupClaGroupID := range uniqueCLAGroupList { + + // Load each CLA Group - save results to our channel + go func(ctx context.Context, projectCLAGroupClaGroupID string) { + log.WithFields(f).Debugf("loading CLA Group by ID: %s", projectCLAGroupClaGroupID) + claGroupModel, claGroupLookupErr := s.v1ProjectService.GetCLAGroupByID(ctx, projectCLAGroupClaGroupID) + if claGroupLookupErr != nil { + log.WithFields(f).Warnf("problem locating project by id: %s, error: %+v", projectCLAGroupClaGroupID, claGroupLookupErr) + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: nil, + Error: &utils.SFProjectNotFound{ProjectSFID: projectCLAGroupClaGroupID, Err: claGroupLookupErr}, + } } - cclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeCCLA), SignatureType: aws.String(utils.SignatureTypeCCLA)}) - if err != nil { - log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + claGroupResultChannel <- &CLAGroupResult{ + claGroupModel: claGroupModel, + Error: nil, } + }(ctx, projectCLAGroupClaGroupID) + } - metricsResultChannel <- &MetricsResult{ - index: index, - iclaSignatureCount: iclaSignatureDetails.ResultCount, - cclaSignatureCount: cclaSignatureDetails.ResultCount, - Error: err, + // Wait for the go routines to finish and load up the results + log.WithFields(f).Debug("waiting for CLA Groups to load...") + for range uniqueCLAGroupList { + select { + case response := <-claGroupResultChannel: + if response.Error != nil { + log.WithFields(f).WithError(response.Error).Warnf("unable to load CLA Group") + return response.Error } - }(idx, responseEntry) + v1ClaGroups.Projects = append(v1ClaGroups.Projects, *response.claGroupModel) + case <-ctx.Done(): + log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for CLA Groups to load timeouted") + return fmt.Errorf("cla group laoding failed : %v", ctx.Err()) + } } + return nil +} - log.WithFields(f).Debugf("Waiting for metrics responses for %d CLA Groups...", len(claGroupIDList.List())) - for range responseModel.List { - response := <-metricsResultChannel - responseModel.List[response.index].TotalSignatures = response.cclaSignatureCount + response.iclaSignatureCount +func (s *service) appendCLAGroupsForProject(ctx context.Context, f logrus.Fields, projectOrFoundationSFID string, sfProjectModelDetails *v2ProjectServiceModels.ProjectOutputDetailed, v1ClaGroups *v1Models.ClaGroups) (string, string, error) { + // Since this is a project and not a foundation, we'll want to set he parent foundation ID and name (which is + // our parent in this case) + var foundationID, foundationName string + log.WithFields(f).Debug("found 'project' in platform project service.") + if sfProjectModelDetails.ProjectOutput.Foundation != nil { + foundationID = sfProjectModelDetails.ProjectOutput.Foundation.ID + foundationName = sfProjectModelDetails.ProjectOutput.Foundation.Name + log.WithFields(f).Debugf("using parent foundation ID: %s and name: %s", foundationID, foundationName) + } else { + // Project with no parent - must be a standalone - use our ID and Name as the foundation + foundationID = sfProjectModelDetails.ID + foundationName = sfProjectModelDetails.Name + log.WithFields(f).Debugf("no parent - using project as foundation ID: %s and name: %s", foundationID, foundationName) } - // Sort the response based on the Foundation and CLA group name - sort.Slice(responseModel.List, func(i, j int) bool { - switch strings.Compare(responseModel.List[i].FoundationName, responseModel.List[j].FoundationName) { - case -1: - return true - case 1: - return false - } - return responseModel.List[i].ClaGroupName < responseModel.List[j].ClaGroupName - }) + log.WithFields(f).Debug("locating CLA Group mapping...") + projectCLAGroup, lookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(projectOrFoundationSFID) + if lookupErr != nil { + log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) + return "", "", &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} + } - return responseModel, nil + log.WithFields(f).Debugf("loading CLA Group by ID: %s", projectCLAGroup.ClaGroupID) + v1ClaGroupsByProject, claGroupLoadErr := s.v1ProjectService.GetCLAGroupByID(ctx, projectCLAGroup.ClaGroupID) + //v1ClaGroupsByProject, prjerr := s.v1ProjectService.GetClaGroupByProjectSFID(projectOrFoundationSFID, DontLoadDetails) + if claGroupLoadErr != nil { + log.WithFields(f).Warnf("problem loading CLA group by id, error: %+v", claGroupLoadErr) + return "", "", &utils.CLAGroupNotFound{CLAGroupID: projectCLAGroup.ClaGroupID, Err: claGroupLoadErr} + } + + v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1ClaGroupsByProject) + + v1CLAGroupData, v1ClaGroupErr := s.v1ProjectService.GetClaGroupByProjectSFID(ctx, projectOrFoundationSFID, false) + if v1ClaGroupErr != nil { + log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", v1ClaGroupErr) + return "", "", &utils.CLAGroupNotFound{CLAGroupID: projectOrFoundationSFID, Err: v1ClaGroupErr} + } + + _, found := Find(v1ClaGroups.Projects, v1CLAGroupData.ProjectID) + if !found { + v1ClaGroups.Projects = append(v1ClaGroups.Projects, *v1CLAGroupData) + } + return foundationID, foundationName, nil } func (s *service) ListAllFoundationClaGroups(ctx context.Context, foundationID *string) (*models.FoundationMappingList, error) { From b786184c386f81bcafe3e3a7f7ded0ea979544b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Mar 2021 19:34:23 -0700 Subject: [PATCH 0197/1276] Bump y18n from 3.2.1 to 3.2.2 in /cla-frontend-contributor-console/edge (#2832) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/edge/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock index 3bc944542..8c6b32458 100644 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ b/cla-frontend-contributor-console/edge/yarn.lock @@ -3421,9 +3421,9 @@ xtend@^4.0.0: integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== yallist@^2.1.2: version "2.1.2" From 9e7663a35d1c59a332e6b018d26dcfe2424ec91a Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 31 Mar 2021 07:59:16 +0300 Subject: [PATCH 0198/1276] [#LFX 3147] Bug/Approval List Update (#2833) - Resolved dereference issue caused during invalidating signature records Signed-off-by: wanyaland --- cla-backend-go/signatures/repository.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index c5d2b074a..95ae6caa3 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2164,6 +2164,8 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.ApprovalList = params.RemoveDomainApprovalList approvalList.Action = utils.RemoveApprovals approvalList.GerritICLAECLAs = gerritICLAECLAs + approvalList.ClaGroupID = projectID + approvalList.CompanyID = companyID invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) if invalidateErr != nil { msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) @@ -2369,6 +2371,7 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A } // Get ICLAs + log.WithFields(f).Debug("getting icla records... ") iclas, err := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil) if err != nil { log.WithFields(f).Warn("unable to get iclas") @@ -2403,11 +2406,14 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A } // Get ECLAs + log.WithFields(f).Debug("getting ecla records... ") companyProjectParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ CompanyID: approvalList.CompanyID, ProjectID: approvalList.ClaGroupID, } - eclas, err := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, nil, int64(10)) + + criteria := ApprovalCriteria{} + eclas, err := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, &criteria, int64(10)) if err != nil { log.WithFields(f).Warnf("unable to get cclas for company: %s and project: %s ", approvalList.CompanyID, approvalList.ClaGroupID) return err From f69b2d2da51adcdc948112ef2e0c6b1de3e9f8aa Mon Sep 17 00:00:00 2001 From: makkalot Date: Wed, 31 Mar 2021 14:16:30 +0300 Subject: [PATCH 0199/1276] missing foundation sfid in cla group update event was preventing querying via the foundation sfid. Signed-off-by: makkalot --- cla-backend-go/v2/cla_groups/handlers.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index a6e97d184..88b5e4c5e 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -144,9 +144,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // Log the event eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupUpdated, - ProjectID: claGroup.ClaGroupID, - LfUsername: authUser.UserName, + EventType: events.CLAGroupUpdated, + ProjectID: claGroup.ClaGroupID, + LfUsername: authUser.UserName, + ParentProjectSFID: claGroupModel.FoundationSFID, EventData: &events.CLAGroupUpdatedEventData{ ClaGroupName: params.Body.ClaGroupName, ClaGroupDescription: params.Body.ClaGroupDescription, From 96d97f6d87dbdafbc5c69e851ab541cb5934c701 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 08:03:15 -0700 Subject: [PATCH 0200/1276] Bump y18n from 3.2.1 to 3.2.2 in /cla-frontend-project-console/src (#2829) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 610bff4d9..658895412 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -4414,8 +4414,9 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== yallist@^2.1.2: version "2.1.2" From 2affd839bc257d4396e4cf9a405df3729311fedf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 10:22:47 -0700 Subject: [PATCH 0201/1276] Bump y18n from 3.2.1 to 3.2.2 in /cla-frontend-corporate-console/src (#2830) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index cbcf7ced9..00c9980c1 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -4251,8 +4251,9 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== yallist@^2.1.2: version "2.1.2" From b9dd4eabe8f206e39fe861bdfc1354bf24884049 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 10:36:05 -0700 Subject: [PATCH 0202/1276] Bump y18n from 3.2.1 to 3.2.2 in /cla-frontend-contributor-console/src (#2831) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 1cd7df705..377a3f2dc 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -4325,8 +4325,9 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== yallist@^2.1.2: version "2.1.2" From 9977146c9a388a3cdb55acced40bf468bf8340e6 Mon Sep 17 00:00:00 2001 From: makkalot Date: Thu, 1 Apr 2021 14:02:11 +0300 Subject: [PATCH 0203/1276] - set cla group id if not set - set parent project sfid to foundation for projects that don't have parents Signed-off-by: makkalot --- cla-backend-go/events/service.go | 7 ++++++- cla-backend-go/v2/cla_groups/handlers.go | 7 +++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index a4cd66efe..ea948ac17 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -189,7 +189,7 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { args.CLAGroupName = args.ClaGroupModel.ProjectName } else { // Did they set the CLA Group ID? - claGroupID := "" + var claGroupID string if args.CLAGroupID != "" { claGroupID = args.CLAGroupID } else if args.ProjectID != "" && utils.IsUUIDv4(args.ProjectID) { // legacy parameter @@ -205,6 +205,7 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { } args.ClaGroupModel = claGroupModel args.CLAGroupName = claGroupModel.ProjectName + args.CLAGroupID = claGroupID } else if args.ProjectSFID != "" { projectCLAGroupModel, projectCLAGroupErr := s.combinedRepo.GetClaGroupIDForProject(args.ProjectSFID) if projectCLAGroupErr != nil || projectCLAGroupModel == nil { @@ -269,6 +270,10 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { project.Parent, parentProjectID, parentProjectName) args.ParentProjectSFID = parentProjectID args.ParentProjectName = parentProjectName + } else if project.Foundation != nil { + // if there's no parent set the foundation as parent project + args.ParentProjectSFID = project.Foundation.ID + args.ParentProjectName = project.Foundation.Name } } diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 88b5e4c5e..a6e97d184 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -144,10 +144,9 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // Log the event eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupUpdated, - ProjectID: claGroup.ClaGroupID, - LfUsername: authUser.UserName, - ParentProjectSFID: claGroupModel.FoundationSFID, + EventType: events.CLAGroupUpdated, + ProjectID: claGroup.ClaGroupID, + LfUsername: authUser.UserName, EventData: &events.CLAGroupUpdatedEventData{ ClaGroupName: params.Body.ClaGroupName, ClaGroupDescription: params.Body.ClaGroupDescription, From 8b7fdfd13786f0f53e9c9265f04990eaedfa3a5e Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 1 Apr 2021 17:46:37 +0300 Subject: [PATCH 0204/1276] [#2792] Feature/Approval Update - invalidate ICLA (#2841) - added icla signature invalidation upon email removal from approval list Signed-off-by: wanyaland --- cla-backend-go/go.sum | 1 + cla-backend-go/signatures/repository.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 9f4b4ebe8..a29cdd858 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -98,6 +98,7 @@ github.com/communitybridge/easycla v1.0.135 h1:Dvn8jX+7BAnpmA+jvdK0n5ajWP8SoH5vv github.com/communitybridge/easycla v1.0.145 h1:ikhBSsOeEL2u3/EoyDsufh/j3HkjfFTiXAk1d61GoS8= github.com/communitybridge/easycla v2.0.10+incompatible h1:6eRJ5fxrMxRZHBkg8piYo+zHTcSowMrP85nZXzp5mpA= github.com/communitybridge/easycla v2.0.16+incompatible h1:I0hEApDh4IvlwRPyHV1LOsSYlSPbqBsGszjSTHwkdak= +github.com/communitybridge/easycla v2.0.19+incompatible h1:HLaNt3jGDXPh3Au+rW/HKbJNkQf3daboVIrP9G6WYQ4= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 95ae6caa3..69e6c882a 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2113,6 +2113,27 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model repo.eventsService.LogEventWithContext(ctx, eventArgs) + // invalidate icla + log.WithFields(f).Debugf("invalidating icla for user: %s...", email) + claUser, userErr := repo.usersRepo.GetUserByEmail(email) + if userErr != nil { + log.WithFields(f).Debugf("error getting user by email: %s ", email) + } + + if claUser != nil { + icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) + if iclaErr != nil { + log.WithFields(f).Debugf("unable to get icla signature for user: %s ", email) + } + if icla != nil { + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal ", utils.GetBestUsername(claManager), email) + err := repo.InvalidateProjectRecord(ctx, icla.SignatureID, note) + if err != nil { + log.WithFields(f).Warnf("unable to invalidate record for user:%s ", email) + } + } + } + //update gerrit permissions gerritUser, err := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) if err != nil || gerritUser == nil { From da837871fedaf219d8aedbee8373955aec1f18b7 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 1 Apr 2021 09:18:25 -0700 Subject: [PATCH 0205/1276] Updated Logging + Added New Python Query (#2839) - Updated logger functionName output - Added python get_repository_models_by_repository_sfdc_id helper query Signed-off-by: David Deal --- .../v2/github_organizations/service.go | 6 +++--- cla-backend/cla/models/dynamo_models.py | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index c45350976..c4b523556 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -73,7 +73,7 @@ const ( func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGithubOrganizations, error) { f := logrus.Fields{ - "functionName": "GetGitHubOrganizations", + "functionName": "v2.github_organizations.service.GetGitHubOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -251,7 +251,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ - "functionName": "AddGitHubOrganization", + "functionName": "v2.github_organizations.service.AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "autoEnabled": utils.BoolValue(input.AutoEnabled), @@ -300,7 +300,7 @@ func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID strin func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { f := logrus.Fields{ - "functionName": "DeleteGitHubOrganization", + "functionName": "v2.github_organizations.service.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "githubOrgName": githubOrgName, diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index e89bf2e26..4ef900489 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -1990,14 +1990,14 @@ class Meta: repository_url = UnicodeAttribute() repository_organization_name = UnicodeAttribute() repository_external_id = UnicodeAttribute(null=True) - repository_project_index = ProjectRepositoryIndex() - project_sfid_repository_index = ProjectSFIDRepositoryIndex() repository_sfdc_id = UnicodeAttribute(null=True) project_sfid = UnicodeAttribute(null=True) - repository_external_index = ExternalRepositoryIndex() - repository_sfdc_index = SFDCRepositoryIndex() enabled = BooleanAttribute(default=False) note = UnicodeAttribute(null=True) + repository_external_index = ExternalRepositoryIndex() + repository_project_index = ProjectRepositoryIndex() + project_sfid_repository_index = ProjectSFIDRepositoryIndex() + repository_sfdc_index = SFDCRepositoryIndex() class Repository(model_interfaces.Repository): @@ -2062,6 +2062,15 @@ def get_repository_by_project_sfid(self, project_sfid) -> List[dict]: repositories.append(repository.to_dict()) return repositories + def get_repository_models_by_repository_sfdc_id(self, project_sfid) -> List[Repository]: + repository_generator = self.model.repository_sfdc_index.query(project_sfid) + repositories = [] + for repository_model in repository_generator: + repository = Repository() + repository.model = repository_model + repositories.append(repository) + return repositories + def delete(self): self.model.delete() From 11a432c436b26e30703abb914d3a7a6ddc8285b5 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Thu, 1 Apr 2021 19:38:23 +0300 Subject: [PATCH 0206/1276] adding pagination to icla signatures list (#2844) Signed-off-by: makkalot --- cla-backend-go/signatures/repository.go | 233 ++++++++++++------ cla-backend-go/signatures/service.go | 6 +- cla-backend-go/swagger/cla.v2.yaml | 2 + .../swagger/common/icla-signatures.yaml | 12 + cla-backend-go/v2/signatures/handlers.go | 2 +- cla-backend-go/v2/signatures/service.go | 8 +- 6 files changed, 180 insertions(+), 83 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 69e6c882a..040cd8564 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -10,6 +10,7 @@ import ( "sort" "strings" "sync" + "time" "github.com/LF-Engineering/lfx-kit/auth" "github.com/sirupsen/logrus" @@ -50,7 +51,9 @@ const ( SignatureReferenceIndex = "reference-signature-index" SignatureReferenceSearchIndex = "reference-signature-search-index" - HugePageSize = 10000 + HugePageSize = 10000 + DefaultPageSize = 10 + BigPageSize = 100 ) // SignatureRepository interface defines the functions for the github whitelist service @@ -84,10 +87,15 @@ type SignatureRepository interface { AddUsersDetails(ctx context.Context, signatureID string, userID string) error AddSignedOn(ctx context.Context, signatureID string) error - GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) + GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) } +type iclaSignatureWithDetails struct { + IclaSignature *models.IclaSignature + SignatureReferenceID string +} + // repository data model type repository struct { stage string @@ -2393,7 +2401,7 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A // Get ICLAs log.WithFields(f).Debug("getting icla records... ") - iclas, err := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil) + iclas, err := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil, 0, "") if err != nil { log.WithFields(f).Warn("unable to get iclas") } @@ -2730,7 +2738,7 @@ func (repo repository) AddSignedOn(ctx context.Context, signatureID string) erro return nil } -func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) { +func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { f := logrus.Fields{ "functionName": "GetClaGroupICLASignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -2738,12 +2746,7 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID "searchTerm": utils.StringValue(searchTerm), } - //sortKeyPrefix := fmt.Sprintf("%s#%v#%v", utils.ClaTypeICLA, true, true) - // This is the key we want to match - //condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). - // And(expression.Key("sigtype_signed_approved_id").BeginsWith(sortKeyPrefix)) condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)) - filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). @@ -2770,93 +2773,168 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID FilterExpression: expr.Filter(), ProjectionExpression: expr.Projection(), TableName: aws.String(repo.signatureTableName), - //IndexName: aws.String(SignatureProjectIDSigTypeSignedApprovedIDIndex), - IndexName: aws.String(SignatureProjectIDIndex), - Limit: aws.Int64(HugePageSize), + IndexName: aws.String(SignatureProjectIDIndex), + } + + if pageSize == 0 { + pageSize = DefaultPageSize + } + + if pageSize > BigPageSize { + pageSize = BigPageSize } + + queryInput.Limit = &pageSize + if searchTerm != nil { searchTerm = aws.String(strings.ToLower(*searchTerm)) } - type IclaSignatureWithDetails struct { - IclaSignature *models.IclaSignature - SignatureReferenceID string + // If we have the next key, set the exclusive start key value + if nextKey != "" { + log.WithFields(f).Debugf("Received a nextKey, value: %s", nextKey) + // The primary key of the first item that this operation will evaluate. + // and the query key (if not the same) + queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "signature_id": { + S: aws.String(nextKey), + }, + "signature_project_id": { + S: aws.String(claGroupID), + }, + } } - var intermediateResponse []*IclaSignatureWithDetails - for { + var intermediateResponse []*iclaSignatureWithDetails + var lastEvaluatedKey string + // Loop until we have all the records + for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call - results, queryErr := repo.dynamoDBClient.Query(queryInput) - if queryErr != nil { - log.WithFields(f).Warnf("error retrieving icla signatures for project: %s, error: %v", claGroupID, queryErr) - return nil, queryErr + log.WithFields(f).Debugf("Running list icla signatures query using queryInput: %+v", queryInput) + results, errQuery := repo.dynamoDBClient.Query(queryInput) + if errQuery != nil { + log.WithFields(f).Warnf("error retrieving icla signatures for project: %s , error: %v", + claGroupID, errQuery) + return nil, errQuery } var dbSignatures []ItemSignature - err := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) - if err != nil { + unmarshallError := dynamodbattribute.UnmarshalListOfMaps(results.Items, &dbSignatures) + if unmarshallError != nil { log.WithFields(f).Warnf("error unmarshalling icla signatures from database for cla group: %s, error: %v", - claGroupID, err) - return nil, err + claGroupID, unmarshallError) + return nil, unmarshallError } - for _, sig := range dbSignatures { - if searchTerm != nil { - if !strings.Contains(sig.SignatureReferenceNameLower, *searchTerm) { - continue - } + intermediateResponse = append(intermediateResponse, repo.getIntermediateICLAResponse(f, dbSignatures, searchTerm)...) + + log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey["signature_id"]) + if results.LastEvaluatedKey["signature_id"] != nil { + lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S + queryInput.ExclusiveStartKey = results.LastEvaluatedKey + } else { + lastEvaluatedKey = "" + } + + if int64(len(intermediateResponse)) >= pageSize { + break + } + } + + // How many total records do we have - may not be up-to-date as this value is updated only periodically + describeTableInput := &dynamodb.DescribeTableInput{ + TableName: &repo.signatureTableName, + } + describeTableResult, err := repo.dynamoDBClient.DescribeTable(describeTableInput) + if err != nil { + log.WithFields(f).Warnf("error retrieving total record count for project: %s, error: %v", claGroupID, err) + return nil, err + } + // Meta-data for the response + totalCount := *describeTableResult.Table.ItemCount + + if int64(len(intermediateResponse)) > pageSize { + intermediateResponse = intermediateResponse[0:pageSize] + lastEvaluatedKey = intermediateResponse[pageSize-1].IclaSignature.SignatureID + } + + // Append all the responses to our list + out := &models.IclaSignatures{ + LastKeyScanned: lastEvaluatedKey, + PageSize: pageSize, + ResultCount: int64(len(intermediateResponse)), + TotalCount: totalCount, + } + + iclaSignatures, err := repo.addAdditionalICLAMetaData(f, intermediateResponse) + if err != nil { + return nil, err + } + + out.List = iclaSignatures + return out, nil +} + +func (repo repository) getIntermediateICLAResponse(f logrus.Fields, dbSignatures []ItemSignature, searchTerm *string) []*iclaSignatureWithDetails { + var intermediateResponse []*iclaSignatureWithDetails + + for _, sig := range dbSignatures { + if searchTerm != nil { + if !strings.Contains(sig.SignatureReferenceNameLower, *searchTerm) { + continue } + } - // Set the signed date/time - var sigSignedTime string - // Use the user docusign date signed value if it is present - older signatures do not have this - if sig.UserDocusignDateSigned != "" { - // Put the date into a standard format - t, err := utils.ParseDateTime(sig.UserDocusignDateSigned) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to parse signature docusign date signed time") - } else { - sigSignedTime = utils.TimeToString(t) - } + // Set the signed date/time + var sigSignedTime string + // Use the user docusign date signed value if it is present - older signatures do not have this + if sig.UserDocusignDateSigned != "" { + // Put the date into a standard format + t, err := utils.ParseDateTime(sig.UserDocusignDateSigned) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature docusign date signed time") } else { - // Put the date into a standard format - t, err := utils.ParseDateTime(sig.DateCreated) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to parse signature date created time") - } else { - sigSignedTime = utils.TimeToString(t) - } + sigSignedTime = utils.TimeToString(t) + } + } else { + // Put the date into a standard format + t, err := utils.ParseDateTime(sig.DateCreated) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to parse signature date created time") + } else { + sigSignedTime = utils.TimeToString(t) } - - intermediateResponse = append(intermediateResponse, &IclaSignatureWithDetails{ - IclaSignature: &models.IclaSignature{ - GithubUsername: sig.UserGithubUsername, - LfUsername: sig.UserLFUsername, - SignatureID: sig.SignatureID, - UserEmail: sig.UserEmail, - UserName: sig.UserName, - SignedOn: sigSignedTime, - UserDocusignName: sig.UserDocusignName, - UserDocusignDateSigned: sigSignedTime, - SignatureModified: sig.DateModified, - }, - SignatureReferenceID: sig.SignatureReferenceID, - }) } - if len(results.LastEvaluatedKey) == 0 { - break - } - queryInput.ExclusiveStartKey = results.LastEvaluatedKey - log.WithFields(f).Debug("querying next page") + intermediateResponse = append(intermediateResponse, &iclaSignatureWithDetails{ + IclaSignature: &models.IclaSignature{ + GithubUsername: sig.UserGithubUsername, + LfUsername: sig.UserLFUsername, + SignatureID: sig.SignatureID, + UserEmail: sig.UserEmail, + UserName: sig.UserName, + SignedOn: sigSignedTime, + UserDocusignName: sig.UserDocusignName, + UserDocusignDateSigned: sigSignedTime, + SignatureModified: sig.DateModified, + }, + SignatureReferenceID: sig.SignatureReferenceID, + }) } + return intermediateResponse +} + +func (repo repository) addAdditionalICLAMetaData(f logrus.Fields, intermediateResponse []*iclaSignatureWithDetails) ([]*models.IclaSignature, error) { log.WithFields(f).Debugf("Adding additional meta-data for %d records...", len(intermediateResponse)) // For some older ICLA signatures, we are missing the user's info, but we have their internal ID - let's look up those values before returning responseChannel := make(chan *models.IclaSignature) - for _, iclaSignatureWithDetails := range intermediateResponse { - go func(iclaSignatureWithDetails *IclaSignatureWithDetails) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + for _, iclaDetails := range intermediateResponse { + go func(iclaSignatureWithDetails *iclaSignatureWithDetails) { userModel, userLookupErr := repo.usersRepo.GetUser(iclaSignatureWithDetails.SignatureReferenceID) if userLookupErr != nil || userModel == nil { log.WithFields(f).WithError(userLookupErr).Warnf("unable to lookup user with id: %s", iclaSignatureWithDetails.SignatureReferenceID) @@ -2883,16 +2961,21 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID } responseChannel <- iclaSignatureWithDetails.IclaSignature - }(iclaSignatureWithDetails) + }(iclaDetails) } - // Append all the responses to our list - out := &models.IclaSignatures{List: make([]*models.IclaSignature, 0)} + var finalResults []*models.IclaSignature for i := 0; i < len(intermediateResponse); i++ { - out.List = append(out.List, <-responseChannel) + select { + case result := <-responseChannel: + finalResults = append(finalResults, result) + case <-ctx.Done(): + log.WithError(ctx.Err()).Warnf("timeout during adding additional meta to icla signatures") + return nil, ctx.Err() + } } - return out, nil + return finalResults, nil } func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) { diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 0a384ef4f..86455cf77 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -53,7 +53,7 @@ type SignatureService interface { AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) RemoveCLAManager(ctx context.Context, ignatureID, claManagerID string) (*models.Signature, error) - GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) + GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) GetClaGroupCCLASignatures(ctx context.Context, claGroupID string) (*models.Signatures, error) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) } @@ -814,8 +814,8 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models } } -func (s service) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) { - return s.repo.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm) +func (s service) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { + return s.repo.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm, pageSize, nextKey) } func (s service) GetClaGroupCCLASignatures(ctx context.Context, claGroupID string) (*models.Signatures, error) { diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index fb5d6291e..8b8e30fe4 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1617,6 +1617,8 @@ paths: - $ref: "#/parameters/path-claGroupID" - $ref: '#/parameters/searchTerm' - $ref: '#/parameters/sortOrder' + - $ref: '#/parameters/pageSize' + - $ref: '#/parameters/nextKey' responses: '200': description: 'Success' diff --git a/cla-backend-go/swagger/common/icla-signatures.yaml b/cla-backend-go/swagger/common/icla-signatures.yaml index e337d4a4e..1d614b916 100644 --- a/cla-backend-go/swagger/common/icla-signatures.yaml +++ b/cla-backend-go/swagger/common/icla-signatures.yaml @@ -3,6 +3,18 @@ type: object properties: + lastKeyScanned: + type: string + pageSize: + type: integer + resultCount: + type: integer + format: int64 + x-omitempty: false + totalCount: + type: integer + format: int64 + x-omitempty: false list: type: array items: diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 1181cda1c..b44d18596 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -840,7 +840,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("searching for ICLA signatures...") - results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm) + results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm, *params.PageSize, *params.NextKey) if err != nil { msg := fmt.Sprintf("problem loading ICLA signatures by CLA Group ID search term: %s", aws.StringValue(params.SearchTerm)) log.WithFields(f).WithError(err).Warn(msg) diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 3d04f4075..a60dd4039 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -56,7 +56,7 @@ type Service interface { GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) - GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) + GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companyID string) ([]byte, error) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companySFID string, searchTerm *string) (*models.CorporateContributorList, error) GetSignedDocument(ctx context.Context, signatureID string) (*models.SignedDocument, error) @@ -132,7 +132,7 @@ func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGro func (s service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { var b bytes.Buffer - result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, nil) + result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, nil, 0, "") if err != nil { return nil, err } @@ -167,7 +167,7 @@ func (s service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID str return b.Bytes(), nil } -func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string) (*models.IclaSignatures, error) { +func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { f := logrus.Fields{ "functionName": "v2.signatures.service.GetProjectIclaSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -176,7 +176,7 @@ func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string } var out models.IclaSignatures - result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm) + result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm, pageSize, nextKey) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load ICLA signatures using the specified search parameters") return nil, err From 2b97182ac4bd95126269c62b93ef17344e748cbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Apr 2021 10:02:59 -0700 Subject: [PATCH 0207/1276] Bump y18n from 4.0.0 to 4.0.1 in /cla-backend (#2835) Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 1ba44bbbb..eb2ab3c37 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -6382,9 +6382,9 @@ xtend@^4.0.0: integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + version "4.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" + integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== yallist@^4.0.0: version "4.0.0" From 68f88622715ac534ad6bcef8d107c0c9ccffa0c6 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 1 Apr 2021 20:45:29 +0300 Subject: [PATCH 0208/1276] [#2838] Bug/Get Project GH Orgs (#2845) - Get GH orgs for Parent/Project or both use cases Signed-off-by: wanyaland --- .../github_organizations/service.go | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 3aa8fb11c..9f1b38d47 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -77,17 +77,21 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) "projectSFID": projectSFID, } - gitHubOrgModels, err := s.repo.GetGithubOrganizations(ctx, projectSFID) + // track githubOrgs based on parent/child anchor + var gitHubOrgModels = models.GithubOrganizations{} + var githubOrgs = make([]*models.GithubOrganization, 0) + + projectGithubModels, err := s.repo.GetGithubOrganizations(ctx, projectSFID) if err != nil { log.WithFields(f).Warnf("problem fetching github organizations by projectSFID, error: %+v", err) return nil, err } - if len(gitHubOrgModels.List) >= 0 { - return gitHubOrgModels, err + if len(projectGithubModels.List) >= 0 { + githubOrgs = append(githubOrgs, projectGithubModels.List...) } - log.WithFields(f).Debug("unable to find github organizations by projectSFID - searching by parent...") + log.WithFields(f).Debug("factoring github orgs for parent...") // Lookup the parent parentProjectSFID, projErr := v2ProjectService.GetClient().GetParentProject(projectSFID) if projErr != nil { @@ -97,12 +101,25 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) if parentProjectSFID != projectSFID { log.WithFields(f).Debugf("searching github organization by parent SFID: %s", parentProjectSFID) - return s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) + parentGithubModels, parentErr := s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) + if parentErr != nil { + log.WithFields(f).Warnf("problem fetching github organizations by paarent projectSFID: %s , error: %+v", parentProjectSFID, err) + return nil, parentErr + } + + if len(parentGithubModels.List) >= 0 { + githubOrgs = append(githubOrgs, parentGithubModels.List...) + } } log.WithFields(f).Debugf("no parent or parent is %s or %s - search criteria exhausted", utils.TheLinuxFoundation, utils.TheLinuxFoundation) - return gitHubOrgModels, err + + gitHubOrgModels.List = githubOrgs + // Remove potential duplicates + s.removeDuplicateGHOrgs(gitHubOrgModels.List) + + return &gitHubOrgModels, err } func (s service) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { @@ -166,3 +183,21 @@ func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID strin return s.repo.DeleteGithubOrganization(ctx, projectSFID, githubOrgName) } + +// filter ghOrgs duplicates +func (s service) removeDuplicateGHOrgs(input []*models.GithubOrganization) []*models.GithubOrganization { + if input == nil { + return nil + } + keys := make(map[string]bool) + + output := []*models.GithubOrganization{} + for _, ghOrg := range input { + if _, value := keys[ghOrg.OrganizationName]; !value { + keys[ghOrg.OrganizationName] = true + output = append(output, ghOrg) + } + } + + return output +} From 726960534fa28bc153fe3dbf2bc2d243b2f56d22 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Thu, 1 Apr 2021 21:27:07 +0300 Subject: [PATCH 0209/1276] some defensive code on pagination parameters (#2846) Signed-off-by: makkalot --- cla-backend-go/signatures/repository.go | 14 -------------- cla-backend-go/swagger/common/icla-signatures.yaml | 4 ---- cla-backend-go/v2/signatures/handlers.go | 14 +++++++++++++- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 040cd8564..0f8db8bdd 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2810,7 +2810,6 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID // Loop until we have all the records for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call - log.WithFields(f).Debugf("Running list icla signatures query using queryInput: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { log.WithFields(f).Warnf("error retrieving icla signatures for project: %s , error: %v", @@ -2842,18 +2841,6 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID } } - // How many total records do we have - may not be up-to-date as this value is updated only periodically - describeTableInput := &dynamodb.DescribeTableInput{ - TableName: &repo.signatureTableName, - } - describeTableResult, err := repo.dynamoDBClient.DescribeTable(describeTableInput) - if err != nil { - log.WithFields(f).Warnf("error retrieving total record count for project: %s, error: %v", claGroupID, err) - return nil, err - } - // Meta-data for the response - totalCount := *describeTableResult.Table.ItemCount - if int64(len(intermediateResponse)) > pageSize { intermediateResponse = intermediateResponse[0:pageSize] lastEvaluatedKey = intermediateResponse[pageSize-1].IclaSignature.SignatureID @@ -2864,7 +2851,6 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID LastKeyScanned: lastEvaluatedKey, PageSize: pageSize, ResultCount: int64(len(intermediateResponse)), - TotalCount: totalCount, } iclaSignatures, err := repo.addAdditionalICLAMetaData(f, intermediateResponse) diff --git a/cla-backend-go/swagger/common/icla-signatures.yaml b/cla-backend-go/swagger/common/icla-signatures.yaml index 1d614b916..b967732ac 100644 --- a/cla-backend-go/swagger/common/icla-signatures.yaml +++ b/cla-backend-go/swagger/common/icla-signatures.yaml @@ -11,10 +11,6 @@ properties: type: integer format: int64 x-omitempty: false - totalCount: - type: integer - format: int64 - x-omitempty: false list: type: array items: diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index b44d18596..2ee9830b7 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -840,7 +840,19 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("searching for ICLA signatures...") - results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm, *params.PageSize, *params.NextKey) + + var pageSize int64 + var nextKey string + + if params.PageSize != nil { + pageSize = *params.PageSize + } + + if params.NextKey != nil { + nextKey = *params.NextKey + } + + results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm, pageSize, nextKey) if err != nil { msg := fmt.Sprintf("problem loading ICLA signatures by CLA Group ID search term: %s", aws.StringValue(params.SearchTerm)) log.WithFields(f).WithError(err).Warn(msg) From 54ab0cc802ce490b4940d63f1c13dca2cc6874b8 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Sat, 3 Apr 2021 01:40:53 +0300 Subject: [PATCH 0210/1276] [#2792] Bug/Domain Approval Removal (#2848) - Invalidate icla/ecla resolved by searching email against domain Signed-off-by: nickmango --- cla-backend-go/signatures/repository.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 0f8db8bdd..149cf677b 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2518,7 +2518,9 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur if approvalList.Criteria == utils.EmailDomainCriteria { // Handle Domains - if utils.StringInSlice(getBestEmail(user), approvalList.DomainApprovals) { + email := getBestEmail(user) + domain := strings.Split(email, "@")[1] + if utils.StringInSlice(domain, approvalList.DomainApprovals) { if !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) && !utils.StringInSlice(getBestEmail(user), approvalList.EmailApprovals) { //Invalidate record note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), utils.EmailDomainCriteria) From 865f7e89389f6fa986f305642a776b064fdf679c Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 5 Apr 2021 18:06:49 +0300 Subject: [PATCH 0211/1276] [#2838] Bug/Project Github Orgs (#2849) - Leveraged v1 getorgs service that caters for duplicates and returns right gh-orgs Signed-off-by: Harold Wanyama Co-authored-by: Harold Wanyama --- cla-backend-go/cmd/server.go | 2 +- .../v2/github_organizations/service.go | 25 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index a7d5a4bf4..08761f72c 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -289,7 +289,7 @@ func server(localMode bool) http.Handler { authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) - v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) + v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, eventsService, autoEnableService) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index c4b523556..730a919bb 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -48,15 +48,17 @@ type Service interface { type service struct { repo v1GithubOrg.Repository ghRepository v1Repositories.Repository + ghService v1GithubOrg.Service projectsCLAGroupService projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo v1GithubOrg.Repository, ghRepository v1Repositories.Repository, projectsCLAGroupService projects_cla_groups.Repository) Service { +func NewService(repo v1GithubOrg.Repository, ghRepository v1Repositories.Repository, projectsCLAGroupService projects_cla_groups.Repository, ghService v1GithubOrg.Service) Service { return service{ repo: repo, ghRepository: ghRepository, projectsCLAGroupService: projectsCLAGroupService, + ghService: ghService, } } @@ -78,6 +80,14 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) "projectSFID": projectSFID, } + orgs, err := s.ghService.GetGithubOrganizations(ctx, projectSFID) + // log.WithFields(f).Debug("loading github organization details by projectSFID...") + //orgs, err := s.repo.GetGithubOrganizations(ctx, projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading github organizations from the project service") + return nil, err + } + psc := v2ProjectService.GetClient() log.WithFields(f).Debug("loading project details from the project service...") projectServiceRecord, err := psc.GetProject(projectSFID) @@ -89,9 +99,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) log.Debugf("project record: %+v ", projectServiceRecord) var parentProjectSFID string - if (projectServiceRecord.Foundation != nil && - (projectServiceRecord.Foundation.Name == utils.TheLinuxFoundation || projectServiceRecord.Foundation.Name == utils.LFProjectsLLC)) || - projectServiceRecord.Parent == "" { + if utils.IsProjectHasRootParent(projectServiceRecord) { parentProjectSFID = projectSFID } else { parentProjectSFID = projectServiceRecord.Parent @@ -99,15 +107,6 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") - log.WithFields(f).Debug("loading github organization details by parentProjectSFID...") - orgs, err := s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) - // log.WithFields(f).Debug("loading github organization details by projectSFID...") - //orgs, err := s.repo.GetGithubOrganizations(ctx, projectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warn("problem loading github organizations from the project service") - return nil, err - } - out := &models.ProjectGithubOrganizations{ List: make([]*models.ProjectGithubOrganization, 0), } From 4119b324f9af79db46b55d6ed2683737e9e50fd1 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 5 Apr 2021 19:53:13 +0300 Subject: [PATCH 0212/1276] adding more information to cla_group.updated event log summary and (#2851) details method Signed-off-by: makkalot --- cla-backend-go/events/event_data.go | 49 ++++++++-- cla-backend-go/events/event_data_test.go | 110 +++++++++++++++++++++++ cla-backend-go/project/handlers.go | 15 +++- cla-backend-go/v2/cla_groups/handlers.go | 15 +++- cla-backend-go/v2/project/handlers.go | 18 +++- 5 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 cla-backend-go/events/event_data_test.go diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 75e7ab1f7..7c9858d40 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -330,8 +330,10 @@ type CLAGroupCreatedEventData struct{} // CLAGroupUpdatedEventData . . . type CLAGroupUpdatedEventData struct { - ClaGroupName string - ClaGroupDescription string + NewClaGroupName string + NewClaGroupDescription string + OldClaGroupName string + OldClaGroupDescription string } // CLAGroupDeletedEventData . . . @@ -676,8 +678,23 @@ func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (s // GetEventDetailsString . . . func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Group ID: %s was updated by: %s with Name: %s, Description: %s.", - args.ProjectID, args.UserName, ed.ClaGroupName, ed.ClaGroupDescription) + var nameUpdated bool + + data := fmt.Sprintf("CLA Group ID: %s was updated by: %s", args.ProjectID, args.UserName) + if ed.NewClaGroupName != ""{ + data = fmt.Sprintf("%s with Name from : %s to : %s", data, ed.OldClaGroupName, ed.NewClaGroupName) + nameUpdated = true + } + + if ed.NewClaGroupDescription != ""{ + if nameUpdated{ + data = data + "," + }else{ + data = data + " with" + } + data = fmt.Sprintf("%s Description from : %s to : %s", data, ed.OldClaGroupDescription, ed.NewClaGroupDescription) + } + data = data + "." return data, true } @@ -1457,7 +1474,29 @@ func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString . . . func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was updated by the user %s.", args.ProjectName, args.UserName) + var nameUpdated, descriptionUpdated bool + + message := "The CLA Group" + if ed.NewClaGroupName != ""{ + message = message + " name was updated to : " + ed.NewClaGroupName + nameUpdated = true + } + + if ed.NewClaGroupDescription != ""{ + descriptionUpdated = true + if nameUpdated{ + message = message + " and the description was updated to : " + ed.NewClaGroupDescription + }else{ + message = message + " description was updated to : " + ed.NewClaGroupDescription + } + } + + //shouldn't happen + if !nameUpdated && !descriptionUpdated{ + message = message + " was updated" + } + + data := fmt.Sprintf("%s by the user %s.", message, args.UserName) return data, true } diff --git a/cla-backend-go/events/event_data_test.go b/cla-backend-go/events/event_data_test.go new file mode 100644 index 000000000..76c525572 --- /dev/null +++ b/cla-backend-go/events/event_data_test.go @@ -0,0 +1,110 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package events + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + testUser = "john" +) + +func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { + + testCases := []struct { + name string + eventData *CLAGroupUpdatedEventData + summaryStr string + }{ + { + name: "empty", + eventData: &CLAGroupUpdatedEventData{}, + summaryStr: "The CLA Group was updated by the user john.", + }, + { + name: "only name updated", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupName: "updatedNameValue", + }, + summaryStr: "The CLA Group name was updated to : updatedNameValue by the user john.", + }, + { + name: "only description updated", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupDescription: "updatedDescriptionValue", + }, + summaryStr: "The CLA Group description was updated to : updatedDescriptionValue by the user john.", + }, + { + name: "name and description updated", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupName: "updatedNameValue", + NewClaGroupDescription: "updatedDescriptionValue", + }, + summaryStr: "The CLA Group name was updated to : updatedNameValue and the description was updated to : updatedDescriptionValue by the user john.", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + summary, _ := tc.eventData.GetEventSummaryString(&LogEventArgs{UserName: testUser}) + assert.Equal(tt, tc.summaryStr, summary) + }) + } +} + +func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { + projectID := "projectIDValue" + + testCases := []struct { + name string + eventData *CLAGroupUpdatedEventData + detailStr string + }{ + { + name: "empty", + eventData: &CLAGroupUpdatedEventData{}, + detailStr: "CLA Group ID: projectIDValue was updated by: john.", + }, + { + name: "only name updated", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupName: "updatedNameValue", + OldClaGroupName: "oldNameValue", + }, + detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue.", + }, + { + name: "only description updated", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupDescription: "updatedDescriptionValue", + OldClaGroupDescription: "oldDescriptionValue", + }, + detailStr: "CLA Group ID: projectIDValue was updated by: john with Description from : oldDescriptionValue to : updatedDescriptionValue.", + }, + { + name: "name and description updated", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupName: "updatedNameValue", + OldClaGroupName: "oldNameValue", + NewClaGroupDescription: "updatedDescriptionValue", + OldClaGroupDescription: "oldDescriptionValue", + }, + detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue, Description from : oldDescriptionValue to : updatedDescriptionValue.", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + summary, _ := tc.eventData.GetEventDetailsString(&LogEventArgs{ + UserName: testUser, + ProjectID: projectID, + }) + assert.Equal(tt, tc.detailStr, summary) + }) + } +} diff --git a/cla-backend-go/project/handlers.go b/cla-backend-go/project/handlers.go index 35f9acf67..32cb774ab 100644 --- a/cla-backend-go/project/handlers.go +++ b/cla-backend-go/project/handlers.go @@ -282,7 +282,7 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser reqID := utils.GetRequestID(projectParams.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - exitingModel, getErr := service.GetCLAGroupByID(ctx, projectParams.Body.ProjectID) + existingModel, getErr := service.GetCLAGroupByID(ctx, projectParams.Body.ProjectID) if getErr != nil { msg := fmt.Sprintf("Error querying the project by ID, error: %+v", getErr) log.Warnf("Update Project Failed - %s", msg) @@ -293,7 +293,7 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser } // If the project with the same name exists... - if exitingModel == nil { + if existingModel == nil { msg := fmt.Sprintf("unable to locate project with ID: %s", projectParams.Body.ProjectID) log.Warn(msg) return project.NewUpdateProjectNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ @@ -302,6 +302,10 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser }) } + var oldCLAGroupName, oldCLAGroupDescription string + oldCLAGroupName = existingModel.ProjectName + oldCLAGroupDescription = existingModel.ProjectDescription + claGroupModel, err := service.UpdateCLAGroup(ctx, &projectParams.Body) if err != nil { if err == ErrProjectDoesNotExist { @@ -318,8 +322,11 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser UserID: claUser.UserID, LfUsername: claUser.LFUsername, EventData: &events.CLAGroupUpdatedEventData{ - ClaGroupName: projectParams.Body.ProjectName, - ClaGroupDescription: projectParams.Body.ProjectDescription, + NewClaGroupName: projectParams.Body.ProjectName, + NewClaGroupDescription: projectParams.Body.ProjectDescription, + + OldClaGroupName: oldCLAGroupName, + OldClaGroupDescription: oldCLAGroupDescription, }, }) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index a6e97d184..3e2475194 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -135,6 +135,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("unable to update the CLA Group Name or Description - values are the same for CLA Group ID: %s", params.ClaGroupID))) } + var oldCLAGroupName, oldCLAGroupDescription string + oldCLAGroupName = claGroupModel.ProjectName + oldCLAGroupDescription = claGroupModel.ProjectDescription + claGroup, err := service.UpdateCLAGroup(ctx, authUser, claGroupModel, params.Body, utils.StringValue(params.XUSERNAME)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to update the CLA Group Name and/or Description - update failed") @@ -148,8 +152,11 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ProjectID: claGroup.ClaGroupID, LfUsername: authUser.UserName, EventData: &events.CLAGroupUpdatedEventData{ - ClaGroupName: params.Body.ClaGroupName, - ClaGroupDescription: params.Body.ClaGroupDescription, + NewClaGroupName: params.Body.ClaGroupName, + NewClaGroupDescription: params.Body.ClaGroupDescription, + + OldClaGroupName: oldCLAGroupName, + OldClaGroupDescription: oldCLAGroupDescription, }, }) @@ -358,8 +365,8 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ClaGroupModel: cg, LfUsername: authUser.UserName, EventData: &events.CLAGroupUpdatedEventData{ - ClaGroupName: cg.ProjectName, - ClaGroupDescription: cg.ProjectDescription, + OldClaGroupName: cg.ProjectName, + OldClaGroupDescription: cg.ProjectDescription, }, }) diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 6fa24ff72..4bc2ba68d 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -239,14 +239,24 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewUpdateProjectBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } + eventData := &events.CLAGroupUpdatedEventData{ + OldClaGroupName: claGroupModel.ProjectName, + OldClaGroupDescription: claGroupModel.ProjectDescription, + } + + if in.ProjectName != "" { + eventData.NewClaGroupName = in.ProjectName + } + + if in.ProjectDescription != "" { + eventData.NewClaGroupDescription = in.ProjectDescription + } + eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLAGroupUpdated, ClaGroupModel: claGroupModel, LfUsername: user.UserName, - EventData: &events.CLAGroupUpdatedEventData{ - ClaGroupName: claGroupModel.ProjectName, - ClaGroupDescription: claGroupModel.ProjectDescription, - }, + EventData: eventData, }) result, err := v2ProjectModel(claGroupModel) From 1651eb90d3adbf0e9a5dcc771187431197f4a140 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 5 Apr 2021 23:14:54 +0300 Subject: [PATCH 0213/1276] [#2842] Feature/Invalidation Email alert (#2852) Co-authored-by: David Deal --- cla-backend-go/signatures/email.go | 23 ++++++++ cla-backend-go/signatures/models.go | 1 + cla-backend-go/signatures/repository.go | 75 ++++++++++++++++++++----- cla-backend-go/tests/signatures_test.go | 29 ++++++++++ cla-backend-go/utils/email.go | 21 +++++++ 5 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 cla-backend-go/signatures/email.go create mode 100644 cla-backend-go/tests/signatures_test.go diff --git a/cla-backend-go/signatures/email.go b/cla-backend-go/signatures/email.go new file mode 100644 index 000000000..7470f236c --- /dev/null +++ b/cla-backend-go/signatures/email.go @@ -0,0 +1,23 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package signatures + +//InvalidateSignatureTemplateParams representing params when invalidating icla/ecla +type InvalidateSignatureTemplateParams struct { + RecipientName string + ClaType string + ClaManager string + RemovalCriteria string +} + +const ( + //InvalidateSignatureTemplateName is email template for InvalidateSignatureTemplate + InvalidateSignatureTemplateName = "InvalidateSignatureTemplate" + //InvalidateSignatureTemplate ... + InvalidateSignatureTemplate = ` +

      Hello {{.RecipientName}}

      +

      This is a notification email from EasyCLA regarding approval list removal for {{.RemovalCriteria}}

      +

      Due to this change your individual contribution authorization has been removed and you will be blocked from making subsequent code contributions on behalf of this project.

      + ` +) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index aad7ee21e..2775ef8e9 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -24,6 +24,7 @@ type ApprovalList struct { Action string ClaGroupID string CompanyID string + Version string DomainApprovals []string GHOrgApprovals []string GitHubUsernameApprovals []string diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 149cf677b..de9bc08d7 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2087,6 +2087,10 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model for _, email := range params.RemoveEmailApprovalList { go func(email string) { defer wg.Done() + claUser, userErr := repo.usersRepo.GetUserByEmail(email) + if userErr != nil { + log.WithFields(f).Debugf("error getting user by email: %s ", email) + } criteria := &ApprovalCriteria{ UserEmail: email, } @@ -2110,6 +2114,24 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) return } + + // update user by emai + // send email + if claUser != nil { + log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) + _, err := utils.RenderTemplate(utils.V2, InvalidateSignatureTemplateName, + InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ + RecipientName: utils.GetBestUsername(claUser), + ClaType: utils.ClaTypeCCLA, + ClaManager: utils.GetBestUsername(claManager), + RemovalCriteria: approvalList.Criteria, + }) + if err != nil { + log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) + return + } + } + //Log Event eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ SignatureID: signs.Signatures[0].SignatureID, @@ -2122,11 +2144,6 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model repo.eventsService.LogEventWithContext(ctx, eventArgs) // invalidate icla - log.WithFields(f).Debugf("invalidating icla for user: %s...", email) - claUser, userErr := repo.usersRepo.GetUserByEmail(email) - if userErr != nil { - log.WithFields(f).Debugf("error getting user by email: %s ", email) - } if claUser != nil { icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) @@ -2195,6 +2212,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.GerritICLAECLAs = gerritICLAECLAs approvalList.ClaGroupID = projectID approvalList.CompanyID = companyID + approvalList.Version = utils.V2 invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) if invalidateErr != nil { msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) @@ -2287,6 +2305,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Criteria = utils.GitHubOrgCriteria approvalList.ApprovalList = params.RemoveGithubOrgApprovalList approvalList.Action = utils.RemoveApprovals + approvalList.Version = utils.V2 // Get repositories by CLAGroup repositories, err := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) if err != nil { @@ -2424,11 +2443,25 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", signature) return } - verifyErr := repo.verifyUserApprovals(ctx, signature.SignatureReferenceID, signature.SignatureID, claManager, approvalList) + user, verifyErr := repo.verifyUserApprovals(ctx, signature.SignatureReferenceID, signature.SignatureID, claManager, approvalList) if verifyErr != nil { log.WithFields(f).Warnf("unable to verify user: %s ", signature.SignatureReferenceID) return } + email := getBestEmail(user) + // send email + log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) + _, emailErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, + InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ + RecipientName: utils.GetBestUsername(user), + ClaType: utils.ClaTypeICLA, + ClaManager: utils.GetBestUsername(claManager), + RemovalCriteria: approvalList.Criteria, + }) + if emailErr != nil { + log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) + return + } }(icla) } iclaWg.Wait() @@ -2461,11 +2494,25 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", ecla) return } - verifyErr := repo.verifyUserApprovals(ctx, ecla.SignatureReferenceID, ecla.SignatureID, claManager, approvalList) + user, verifyErr := repo.verifyUserApprovals(ctx, ecla.SignatureReferenceID, ecla.SignatureID, claManager, approvalList) if verifyErr != nil { log.WithFields(f).Warnf("unable to verify user: %s ", ecla.SignatureReferenceID) return } + email := getBestEmail(user) + // send email + log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) + _, err := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, + InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ + RecipientName: utils.GetBestUsername(user), + ClaType: utils.ClaTypeECLA, + ClaManager: utils.GetBestUsername(claManager), + RemovalCriteria: approvalList.Criteria, + }) + if err != nil { + log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) + return + } }(ecla) } eclaWg.Wait() @@ -2498,7 +2545,7 @@ func (repo repository) getGerritUserByEmail(ctx context.Context, email string, g } // verify UserApprovals checks user -func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatureID string, claManager *models.User, approvalList *ApprovalList) error { +func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatureID string, claManager *models.User, approvalList *ApprovalList) (*models.User, error) { f := logrus.Fields{ "functionName": "verifyUserApprovals", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -2508,7 +2555,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur user, err := repo.usersRepo.GetUser(userID) if err != nil { log.WithFields(f).Warnf("unable to get user record for ID: %s ", userID) - return err + return nil, err } authUser := auth.User{ @@ -2527,7 +2574,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur err := repo.InvalidateProjectRecord(ctx, signatureID, note) if err != nil { log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) - return err + return user, err } log.WithFields(f).Debugf("removing gerrit user:%s from claGroup: %s ...", user.LfUsername, approvalList.ClaGroupID) @@ -2535,13 +2582,13 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur if iclaErr != nil { msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) log.WithFields(f).Warn(msg) - return iclaErr + return user, iclaErr } eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeECLA) if eclaErr != nil { msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) log.WithFields(f).Warn(msg) - return eclaErr + return user, eclaErr } } } @@ -2554,13 +2601,13 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur err := repo.InvalidateProjectRecord(ctx, signatureID, note) if err != nil { log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) - return err + return user, err } } } } - return nil + return user, nil } // removeColumn is a helper function to remove a given column when we need to zero out the column value - typically the approval list diff --git a/cla-backend-go/tests/signatures_test.go b/cla-backend-go/tests/signatures_test.go new file mode 100644 index 000000000..46698ba77 --- /dev/null +++ b/cla-backend-go/tests/signatures_test.go @@ -0,0 +1,29 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/signatures" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +//TestInvalidateSignatureTemplate validates email sent when signature is invalidated +func TestInvalidateSignatureTemplate(t *testing.T) { + params := signatures.InvalidateSignatureTemplateParams{ + RecipientName: "TestUser", + ClaType: utils.ClaTypeICLA, + ClaManager: "claManager", + RemovalCriteria: "email removal", + } + + result, err := utils.RenderTemplate(utils.V1, signatures.InvalidateSignatureTemplateName, signatures.InvalidateSignatureTemplate, params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello TestUser") + assert.Contains(t, result, "regarding approval list removal for email removal") + assert.Contains(t, result, "your signature record has been invalidated") + +} diff --git a/cla-backend-go/utils/email.go b/cla-backend-go/utils/email.go index bf3758f34..36047b891 100644 --- a/cla-backend-go/utils/email.go +++ b/cla-backend-go/utils/email.go @@ -4,7 +4,9 @@ package utils import ( + "bytes" "errors" + "html/template" "strings" "github.com/sirupsen/logrus" @@ -124,3 +126,22 @@ support.

      ` func GetEmailSignOffContent() string { return `

      EasyCLA Support Team

      ` } + +// RenderTemplate renders the template for given template with given params +func RenderTemplate(claGroupVersion, templateName, templateStr string, params interface{}) (string, error) { + tmpl := template.New(templateName) + t, err := tmpl.Parse(templateStr) + if err != nil { + return "", err + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, params); err != nil { + return "", err + } + + result := tpl.String() + result = result + GetEmailHelpContent(claGroupVersion == V2) + result = result + GetEmailSignOffContent() + return result, nil +} From 05ee9e4d16018a82bb1d0fbfadd33332eb241b2e Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 5 Apr 2021 15:46:16 -0700 Subject: [PATCH 0214/1276] Resolved [#2838] GitHub Organization Not Displayed on PCC (#2853) - Updated GitHub Org and Repository queries to resolve display issue. Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 18 ++++++------- cla-backend-go/repositories/repository.go | 27 +++++++------------ cla-backend-go/repositories/service.go | 4 +-- cla-backend-go/tests/signatures_test.go | 4 +-- .../v2/github_organizations/service.go | 7 ++--- cla-backend-go/v2/repositories/service.go | 2 +- 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 7c9858d40..2a770422e 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -681,15 +681,15 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s var nameUpdated bool data := fmt.Sprintf("CLA Group ID: %s was updated by: %s", args.ProjectID, args.UserName) - if ed.NewClaGroupName != ""{ + if ed.NewClaGroupName != "" { data = fmt.Sprintf("%s with Name from : %s to : %s", data, ed.OldClaGroupName, ed.NewClaGroupName) nameUpdated = true } - if ed.NewClaGroupDescription != ""{ - if nameUpdated{ + if ed.NewClaGroupDescription != "" { + if nameUpdated { data = data + "," - }else{ + } else { data = data + " with" } data = fmt.Sprintf("%s Description from : %s to : %s", data, ed.OldClaGroupDescription, ed.NewClaGroupDescription) @@ -1477,22 +1477,22 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s var nameUpdated, descriptionUpdated bool message := "The CLA Group" - if ed.NewClaGroupName != ""{ + if ed.NewClaGroupName != "" { message = message + " name was updated to : " + ed.NewClaGroupName nameUpdated = true } - if ed.NewClaGroupDescription != ""{ + if ed.NewClaGroupDescription != "" { descriptionUpdated = true - if nameUpdated{ + if nameUpdated { message = message + " and the description was updated to : " + ed.NewClaGroupDescription - }else{ + } else { message = message + " description was updated to : " + ed.NewClaGroupDescription } } //shouldn't happen - if !nameUpdated && !descriptionUpdated{ + if !nameUpdated && !descriptionUpdated { message = message + " was updated" } diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 5d5638325..d5275448c 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -64,7 +64,7 @@ type Repository interface { GetRepositoriesByCLAGroup(ctx context.Context, claGroup string, enabled bool) ([]*models.GithubRepository, error) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) - ListProjectRepositories(ctx context.Context, externalProjectID string, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) + ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) } // NewRepository create new Repository @@ -545,31 +545,22 @@ func (r repo) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID } // List github repositories of project by external/salesforce project id -func (r repo) ListProjectRepositories(ctx context.Context, externalProjectID string, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { +func (r repo) ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { f := logrus.Fields{ - "functionName": "repositories.repository.ListProjectRepositories", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "externalProjectID": externalProjectID, - "projectSFID": projectSFID, - "enabled": utils.BoolValue(enabled), + "functionName": "repositories.repository.ListProjectRepositories", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "enabled": utils.BoolValue(enabled), } - var indexName string out := &models.ListGithubRepositories{ List: make([]*models.GithubRepository, 0), } - var condition expression.KeyConditionBuilder - var filter expression.ConditionBuilder - if externalProjectID != "" { - condition = expression.Key("repository_sfdc_id").Equal(expression.Value(externalProjectID)) - indexName = SFDCRepositoryIndex - } else { - condition = expression.Key("project_sfid").Equal(expression.Value(projectSFID)) - indexName = ProjectSFIDRepositoryOrganizationNameIndex - } + condition := expression.Key("project_sfid").Equal(expression.Value(projectSFID)) // Add the enabled filter, if set + var filter expression.ConditionBuilder if enabled != nil { filter = expression.Name(repositoryEnabledColumn).Equal(expression.Value(enabled)) } @@ -585,7 +576,7 @@ func (r repo) ListProjectRepositories(ctx context.Context, externalProjectID str ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(r.repositoryTableName), - IndexName: aws.String(indexName), + IndexName: aws.String(ProjectSFIDRepositoryOrganizationNameIndex), } results, err := r.dynamoDBClient.Query(queryInput) diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index 67d7210ef..9c2cd36ed 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -152,7 +152,7 @@ func (s *service) DisableRepository(ctx context.Context, repositoryID string) er } func (s *service) ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.ListGithubRepositories, error) { - return s.repo.ListProjectRepositories(ctx, externalProjectID, "", enabled) + return s.repo.ListProjectRepositories(ctx, externalProjectID, enabled) } func (s *service) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { @@ -160,7 +160,7 @@ func (s *service) GetRepository(ctx context.Context, repositoryID string) (*mode } func (s *service) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { - return s.repo.ListProjectRepositories(ctx, "", projectSFID, enabled) + return s.repo.ListProjectRepositories(ctx, projectSFID, enabled) } // GetRepositoryByName returns the repository by name: project-level/cla-project diff --git a/cla-backend-go/tests/signatures_test.go b/cla-backend-go/tests/signatures_test.go index 46698ba77..d5b1ab65e 100644 --- a/cla-backend-go/tests/signatures_test.go +++ b/cla-backend-go/tests/signatures_test.go @@ -23,7 +23,5 @@ func TestInvalidateSignatureTemplate(t *testing.T) { result, err := utils.RenderTemplate(utils.V1, signatures.InvalidateSignatureTemplateName, signatures.InvalidateSignatureTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello TestUser") - assert.Contains(t, result, "regarding approval list removal for email removal") - assert.Contains(t, result, "your signature record has been invalidated") - + assert.Contains(t, result, "Due to this change your individual contribution authorization has been removed and you will be blocked from making subsequent code contributions on behalf of this project.") } diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 730a919bb..e6ef1583a 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -80,6 +80,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) "projectSFID": projectSFID, } + // Load the GitHub Organization and Repository details - result will be missing CLA Group info and ProjectSFID details orgs, err := s.ghService.GetGithubOrganizations(ctx, projectSFID) // log.WithFields(f).Debug("loading github organization details by projectSFID...") //orgs, err := s.repo.GetGithubOrganizations(ctx, projectSFID) @@ -96,7 +97,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return nil, err } - log.Debugf("project record: %+v ", projectServiceRecord) + // log.Debugf("project record: %+v ", projectServiceRecord) var parentProjectSFID string if utils.IsProjectHasRootParent(projectServiceRecord) { @@ -169,9 +170,9 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } } - log.WithFields(f).Debug("listing github repositories...") + log.WithFields(f).Debugf("loading github repositories by projectSFID: %s...", projectSFID) enabled := true - repos, err := s.ghRepository.ListProjectRepositories(ctx, parentProjectSFID, projectSFID, &enabled) + repos, err := s.ghRepository.ListProjectRepositories(ctx, projectSFID, &enabled) if err != nil { log.WithFields(f).WithError(err).Warn("problem loading github repositories") return nil, err diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index f37f8a1f5..92bc2b48c 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -251,7 +251,7 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin } log.WithFields(f).Debug("loaded project from the project service") enabled := true - return s.repo.ListProjectRepositories(ctx, "", projectSFID, &enabled) + return s.repo.ListProjectRepositories(ctx, projectSFID, &enabled) //// Lookup orgs via projectSFID //log.WithFields(f).Debug("querying EasyCLA for organizations by project id...") From 58a03669b71febc44a5a6fa460d80a94bbd73377 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 6 Apr 2021 20:55:59 +0300 Subject: [PATCH 0215/1276] fix legacy/v2 project property loading for event logs (#2854) Signed-off-by: makkalot --- cla-backend-go/events/service.go | 24 +++++++++++++++--------- cla-backend-go/project/handlers.go | 3 ++- cla-backend-go/v2/cla_groups/handlers.go | 7 ++++--- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index ea948ac17..e2670565a 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -222,24 +222,28 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { } func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { + if args == nil { + return errors.New("unable to load SF project data - args is nil") + } + f := logrus.Fields{ "functionName": "v1.events.service.loadSFProject", + "projectID": args.ProjectID, + "projectSFID": args.ProjectSFID, utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - if args == nil { - return errors.New("unable to load SF project data - args is nil") + // if it's a legacy model (v1) we need ot set the project sfid from project id + if args.ProjectSFID == "" && args.ProjectID != "" && utils.IsSalesForceID(args.ProjectID) { + args.ProjectSFID = args.ProjectID } - // Should be the same value for now...cleanup: need to remove one or the other - if args.ProjectID == "" && args.ProjectSFID != "" { - args.ProjectID = args.ProjectSFID - } - if args.ProjectSFID == "" && args.ProjectID != "" { - args.ProjectSFID = args.ProjectID + // if project sfid not there try to set it from claGroupModel (v2) + if args.ProjectSFID == "" && args.ClaGroupModel != nil && args.ClaGroupModel.ProjectExternalID != "" { + args.ProjectSFID = args.ClaGroupModel.ProjectExternalID } - if utils.IsSalesForceID(args.ProjectID) { + if args.ProjectSFID != "" && utils.IsSalesForceID(args.ProjectSFID) { // Check if project exists in platform project service log.WithFields(f).Debugf("loading salesforce project by ID: %s...", args.ProjectSFID) project, projectErr := project_service.GetClient().GetProject(args.ProjectSFID) @@ -275,6 +279,8 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { args.ParentProjectSFID = project.Foundation.ID args.ParentProjectName = project.Foundation.Name } + } else { + log.WithFields(f).Warnf("project sfid %s was not set properly can't set parent project fields in event", args.ProjectSFID) } return nil diff --git a/cla-backend-go/project/handlers.go b/cla-backend-go/project/handlers.go index 32cb774ab..7799daa9e 100644 --- a/cla-backend-go/project/handlers.go +++ b/cla-backend-go/project/handlers.go @@ -316,8 +316,9 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser // Log an event eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + ProjectID: projectParams.Body.ProjectID, + ProjectSFID: projectParams.Body.ProjectExternalID, EventType: events.CLAGroupUpdated, - ProjectSFID: claGroupModel.ProjectExternalID, ClaGroupModel: claGroupModel, UserID: claUser.UserID, LfUsername: claUser.LFUsername, diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 3e2475194..97370a19e 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -148,9 +148,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // Log the event eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupUpdated, - ProjectID: claGroup.ClaGroupID, - LfUsername: authUser.UserName, + EventType: events.CLAGroupUpdated, + ClaGroupModel: claGroupModel, + ProjectID: claGroup.ClaGroupID, + LfUsername: authUser.UserName, EventData: &events.CLAGroupUpdatedEventData{ NewClaGroupName: params.Body.ClaGroupName, NewClaGroupDescription: params.Body.ClaGroupDescription, From 6941e11984f51e07710611977d5a1ebf784d8d46 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 6 Apr 2021 14:52:18 -0700 Subject: [PATCH 0216/1276] Resolves [#2838] GitHub Organization/Repo Display Issue in PCC (#2855) Signed-off-by: David Deal --- .../github_organizations/helpers.go | 6 ++-- .../github_organizations/service.go | 10 +++---- .../v2/github_organizations/service.go | 29 ++++++++++++++----- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/cla-backend-go/github_organizations/helpers.go b/cla-backend-go/github_organizations/helpers.go index a42e43a3e..94ae540ce 100644 --- a/cla-backend-go/github_organizations/helpers.go +++ b/cla-backend-go/github_organizations/helpers.go @@ -59,15 +59,15 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations } if ghorg.OrganizationInstallationID != 0 { - log.WithFields(f).Debugf("Loading GitHub repository list based on installation id: %d...", ghorg.OrganizationInstallationID) + log.WithFields(f).Debugf("Loading GitHub repository list directly from GitHub based on the installation id: %d...", ghorg.OrganizationInstallationID) list, err := github.GetInstallationRepositories(ctx, ghorg.OrganizationInstallationID) if err != nil { - log.WithFields(f).Warnf("unable to get repositories for installation id : %d", ghorg.OrganizationInstallationID) + log.WithFields(f).Warnf("unable to get repositories from GitHub for the installation id: %d", ghorg.OrganizationInstallationID) ghorg.Repositories.Error = err.Error() return } - log.WithFields(f).Debugf("Found %d GitHub repositories using installation id: %d...", + log.WithFields(f).Debugf("Found %d repositories from GitHUb using the installation id: %d...", len(list), ghorg.OrganizationInstallationID) for _, repoInfo := range list { ghorg.Repositories.List = append(ghorg.Repositories.List, &models.GithubRepositoryInfo{ diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 9f1b38d47..f95241362 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -90,9 +90,10 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) if len(projectGithubModels.List) >= 0 { githubOrgs = append(githubOrgs, projectGithubModels.List...) } + log.WithFields(f).Debugf("loaded %d GitHub organizations using projectSFID: %s", len(projectGithubModels.List), projectSFID) - log.WithFields(f).Debug("factoring github orgs for parent...") // Lookup the parent + log.WithFields(f).Debugf("looking up parent for projectSFID: %s...", projectSFID) parentProjectSFID, projErr := v2ProjectService.GetClient().GetParentProject(projectSFID) if projErr != nil { log.WithFields(f).Warnf("problem fetching project parent SFID, error: %+v", projErr) @@ -100,7 +101,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } if parentProjectSFID != projectSFID { - log.WithFields(f).Debugf("searching github organization by parent SFID: %s", parentProjectSFID) + log.WithFields(f).Debugf("found parent of projectSFID: %s to be %s. Searching github organization by parent SFID: %s...", projectSFID, parentProjectSFID, parentProjectSFID) parentGithubModels, parentErr := s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) if parentErr != nil { log.WithFields(f).Warnf("problem fetching github organizations by paarent projectSFID: %s , error: %+v", parentProjectSFID, err) @@ -110,12 +111,11 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) if len(parentGithubModels.List) >= 0 { githubOrgs = append(githubOrgs, parentGithubModels.List...) } + log.WithFields(f).Debugf("loaded %d GitHub organizations using projectSFID: %s", len(parentGithubModels.List), parentProjectSFID) } - log.WithFields(f).Debugf("no parent or parent is %s or %s - search criteria exhausted", - utils.TheLinuxFoundation, utils.TheLinuxFoundation) - gitHubOrgModels.List = githubOrgs + // Remove potential duplicates s.removeDuplicateGHOrgs(gitHubOrgModels.List) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index e6ef1583a..bf30b8650 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -170,16 +170,31 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } } + // We need to search the repository list based on two criteria + // Need to search by projectSFID and/or Organization ID???? log.WithFields(f).Debugf("loading github repositories by projectSFID: %s...", projectSFID) - enabled := true - repos, err := s.ghRepository.ListProjectRepositories(ctx, projectSFID, &enabled) - if err != nil { - log.WithFields(f).WithError(err).Warn("problem loading github repositories") - return nil, err + //enabled := true + //repos, err := s.ghRepository.ListProjectRepositories(ctx, projectSFID, &enabled) + //if err != nil { + // log.WithFields(f).WithError(err).Warn("problem loading github repositories") + // return nil, err + //} + var repoList []*v1Models.GithubRepository + for _, org := range orgs.List { + orgRepos, orgReposErr := s.ghRepository.GetRepositoriesByOrganizationName(ctx, org.OrganizationName) + if orgReposErr != nil { + log.WithFields(f).WithError(orgReposErr).Warn("problem loading github repositories by org name") + return nil, orgReposErr + } + repoList = append(repoList, orgRepos...) } - log.WithFields(f).Debugf("processing %d github repositories...", len(repos.List)) - for _, repo := range repos.List { + // Remove any duplicates + + //jlog.WithFields(f).Debugf("processing %d github repositories...", len(repos.List)) + log.WithFields(f).Debugf("processing %d github repositories...", len(repoList)) + //for _, repo := range repos.List { + for _, repo := range repoList { rorg, ok := orgmap[repo.RepositoryOrganizationName] if !ok { log.WithFields(f).Warnf("repositories table contain stale data for organization %s", repo.RepositoryOrganizationName) From 64fef98a3b4311121751eb94a79a2b396e0cf026 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 6 Apr 2021 16:19:34 -0700 Subject: [PATCH 0217/1276] Resolved GitHub Query Issue with Missing Parent SFID (#2856) Signed-off-by: David Deal --- cla-backend-go/github_organizations/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index f95241362..2d592a6ea 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -100,7 +100,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return nil, projErr } - if parentProjectSFID != projectSFID { + if parentProjectSFID != projectSFID && parentProjectSFID != "" { log.WithFields(f).Debugf("found parent of projectSFID: %s to be %s. Searching github organization by parent SFID: %s...", projectSFID, parentProjectSFID, parentProjectSFID) parentGithubModels, parentErr := s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) if parentErr != nil { From 926749c6a0ebc6816b394fa20291e1330a3b43d4 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:47:47 +0300 Subject: [PATCH 0218/1276] [#2792,#2842] Bug/Update Approval List (#2857) - Resolved icla invalidation for email removals - Resolved email sent for invalidation notification Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/email.go | 6 +- cla-backend-go/signatures/models.go | 1 + cla-backend-go/signatures/repository.go | 91 ++++++++++++++++++------- cla-backend-go/signatures/service.go | 2 +- cla-backend-go/tests/signatures_test.go | 4 +- 5 files changed, 74 insertions(+), 30 deletions(-) diff --git a/cla-backend-go/signatures/email.go b/cla-backend-go/signatures/email.go index 7470f236c..84a69759e 100644 --- a/cla-backend-go/signatures/email.go +++ b/cla-backend-go/signatures/email.go @@ -9,6 +9,7 @@ type InvalidateSignatureTemplateParams struct { ClaType string ClaManager string RemovalCriteria string + ProjectName string } const ( @@ -17,7 +18,8 @@ const ( //InvalidateSignatureTemplate ... InvalidateSignatureTemplate = `

      Hello {{.RecipientName}}

      -

      This is a notification email from EasyCLA regarding approval list removal for {{.RemovalCriteria}}

      -

      Due to this change your individual contribution authorization has been removed and you will be blocked from making subsequent code contributions on behalf of this project.

      +

      This is a notification email from EasyCLA regarding the claGroup {{.ProjectName}}

      +

      The ICLA signature for {{.RecipientName}} has been invalidated.

      +

      Please contact Project Manager for the claGroup {{.ProjectName}} and/or CLA Manager from your company if you have more questions.

      ` ) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 2775ef8e9..6199fe80a 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -23,6 +23,7 @@ type ApprovalList struct { ApprovalList []string Action string ClaGroupID string + ClaGroupName string CompanyID string Version string DomainApprovals []string diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index de9bc08d7..a68bdd63e 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -76,7 +76,7 @@ type SignatureRepository interface { GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) ProjectSignatures(ctx context.Context, projectID string) (*models.Signatures, error) - UpdateApprovalList(ctx context.Context, claManager *models.User, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) + UpdateApprovalList(ctx context.Context, claManager *models.User, claGroupModel *models.ClaGroup, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) RemoveCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) @@ -450,6 +450,8 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u "signatureSigned": "true", } + log.WithFields(f).Debug("querying signature for icla records ...") + // These are the keys we want to match for an ICLA Signature with a given CLA Group and User ID condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(userID))) @@ -1963,8 +1965,10 @@ func (repo repository) RemoveCLAManager(ctx context.Context, signatureID, claMan } // UpdateApprovalList updates the specified project/company signature with the updated approval list information -func (repo repository) UpdateApprovalList(ctx context.Context, claManager *models.User, projectID, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) { // nolint +func (repo repository) UpdateApprovalList(ctx context.Context, claManager *models.User, claGroupModel *models.ClaGroup, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) { // nolint + projectID := claGroupModel.ProjectID + projectName := claGroupModel.ProjectName f := logrus.Fields{ "functionName": "UpdateApprovalList", "projectID": projectID, @@ -2082,14 +2086,17 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // if email removal update signature approvals if params.RemoveEmailApprovalList != nil { + log.WithFields(f).Debugf("removing email: %+v the approval list", params.RemoveDomainApprovalList) var wg sync.WaitGroup wg.Add(len(params.RemoveEmailApprovalList)) for _, email := range params.RemoveEmailApprovalList { go func(email string) { defer wg.Done() + log.WithFields(f).Debugf("getting cla user record for email: %s ", email) claUser, userErr := repo.usersRepo.GetUserByEmail(email) - if userErr != nil { + if userErr != nil || claUser == nil { log.WithFields(f).Debugf("error getting user by email: %s ", email) + return } criteria := &ApprovalCriteria{ UserEmail: email, @@ -2115,21 +2122,26 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return } - // update user by emai + // update user by email // send email - if claUser != nil { - log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) - _, err := utils.RenderTemplate(utils.V2, InvalidateSignatureTemplateName, - InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ - RecipientName: utils.GetBestUsername(claUser), - ClaType: utils.ClaTypeCCLA, - ClaManager: utils.GetBestUsername(claManager), - RemovalCriteria: approvalList.Criteria, - }) - if err != nil { - log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) - return - } + eclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, projectName) + log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) + body, err := utils.RenderTemplate(claGroupModel.Version, InvalidateSignatureTemplateName, + InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ + RecipientName: utils.GetBestUsername(claUser), + ClaType: utils.ClaTypeCCLA, + ClaManager: utils.GetBestUsername(claManager), + RemovalCriteria: approvalList.Criteria, + ProjectName: projectName, + }) + if err != nil { + log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) + return + } + err = utils.SendEmail(eclaEmailSubject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) + return } //Log Event @@ -2139,15 +2151,15 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model CLAManager: claManager, CLAGroupID: signs.Signatures[0].ProjectID, } + repo.eventsService.LogEventWithContext(ctx, eventArgs) } - repo.eventsService.LogEventWithContext(ctx, eventArgs) - // invalidate icla + log.WithFields(f).Debugf("invalidating icla...:user: %+v", claUser) if claUser != nil { icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) - if iclaErr != nil { + if iclaErr != nil || icla == nil { log.WithFields(f).Debugf("unable to get icla signature for user: %s ", email) } if icla != nil { @@ -2156,6 +2168,26 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if err != nil { log.WithFields(f).Warnf("unable to invalidate record for user:%s ", email) } + // send email + log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, + InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ + RecipientName: utils.GetBestUsername(claUser), + ClaType: utils.ClaTypeICLA, + ClaManager: utils.GetBestUsername(claManager), + RemovalCriteria: approvalList.Criteria, + ProjectName: projectName, + }) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) + return + } + iclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, projectName) + err = utils.SendEmail(iclaEmailSubject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) + return + } } } @@ -2211,8 +2243,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Action = utils.RemoveApprovals approvalList.GerritICLAECLAs = gerritICLAECLAs approvalList.ClaGroupID = projectID + approvalList.ClaGroupName = claGroupModel.ProjectName approvalList.CompanyID = companyID - approvalList.Version = utils.V2 + approvalList.Version = claGroupModel.Version invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) if invalidateErr != nil { msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) @@ -2305,7 +2338,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Criteria = utils.GitHubOrgCriteria approvalList.ApprovalList = params.RemoveGithubOrgApprovalList approvalList.Action = utils.RemoveApprovals - approvalList.Version = utils.V2 + approvalList.Version = claGroupModel.Version // Get repositories by CLAGroup repositories, err := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) if err != nil { @@ -2450,16 +2483,22 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A } email := getBestEmail(user) // send email + domainSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, approvalList.ClaGroupName) log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) - _, emailErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ RecipientName: utils.GetBestUsername(user), ClaType: utils.ClaTypeICLA, ClaManager: utils.GetBestUsername(claManager), RemovalCriteria: approvalList.Criteria, }) - if emailErr != nil { - log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) + return + } + err = utils.SendEmail(domainSubject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) return } }(icla) @@ -2525,7 +2564,7 @@ func (repo repository) getGerritUserByEmail(ctx context.Context, email string, g f := logrus.Fields{ "functionName": "getGerritUserByEmailDomain", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "emai": email, + "email": email, } log.WithFields(f).Debugf("checking gerrit user for email: %s ", email) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 86455cf77..340d6d3da 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -435,7 +435,7 @@ func (s service) UpdateApprovalList(ctx context.Context, authUser *auth.User, cl ProjectSFID: claGroupModel.ProjectExternalID, } - updatedSig, err := s.repo.UpdateApprovalList(ctx, userModel, claGroupModel.ProjectID, companyModel.CompanyID, params, eventArgs) + updatedSig, err := s.repo.UpdateApprovalList(ctx, userModel, claGroupModel, companyModel.CompanyID, params, eventArgs) if err != nil { return updatedSig, err diff --git a/cla-backend-go/tests/signatures_test.go b/cla-backend-go/tests/signatures_test.go index d5b1ab65e..b2eed5317 100644 --- a/cla-backend-go/tests/signatures_test.go +++ b/cla-backend-go/tests/signatures_test.go @@ -18,10 +18,12 @@ func TestInvalidateSignatureTemplate(t *testing.T) { ClaType: utils.ClaTypeICLA, ClaManager: "claManager", RemovalCriteria: "email removal", + ProjectName: "testProject", } result, err := utils.RenderTemplate(utils.V1, signatures.InvalidateSignatureTemplateName, signatures.InvalidateSignatureTemplate, params) assert.NoError(t, err) assert.Contains(t, result, "Hello TestUser") - assert.Contains(t, result, "Due to this change your individual contribution authorization has been removed and you will be blocked from making subsequent code contributions on behalf of this project.") + assert.Contains(t, result, "The ICLA signature for TestUser has been invalidated.") + assert.Contains(t, result, "Please contact Project Manager for the claGroup testProject and/or CLA Manager from your company if you have more questions.") } From 8eb4740465ea9bef65db330f5b944fa730abafbb Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 7 Apr 2021 11:40:37 -0700 Subject: [PATCH 0219/1276] Updated GitHub Query Logic (#2858) - Trap and handle the situation for no repositories - Remove duplicates Signed-off-by: David Deal --- .../github_organizations/repository.go | 16 ++++---- .../github_organizations/service.go | 7 ++-- cla-backend-go/repositories/repository.go | 2 +- .../v2/github_organizations/service.go | 38 +++++++++---------- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index a6ef3adc4..e63c20257 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -65,7 +65,7 @@ func NewRepository(awsSession *session.Session, stage string) repository { // AddGithubOrganization add github organization logic func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ - "functionName": "AddGitHubOrganization", + "functionName": "v1.github_organizations.repository.AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "projectSFID": projectSFID, @@ -186,7 +186,7 @@ func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectS // GetGithubOrganizations get github organizations based on the project SFID func (repo repository) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ - "functionName": "GetGitHubOrganizations", + "functionName": "v1.github_organizations.repository.GetGitHubOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -238,7 +238,7 @@ func (repo repository) GetGithubOrganizations(ctx context.Context, projectSFID s func (repo repository) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ - "functionName": "GetGithubOrganizationsByParent", + "functionName": "v1.github_organizations.repository.GetGithubOrganizationsByParent", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, } @@ -289,7 +289,7 @@ func (repo repository) GetGithubOrganizationsByParent(ctx context.Context, paren func (repo repository) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { f := logrus.Fields{ - "functionName": "GetGitHubOrganizationByName", + "functionName": "v1.github_organizations.repository.GetGitHubOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "githubOrganizationName": githubOrganizationName, } @@ -337,7 +337,7 @@ func (repo repository) GetGithubOrganizationByName(ctx context.Context, githubOr func (repo repository) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { f := logrus.Fields{ - "functionName": "GetGitHubOrganization", + "functionName": "v1.github_organizations.repository.GetGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "githubOrganizationName": githubOrganizationName, } @@ -371,7 +371,7 @@ func (repo repository) GetGithubOrganization(ctx context.Context, githubOrganiza // UpdateGithubOrganization updates the specified GitHub organization based on the update model provided func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { f := logrus.Fields{ - "functionName": "UpdateGitHubOrganization", + "functionName": "v1.github_organizations.repository.UpdateGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "organizationName": organizationName, @@ -435,7 +435,7 @@ func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID func (repo repository) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { f := logrus.Fields{ - "functionName": "DeleteGitHubOrganization", + "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "githubOrgName": githubOrgName, @@ -475,7 +475,7 @@ func (repo repository) DeleteGithubOrganization(ctx context.Context, projectSFID func (repo repository) DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error { f := logrus.Fields{ - "functionName": "DeleteGitHubOrganization", + "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "githubOrgName": githubOrgName, diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 2d592a6ea..f810b7444 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -27,6 +27,7 @@ type Service interface { GetGithubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error + RemoveDuplicates(input []*models.GithubOrganization) []*models.GithubOrganization } type service struct { @@ -117,7 +118,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) gitHubOrgModels.List = githubOrgs // Remove potential duplicates - s.removeDuplicateGHOrgs(gitHubOrgModels.List) + gitHubOrgModels.List = s.RemoveDuplicates(gitHubOrgModels.List) return &gitHubOrgModels, err } @@ -184,8 +185,8 @@ func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID strin return s.repo.DeleteGithubOrganization(ctx, projectSFID, githubOrgName) } -// filter ghOrgs duplicates -func (s service) removeDuplicateGHOrgs(input []*models.GithubOrganization) []*models.GithubOrganization { +// RemoveDuplicates removes any duplicates from the specified list +func (s service) RemoveDuplicates(input []*models.GithubOrganization) []*models.GithubOrganization { if input == nil { return nil } diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index d5275448c..3c192a5c3 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -506,7 +506,7 @@ func (r *repo) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgN if len(results.Items) == 0 { msg := fmt.Sprintf("no repositories found associated GitHub Organization: %s", gitHubOrgName) - log.WithFields(f).Warn(msg) + log.WithFields(f).Debug(msg) return nil, &utils.GitHubRepositoryNotFound{ Message: msg, } diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index bf30b8650..6a65e22c9 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -81,13 +81,14 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } // Load the GitHub Organization and Repository details - result will be missing CLA Group info and ProjectSFID details + log.WithFields(f).Debugf("loading GitHub organizations for projectSFID: %s", projectSFID) orgs, err := s.ghService.GetGithubOrganizations(ctx, projectSFID) - // log.WithFields(f).Debug("loading github organization details by projectSFID...") - //orgs, err := s.repo.GetGithubOrganizations(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("problem loading github organizations from the project service") return nil, err } + log.WithFields(f).Debugf("discovered %d GitHub organizations for projectSFID: %s", len(orgs.List), projectSFID) + orgs.List = s.ghService.RemoveDuplicates(orgs.List) psc := v2ProjectService.GetClient() log.WithFields(f).Debug("loading project details from the project service...") @@ -97,8 +98,6 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return nil, err } - // log.Debugf("project record: %+v ", projectServiceRecord) - var parentProjectSFID string if utils.IsProjectHasRootParent(projectServiceRecord) { parentProjectSFID = projectSFID @@ -108,14 +107,20 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") + // Our response model out := &models.ProjectGithubOrganizations{ List: make([]*models.ProjectGithubOrganization, 0), } + + // Next, we need to load a bunch of additional data for the response including the github status (if it's still connected/live, not renamed/moved), the CLA Group details, etc. + + // A temp data model for holding the intermediate results type githubRepoInfo struct { orgName string repoInfo *v1Models.GithubRepositoryInfo } - // connectedRepo contains list of repositories for which github app have permission + + // connectedRepo contains list of repositories for which github app have permission to see connectedRepo := make(map[string]*githubRepoInfo) orgmap := make(map[string]*models.ProjectGithubOrganization) for _, org := range orgs.List { @@ -172,28 +177,23 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) // We need to search the repository list based on two criteria // Need to search by projectSFID and/or Organization ID???? - log.WithFields(f).Debugf("loading github repositories by projectSFID: %s...", projectSFID) - //enabled := true - //repos, err := s.ghRepository.ListProjectRepositories(ctx, projectSFID, &enabled) - //if err != nil { - // log.WithFields(f).WithError(err).Warn("problem loading github repositories") - // return nil, err - //} + log.WithFields(f).Debugf("loading github repositories from %d organizations for projectSFID: %s...", len(orgs.List), projectSFID) var repoList []*v1Models.GithubRepository for _, org := range orgs.List { orgRepos, orgReposErr := s.ghRepository.GetRepositoriesByOrganizationName(ctx, org.OrganizationName) - if orgReposErr != nil { - log.WithFields(f).WithError(orgReposErr).Warn("problem loading github repositories by org name") - return nil, orgReposErr + if orgReposErr != nil || orgRepos == nil { + if _, ok := orgReposErr.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).Debug(orgReposErr) + } else { + log.WithFields(f).WithError(orgReposErr).Warn("problem loading github repositories by org name") + } + } else { + repoList = append(repoList, orgRepos...) } - repoList = append(repoList, orgRepos...) } // Remove any duplicates - - //jlog.WithFields(f).Debugf("processing %d github repositories...", len(repos.List)) log.WithFields(f).Debugf("processing %d github repositories...", len(repoList)) - //for _, repo := range repos.List { for _, repo := range repoList { rorg, ok := orgmap[repo.RepositoryOrganizationName] if !ok { From 96f8c5003bec5fd750a447e8c451bc2b69d13b71 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 7 Apr 2021 14:46:28 -0700 Subject: [PATCH 0220/1276] Added Nil Check For Signature Query (#2859) Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index a68bdd63e..168f79888 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -1407,12 +1407,12 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, filter := expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true))). And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))) - if criteria.GitHubUsername != "" { + if criteria != nil && criteria.GitHubUsername != "" { log.WithFields(f).Debugf("adding Githubusername criteria filter for :%s ", criteria.GitHubUsername) filter = filter.And(expression.Name("user_github_username").Equal(expression.Value(criteria.GitHubUsername))) } - if criteria.UserEmail != "" { + if criteria != nil && criteria.UserEmail != "" { log.WithFields(f).Debugf("adding useremail criteria filter for : %s ", criteria.UserEmail) filter = filter.And(expression.Name("user_email").Equal(expression.Value(criteria.UserEmail))) } From 41e08cde2ac53cbf1add0f135a936947b8266836 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 8 Apr 2021 17:29:35 -0700 Subject: [PATCH 0221/1276] Removed Duplicate CCLA Get Signature API Call (#2862) - Only load gerrit user list on approval list removals - Updated logging/error handling - Load both ICLA and CCLA gerrit users using a go routine (concurrently) - Resolved lint errors - Updated signature query by using the proper index Signed-off-by: David Deal --- cla-backend-go/gerrits/lf_group.go | 6 +- cla-backend-go/go.mod | 6 +- cla-backend-go/go.sum | 6 + cla-backend-go/signatures/models.go | 9 + cla-backend-go/signatures/repository.go | 310 ++++++++++++------------ cla-backend-go/tests/utils_test.go | 7 - cla-backend-go/utils/signature_utils.go | 2 +- 7 files changed, 179 insertions(+), 167 deletions(-) diff --git a/cla-backend-go/gerrits/lf_group.go b/cla-backend-go/gerrits/lf_group.go index f3b987b72..dd73302bb 100644 --- a/cla-backend-go/gerrits/lf_group.go +++ b/cla-backend-go/gerrits/lf_group.go @@ -25,6 +25,7 @@ import ( // constants const ( DefaultHTTPTimeout = 10 * time.Second + LongHTTPTimeout = 45 * time.Second ) // LFGroup contains access information of lf LDAP group @@ -186,7 +187,7 @@ func (lfg *LFGroup) GetUsersOfGroup(ctx context.Context, authUser *auth.User, cl req.Header.Add("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+accessToken) client := http.Client{ - Timeout: DefaultHTTPTimeout, + Timeout: LongHTTPTimeout, } // Invoke the request @@ -209,8 +210,6 @@ func (lfg *LFGroup) GetUsersOfGroup(ctx context.Context, authUser *auth.User, cl log.WithFields(f).Debugf("successfully fetched members from group: %s", groupName) var result v2Models.GerritGroupResponse - //err = json.NewDecoder(resp.Body).Decode(&result) - body, err := ioutil.ReadAll(resp.Body) if err != nil { log.WithFields(f).WithError(err).Warnf("problem reading response for url: %s", url) @@ -223,7 +222,6 @@ func (lfg *LFGroup) GetUsersOfGroup(ctx context.Context, authUser *auth.User, cl log.WithFields(f).WithError(err).Warnf("problem unmarshalling response for url: %s", url) return nil, err } - log.WithFields(f).Debugf("response body: %+v", result) return &result, nil } diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index ae1fb4901..a98bc3a35 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -8,8 +8,8 @@ replace github.com/awslabs/aws-lambda-go-api-proxy => github.com/LF-Engineering/ require ( github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 - github.com/LF-Engineering/lfx-kit v0.1.22 - github.com/LF-Engineering/lfx-models v0.6.34 + github.com/LF-Engineering/lfx-kit v0.1.24 + github.com/LF-Engineering/lfx-models v0.6.42 github.com/aws/aws-lambda-go v1.22.0 github.com/aws/aws-sdk-go v1.36.27 github.com/aymerick/raymond v2.0.2+incompatible @@ -28,7 +28,7 @@ require ( github.com/go-openapi/swag v0.19.9 github.com/go-openapi/validate v0.19.10 github.com/go-resty/resty/v2 v2.3.0 - github.com/gofrs/uuid v3.2.0+incompatible + github.com/gofrs/uuid v4.0.0+incompatible github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.3 // indirect github.com/google/go-github/v33 v33.0.0 diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index a29cdd858..70303eb34 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -28,8 +28,12 @@ github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUam github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= github.com/LF-Engineering/lfx-kit v0.1.22 h1:4tE1xTvu5CRWIokOo1waOfuB6vgaCpov5glhkdVzbAs= github.com/LF-Engineering/lfx-kit v0.1.22/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= +github.com/LF-Engineering/lfx-kit v0.1.24 h1:2lfmBMWWfOwU2XEOSP7keS/CqR5mj30YmJPv6fUiOvM= +github.com/LF-Engineering/lfx-kit v0.1.24/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.34 h1:K8al2aTq8nDm3qNmsTNAhZ1uDzfew/UymwbcW9gbDDs= github.com/LF-Engineering/lfx-models v0.6.34/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= +github.com/LF-Engineering/lfx-models v0.6.42 h1:PfvP/hmcV+OHftNrzclzvwGUv864xyNcp0Kz2HUA5C0= +github.com/LF-Engineering/lfx-models v0.6.42/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= @@ -265,6 +269,8 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5 github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 6199fe80a..3faf360b3 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -3,6 +3,8 @@ package signatures +import v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + // SignatureCompanyID is a simple data model to hold the signature ID and come company details for CCLA's type SignatureCompanyID struct { SignatureID string @@ -33,3 +35,10 @@ type ApprovalList struct { GHUsernames []string GerritICLAECLAs []string } + +// GerritUserResponse is a data structure to hold the gerrit user query response +type GerritUserResponse struct { + gerritGroupResponse *v2Models.GerritGroupResponse + queryType string + Error error +} diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 168f79888..a3a4046c0 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -127,7 +127,7 @@ func NewRepository(awsSession *session.Session, stage string, companyRepo compan // GetGithubOrganizationsFromWhitelist returns a list of GH organizations stored in the whitelist func (repo repository) GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string) ([]models.GithubOrg, error) { f := logrus.Fields{ - "functionName": "GetGitHubOrganizationsFromWhitelist", + "functionName": "v1.signatures.repository.GetGitHubOrganizationsFromWhitelist", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, } @@ -171,7 +171,7 @@ func (repo repository) GetGithubOrganizationsFromWhitelist(ctx context.Context, // AddGithubOrganizationToWhitelist adds the specified GH organization to the whitelist func (repo repository) AddGithubOrganizationToWhitelist(ctx context.Context, signatureID, GitHubOrganizationID string) ([]models.GithubOrg, error) { f := logrus.Fields{ - "functionName": "AddGitHubOrganizationToWhitelist", + "functionName": "v1.signatures.repository.AddGitHubOrganizationToWhitelist", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "GitHubOrganizationID": GitHubOrganizationID, @@ -264,7 +264,7 @@ func (repo repository) AddGithubOrganizationToWhitelist(ctx context.Context, sig // DeleteGithubOrganizationFromWhitelist removes the specified GH organization from the whitelist func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context, signatureID, GitHubOrganizationID string) ([]models.GithubOrg, error) { f := logrus.Fields{ - "functionName": "DeleteGitHubOrganizationFromWhitelist", + "functionName": "v1.signatures.repository.DeleteGitHubOrganizationFromWhitelist", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "GitHubOrganizationID": GitHubOrganizationID, @@ -385,7 +385,7 @@ func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context // GetSignature returns the signature for the specified signature id func (repo repository) GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "GetSignature", + "functionName": "v1.signatures.repository.GetSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, } @@ -439,7 +439,7 @@ func (repo repository) GetSignature(ctx context.Context, signatureID string) (*m // GetIndividualSignature returns the signature record for the specified CLA Group and User func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "GetIndividualSignature", + "functionName": "v1.signatures.repository.GetIndividualSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "tableName": repo.signatureTableName, "claGroupID": claGroupID, @@ -534,7 +534,7 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u // GetCorporateSignature returns the signature record for the specified CLA Group and Company ID func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "GetCorporateSignature", + "functionName": "v1.signatures.repository.GetCorporateSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "tableName": repo.signatureTableName, "claGroupID": claGroupID, @@ -545,7 +545,7 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co "signatureSigned": "true", } - // These are the keys we want to match for an ICLA Signature with a given CLA Group and User ID + // These are the keys we want to match for an CCLA Signature with a given CLA Group and Company ID condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(companyID))) filter := expression.Name("signature_type").Equal(expression.Value("ccla")). @@ -626,7 +626,7 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co // GetSignatureACL returns the signature ACL for the specified signature id func (repo repository) GetSignatureACL(ctx context.Context, signatureID string) ([]string, error) { f := logrus.Fields{ - "functionName": "GetSignatureACL", + "functionName": "v1.signatures.repository.GetSignatureACL", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, } @@ -687,7 +687,7 @@ func addConditionToFilter(filter expression.ConditionBuilder, cond expression.Co // GetProjectSignatures returns a list of signatures for the specified project func (repo repository) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "signatures.repository.GetProjectSignatures", + "functionName": "v1.signatures.repository.GetProjectSignatures", "tableName": repo.signatureTableName, utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ProjectID, @@ -891,7 +891,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // CreateProjectSummaryReport generates a project summary report based on the specified input func (repo repository) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) { // nolint f := logrus.Fields{ - "functionName": "signatures.repository.CreateProjectSummaryReport", + "functionName": "v1.signatures.repository.CreateProjectSummaryReport", "tableName": repo.signatureTableName, utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ProjectID, @@ -1109,7 +1109,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si // GetProjectCompanySignature returns a the signature for the specified project and specified company with the other query flags func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "GetProjectCompanySignature", + "functionName": "v1.signatures.repository.GetProjectCompanySignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectID": projectID, @@ -1118,9 +1118,12 @@ func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID "pageSize": aws.Int64Value(pageSize), "nextKey": aws.StringValue(nextKey), } + + log.WithFields(f).Debug("querying for project company signature...") sortOrder := utils.SortOrderAscending sigs, getErr := repo.GetProjectCompanySignatures(ctx, companyID, projectID, signed, approved, nextKey, &sortOrder, pageSize) if getErr != nil { + log.WithFields(f).WithError(getErr).Warn("problem loading project company signatures...") return nil, getErr } @@ -1133,13 +1136,14 @@ func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID companyID, projectID) } + log.WithFields(f).Debugf("returning project company signature") return sigs.Signatures[0], nil } // GetProjectCompanySignatures returns a list of signatures for the specified project and specified company func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "GetProjectCompanySignatures", + "functionName": "v1.signatures.repository.GetProjectCompanySignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectID": projectID, @@ -1151,20 +1155,21 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI } // These are the keys we want to match - condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)) - filter := expression.Name("signature_reference_id").Equal(expression.Value(companyID)). - And(expression.Name("signature_type").Equal(expression.Value("ccla"))). + //condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)) + condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)). + And(expression.Key("signature_reference_id").Equal(expression.Value(companyID))) + filter := expression.Name("signature_type").Equal(expression.Value("ccla")). And(expression.Name("signature_reference_type").Equal(expression.Value("company"))) // If the caller provided a signature signed value...add the appropriate filter if signed != nil { - log.WithFields(f).Debugf("Filtering signature_signed: %+v", *signed) + log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(*signed)))) } // If the caller provided a signature approved value...add the appropriate filter if approved != nil { - log.WithFields(f).Debugf("Filter by signature_approved: %+v", *approved) + log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(*approved)))) } @@ -1172,6 +1177,7 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI if pageSize != nil { limit = *pageSize } + log.WithFields(f).Debugf("page size %d", limit) // Use the nice builder to create the expression expr, err := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter).WithProjection(buildProjection()).Build() @@ -1189,8 +1195,9 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI FilterExpression: expr.Filter(), ProjectionExpression: expr.Projection(), TableName: aws.String(repo.signatureTableName), - IndexName: aws.String("project-signature-index"), // Name of a secondary index to scan + IndexName: aws.String(SignatureProjectReferenceIndex), // Name of a secondary index to scan Limit: aws.Int64(limit), + //IndexName: aws.String("project-signature-index"), // Name of a secondary index to scan } // If we have the next key, set the exclusive start key value @@ -1214,14 +1221,17 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI // Loop until we have all the records for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call + log.WithFields(f).Debugf("executing query for input: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { - log.WithFields(f).Warnf("error retrieving project signature ID for project: %s with company: %s, error: %v", + log.WithFields(f).WithError(errQuery).Warnf("error retrieving project signature ID for project: %s with company: %s, error: %v", projectID, companyID, errQuery) return nil, errQuery } + log.WithFields(f).Debugf("query response received with %d results", len(results.Items)) // Convert the list of DB models to a list of response models + log.WithFields(f).Debugf("building response model for %d results", len(results.Items)) signatureList, modelErr := repo.buildProjectSignatureModels(ctx, results, projectID, LoadACLDetails) if modelErr != nil { log.WithFields(f).Warnf("error converting DB model to response model for signatures with project %s with company: %s, error: %v", @@ -1265,6 +1275,10 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI // Meta-data for the response totalCount := *describeTableResult.Table.ItemCount + log.WithFields(f).Debugf("returing %d signatures", len(sigs)) + if len(sigs) > 0 { + log.WithFields(f).Debugf("signatureID: %s", sigs[0].SignatureID) + } return &models.Signatures{ ProjectID: projectID, ResultCount: int64(len(sigs)), @@ -1277,7 +1291,7 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI // Get project signatures with no pagination func (repo repository) ProjectSignatures(ctx context.Context, projectID string) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "ProjectSignatures", + "functionName": "v1.signatures.repository.ProjectSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectID": projectID, } @@ -1347,7 +1361,7 @@ func (repo repository) ProjectSignatures(ctx context.Context, projectID string) // InvalidateProjectRecord invalidates the specified project record by setting the signature_approved flag to false func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID, note string) error { f := logrus.Fields{ - "functionName": "InvalidateProjectRecord", + "functionName": "v1.signatures.repository.InvalidateProjectRecord", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, } @@ -1391,7 +1405,7 @@ func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID, // GetProjectCompanyEmployeeSignatures returns a list of employee signatures for the specified project and specified company func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, pageSize int64) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "GetProjectCompanyEmployeeSignatures", + "functionName": "v1.signatures.repository.GetProjectCompanyEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectID": params.ProjectID, "companyID": params.CompanyID, @@ -1521,7 +1535,7 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, // GetCompanySignatures returns a list of company signatures for the specified company func (repo repository) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "GetCompanySignatures", + "functionName": "v1.signatures.repository.GetCompanySignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } @@ -1639,7 +1653,7 @@ func (repo repository) GetCompanySignatures(ctx context.Context, params signatur // GetCompanyIDsWithSignedCorporateSignatures returns a list of company IDs that have signed a CLA agreement func (repo repository) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) { f := logrus.Fields{ - "functionName": "GetCompanyIDsWithSignedCorporateSignatures", + "functionName": "v1.signatures.repository.GetCompanyIDsWithSignedCorporateSignatures", "claGroupID": claGroupID, "signature_project_id": claGroupID, "signature_type": "ccla", @@ -1721,7 +1735,7 @@ func (repo repository) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Co // GetUserSignatures returns a list of user signatures for the specified user func (repo repository) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) { f := logrus.Fields{ - "functionName": "GetUserSignatures", + "functionName": "v1.signatures.repository.GetUserSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } // This is the keys we want to match @@ -1824,7 +1838,7 @@ func (repo repository) GetUserSignatures(ctx context.Context, params signatures. func (repo repository) AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "AddCLAManager", + "functionName": "v1.signatures.repository.AddCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "claManagerID": claManagerID, @@ -1892,7 +1906,7 @@ func (repo repository) AddCLAManager(ctx context.Context, signatureID, claManage func (repo repository) RemoveCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "RemoveCLAManager", + "functionName": "v1.signatures.repository.RemoveCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "claManagerID": claManagerID, @@ -1970,7 +1984,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model projectID := claGroupModel.ProjectID projectName := claGroupModel.ProjectName f := logrus.Fields{ - "functionName": "UpdateApprovalList", + "functionName": "v1.signatures.repository.UpdateApprovalList", "projectID": projectID, "companyID": companyID, } @@ -1978,30 +1992,11 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model signed, approved := true, true pageSize := int64(10) - log.WithFields(f).Debugf("querying database for approval list details using company ID: %s project ID: %s, type: ccla, signed: true, approved: true", - companyID, projectID) - sortOrder := utils.SortOrderAscending - sigs, sigErr := repo.GetProjectCompanySignatures(ctx, companyID, projectID, &signed, &approved, nil, &sortOrder, &pageSize) - if sigErr != nil { - return nil, sigErr - } - - if sigs == nil || sigs.Signatures == nil { - msg := fmt.Sprintf("unable to locate signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - companyID, projectID, signed, approved) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) - } - - if len(sigs.Signatures) > 1 { - log.WithFields(f).Warnf("more than 1 CCLA signature returned for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t - expecting zero or 1 - using first record", - companyID, projectID, signed, approved) - } // Get CCLA signature - For Approval List info cclaSignature, err := repo.GetCorporateSignature(ctx, projectID, companyID) - if err != nil { - msg := "unable to get corporate signature" + if err != nil || cclaSignature == nil { + msg := fmt.Sprintf("unable to get corporate signature for CLA Group: %s and company: %s", projectID, companyID) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -2015,7 +2010,6 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } // Just grab and use the first one - need to figure out conflict resolution if more than one - sig := sigs.Signatures[0] expressionAttributeNames := map[string]*string{} expressionAttributeValues := map[string]*dynamodb.AttributeValue{} haveAdditions := false @@ -2034,46 +2028,42 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Keep track of gerrit users under a give CLA Group var gerritICLAECLAs []string - log.WithFields(f).Debug("aggregating ICLA and ECLA gerrit users...") - gerritIclaUsers, err := repo.gerritService.GetUsersOfGroup(ctx, &authUser, projectID, utils.ClaTypeICLA) - - if err != nil { - msg := fmt.Sprintf("unable to fetch gerrit users for claGroup: %s , claType: %s ", projectID, utils.ClaTypeICLA) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) - } - - if gerritIclaUsers != nil { - for _, member := range gerritIclaUsers.Members { - gerritICLAECLAs = append(gerritICLAECLAs, member.Username) - } - } - - gerritEclaUsers, err := repo.gerritService.GetUsersOfGroup(ctx, &authUser, projectID, utils.ClaTypeECLA) - - if err != nil { - msg := fmt.Sprintf("unable to fetch gerrit users for claGroup: %s , claType: %s ", projectID, utils.ClaTypeECLA) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) - } - - if gerritEclaUsers != nil { - for _, member := range gerritEclaUsers.Members { - gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + // Only load the gerrit user information, which is costly, if we have updates to remove email or email domains + if params.RemoveEmailApprovalList != nil || params.RemoveDomainApprovalList != nil { + + goRoutines := 2 + gerritResultChannel := make(chan *GerritUserResponse, goRoutines) + gerritQueryStartTime, _ := utils.CurrentTime() + go repo.getGerritUsers(ctx, &authUser, projectID, utils.ClaTypeICLA, gerritResultChannel) + go repo.getGerritUsers(ctx, &authUser, projectID, utils.ClaTypeECLA, gerritResultChannel) + + log.WithFields(f).Debug("waiting on gerrit user query results from 2 go routines...") + for i := 0; i < goRoutines; i++ { + results := <-gerritResultChannel + log.WithFields(f).Debugf("received gerrit user query results response for %s - took: %+v", results.queryType, time.Since(gerritQueryStartTime)) + if results.Error != nil { + log.WithFields(f).WithError(results.Error).Warnf("problem retrieving gerrit users for %s, error: %+v", results.queryType, results.Error) + return nil, results.Error + } + for _, member := range results.gerritGroupResponse.Members { + gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + } + log.WithFields(f).Debugf("updated gerrit user query results response for %s - list size is %d...", results.queryType, len(gerritICLAECLAs)) } + log.WithFields(f).Debugf("received the gerrit user query results from %d go routines...", goRoutines) } // If we have an add or remove email list...we need to run an update for this column if params.AddEmailApprovalList != nil || params.RemoveEmailApprovalList != nil { columnName := "email_whitelist" - attrList := buildApprovalAttributeList(ctx, sig.EmailApprovalList, params.AddEmailApprovalList, params.RemoveEmailApprovalList) + attrList := buildApprovalAttributeList(ctx, cclaSignature.EmailApprovalList, params.AddEmailApprovalList, params.RemoveEmailApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) + cclaSignature, rmColErr = repo.removeColumn(ctx, cclaSignature.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - columnName, companyID, projectID, signed, approved) + columnName, companyID, projectID, true, true) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -2118,15 +2108,15 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) if signErr != nil { - log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) + log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", cclaSignature.SignatureID, signErr) return } // update user by email // send email - eclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, projectName) - log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) - body, err := utils.RenderTemplate(claGroupModel.Version, InvalidateSignatureTemplateName, + eclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for '%s' on project '%s'", email, projectName) + log.WithFields(f).Debugf("sending invalidation email to user: '%s'", email) + body, templateErr := utils.RenderTemplate(claGroupModel.Version, InvalidateSignatureTemplateName, InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ RecipientName: utils.GetBestUsername(claUser), ClaType: utils.ClaTypeCCLA, @@ -2134,13 +2124,13 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model RemovalCriteria: approvalList.Criteria, ProjectName: projectName, }) - if err != nil { - log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) + if templateErr != nil { + log.WithFields(f).WithError(templateErr).Debugf("unable to render invalidation notice for user: '%s'", email) return } - err = utils.SendEmail(eclaEmailSubject, body, []string{email}) - if err != nil { - log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) + sendEmailErr := utils.SendEmail(eclaEmailSubject, body, []string{email}) + if sendEmailErr != nil { + log.WithFields(f).WithError(sendEmailErr).Warnf("unable to send email invalidation notice to user: '%s'", email) return } @@ -2155,21 +2145,21 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } // invalidate icla - log.WithFields(f).Debugf("invalidating icla...:user: %+v", claUser) + log.WithFields(f).Debugf("invalidating icla for user: %+v", claUser) if claUser != nil { icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) if iclaErr != nil || icla == nil { - log.WithFields(f).Debugf("unable to get icla signature for user: %s ", email) + log.WithFields(f).Debugf("unable to get icla signature for user: '%s'", email) } if icla != nil { - note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal ", utils.GetBestUsername(claManager), email) - err := repo.InvalidateProjectRecord(ctx, icla.SignatureID, note) - if err != nil { - log.WithFields(f).Warnf("unable to invalidate record for user:%s ", email) + note := fmt.Sprintf("Signature invalidated (approved set to false) by '%s' due to '%s' removal ", utils.GetBestUsername(claManager), email) + invalidateErr := repo.InvalidateProjectRecord(ctx, icla.SignatureID, note) + if invalidateErr != nil { + log.WithFields(f).WithError(invalidateErr).Warnf("unable to invalidate record for user: '%s' ", email) } // send email - log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) + log.WithFields(f).Debugf("sending invalidation email to user: '%s'", email) body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ RecipientName: utils.GetBestUsername(claUser), @@ -2179,23 +2169,23 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model ProjectName: projectName, }) if renderErr != nil { - log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) + log.WithFields(f).Debugf("unable to render invalidation notice for user: '%s' ", email) return } - iclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, projectName) - err = utils.SendEmail(iclaEmailSubject, body, []string{email}) - if err != nil { - log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) + iclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for '%s' on '%s'", email, projectName) + sendEmailErr := utils.SendEmail(iclaEmailSubject, body, []string{email}) + if sendEmailErr != nil { + log.WithFields(f).WithError(sendEmailErr).Warnf("unable to send email invalidation notice to user: '%s'", email) return } } } //update gerrit permissions - gerritUser, err := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) - if err != nil || gerritUser == nil { + gerritUser, getGerritUserErr := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) + if getGerritUserErr != nil || gerritUser == nil { msg := fmt.Sprintf("unable to get gerrit user by email : %s ", email) - log.WithFields(f).Warn(msg) + log.WithFields(f).WithError(getGerritUserErr).Warn(msg) return } iclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, gerritUser.LfUsername, utils.ClaTypeICLA) @@ -2219,14 +2209,14 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if params.AddDomainApprovalList != nil || params.RemoveDomainApprovalList != nil { columnName := "domain_whitelist" - attrList := buildApprovalAttributeList(ctx, sig.DomainApprovalList, params.AddDomainApprovalList, params.RemoveDomainApprovalList) + attrList := buildApprovalAttributeList(ctx, cclaSignature.DomainApprovalList, params.AddDomainApprovalList, params.RemoveDomainApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) + cclaSignature, rmColErr = repo.removeColumn(ctx, cclaSignature.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - columnName, companyID, projectID, signed, approved) + columnName, companyID, projectID, true, true) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -2256,14 +2246,14 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if params.AddGithubUsernameApprovalList != nil || params.RemoveGithubUsernameApprovalList != nil { columnName := "github_whitelist" - attrList := buildApprovalAttributeList(ctx, sig.GithubUsernameApprovalList, params.AddGithubUsernameApprovalList, params.RemoveGithubUsernameApprovalList) + attrList := buildApprovalAttributeList(ctx, cclaSignature.GithubUsernameApprovalList, params.AddGithubUsernameApprovalList, params.RemoveGithubUsernameApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) + cclaSignature, rmColErr = repo.removeColumn(ctx, cclaSignature.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - columnName, companyID, projectID, signed, approved) + columnName, companyID, projectID, true, true) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -2286,7 +2276,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } log.WithFields(f).Debugf("Updating signature records for ghUsernameApporvalList: %+v ", params.RemoveGithubUsernameApprovalList) signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) - if sigErr != nil { + if ghUserErr != nil { log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) return } @@ -2294,7 +2284,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to ghUsernames approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveGithubUsernameApprovalList) signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) if signErr != nil { - log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", sigs.Signatures[0].SignatureID, signErr) + log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", cclaSignature.SignatureID, signErr) return } @@ -2315,14 +2305,14 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if params.AddGithubOrgApprovalList != nil || params.RemoveGithubOrgApprovalList != nil { columnName := "github_org_whitelist" - attrList := buildApprovalAttributeList(ctx, sig.GithubOrgApprovalList, params.AddGithubOrgApprovalList, params.RemoveGithubOrgApprovalList) + attrList := buildApprovalAttributeList(ctx, cclaSignature.GithubOrgApprovalList, params.AddGithubOrgApprovalList, params.RemoveGithubOrgApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { var rmColErr error - sig, rmColErr = repo.removeColumn(ctx, sig.SignatureID, columnName) + cclaSignature, rmColErr = repo.removeColumn(ctx, cclaSignature.SignatureID, columnName) if rmColErr != nil { msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - columnName, companyID, projectID, signed, approved) + columnName, companyID, projectID, true, true) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -2340,10 +2330,10 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Action = utils.RemoveApprovals approvalList.Version = claGroupModel.Version // Get repositories by CLAGroup - repositories, err := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) - if err != nil { + repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) + if getRepoByCLAGroupErr != nil { msg := fmt.Sprintf("unable to fetch repositories for claGroupID: %s ", projectID) - log.WithFields(f).Warn(msg) + log.WithFields(f).WithError(getRepoByCLAGroupErr).Warn(msg) return nil, errors.New(msg) } var ghOrgRepositories []*models.GithubRepository @@ -2356,10 +2346,10 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } for _, ghOrgRepo := range ghOrgRepositories { - ghOrg, err := repo.ghOrgRepo.GetGithubOrganization(ctx, ghOrgRepo.RepositoryOrganizationName) - if err != nil { + ghOrg, getGHOrgErr := repo.ghOrgRepo.GetGithubOrganization(ctx, ghOrgRepo.RepositoryOrganizationName) + if getGHOrgErr != nil { msg := fmt.Sprintf("unable to get gh org by name: %s ", ghOrgRepo.RepositoryOrganizationName) - log.WithFields(f).Warn(msg) + log.WithFields(f).WithError(getGHOrgErr).Warn(msg) return nil, errors.New(msg) } ghOrgs = append(ghOrgs, ghOrg) @@ -2367,10 +2357,10 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model var ghUsernames []string for _, ghOrg := range ghOrgs { - ghOrgUsers, err := github.GetOrganizationMembers(ctx, ghOrg.OrganizationName, ghOrg.OrganizationInstallationID) - if err != nil { + ghOrgUsers, getOrgMembersErr := github.GetOrganizationMembers(ctx, ghOrg.OrganizationName, ghOrg.OrganizationInstallationID) + if getOrgMembersErr != nil { msg := fmt.Sprintf("unable to fetch ghOrgUsers for org: %s ", ghOrg.OrganizationName) - log.WithFields(f).Warnf(msg) + log.WithFields(f).WithError(getOrgMembersErr).Warnf(msg) return nil, errors.New(msg) } ghUsernames = append(ghUsernames, ghOrgUsers...) @@ -2389,8 +2379,8 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Ensure at least one value is set for us to update if !haveAdditions { log.WithFields(f).Debugf("no updates required to any of the approved list values company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t - expecting at least something to update", - companyID, projectID, signed, approved) - return sig, nil + companyID, projectID, true, true) + return cclaSignature, nil } // Remove trailing comma from the expression, if present @@ -2401,7 +2391,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model TableName: aws.String(repo.signatureTableName), Key: map[string]*dynamodb.AttributeValue{ "signature_id": { - S: aws.String(sig.SignatureID), + S: aws.String(cclaSignature.SignatureID), }, }, ExpressionAttributeNames: expressionAttributeNames, @@ -2419,34 +2409,22 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return nil, updateErr } - log.WithFields(f).Debugf("querying database for approval list details after update using company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - companyID, projectID, signed, approved) - - updatedSig, sigErr := repo.GetProjectCompanySignatures(ctx, companyID, projectID, &signed, &approved, nil, &sortOrder, &pageSize) - if sigErr != nil { - return nil, sigErr - } - - if updatedSig == nil || updatedSig.Signatures == nil { - msg := fmt.Sprintf("unable to locate signature after update for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", - companyID, projectID, signed, approved) + // Query the CCLA signature once again to load the most recent updates which include approval list updates from above + updatedSig, err := repo.GetCorporateSignature(ctx, projectID, companyID) + if err != nil || cclaSignature == nil { + msg := fmt.Sprintf("unable to get corporate signature for CLA Group: %s and company: %s", projectID, companyID) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } - if len(updatedSig.Signatures) > 1 { - log.WithFields(f).Warnf("more than 1 CCLA signature returned after update for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t - expecting zero or 1 - using first record", - companyID, projectID, signed, approved) - } - // Just grab and use the first one - need to figure out conflict resolution if more than one - return updatedSig.Signatures[0], nil + return updatedSig, nil } // invalidateSignatures is a helper function that invalidates signature records based on approval list func (repo repository) invalidateSignatures(ctx context.Context, approvalList *ApprovalList, claManager *models.User) error { f := logrus.Fields{ - "functionName": "invalidateSignatures", + "functionName": "v1.signatures.repository.invalidateSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": &approvalList, } @@ -2562,7 +2540,7 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A // getGerritUsersByEmail searches gerrit instances for users with given email func (repo repository) getGerritUserByEmail(ctx context.Context, email string, gerritICLAECLAs []string) (*models.User, error) { f := logrus.Fields{ - "functionName": "getGerritUserByEmailDomain", + "functionName": "v1.signatures.repository.getGerritUserByEmail", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "email": email, } @@ -2586,7 +2564,7 @@ func (repo repository) getGerritUserByEmail(ctx context.Context, email string, g // verify UserApprovals checks user func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatureID string, claManager *models.User, approvalList *ApprovalList) (*models.User, error) { f := logrus.Fields{ - "functionName": "verifyUserApprovals", + "functionName": "v1.signatures.repository.verifyUserApprovals", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "userID": userID, } @@ -2652,7 +2630,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur // removeColumn is a helper function to remove a given column when we need to zero out the column value - typically the approval list func (repo repository) removeColumn(ctx context.Context, signatureID, columnName string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "removeColumn", + "functionName": "v1.signatures.repository.removeColumn", "signatureID": signatureID, "columnName": columnName, } @@ -2694,7 +2672,7 @@ func (repo repository) removeColumn(ctx context.Context, signatureID, columnName func (repo repository) AddSigTypeSignedApprovedID(ctx context.Context, signatureID string, val string) error { f := logrus.Fields{ - "functionName": "AddSigTypeSignedApprovedID", + "functionName": "v1.signatures.repository.AddSigTypeSignedApprovedID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "sigtypeSignedApprovedID": val, @@ -2726,7 +2704,7 @@ func (repo repository) AddSigTypeSignedApprovedID(ctx context.Context, signature } func (repo repository) AddUsersDetails(ctx context.Context, signatureID string, userID string) error { f := logrus.Fields{ - "functionName": "AddUserDetails", + "functionName": "v1.signatures.repository.AddUserDetails", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "userID": userID, @@ -2791,7 +2769,7 @@ func (repo repository) AddUsersDetails(ctx context.Context, signatureID string, func (repo repository) AddSignedOn(ctx context.Context, signatureID string) error { f := logrus.Fields{ - "functionName": "AddSignedOn", + "functionName": "v1.signatures.repository.AddSignedOn", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, } @@ -2828,7 +2806,7 @@ func (repo repository) AddSignedOn(ctx context.Context, signatureID string) erro func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { f := logrus.Fields{ - "functionName": "GetClaGroupICLASignatures", + "functionName": "v1.signatures.repository.GetClaGroupICLASignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "searchTerm": utils.StringValue(searchTerm), @@ -3054,7 +3032,7 @@ func (repo repository) addAdditionalICLAMetaData(f logrus.Fields, intermediateRe func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) { f := logrus.Fields{ - "functionName": "GetClaGroupCorporateContributors", + "functionName": "v1.signatures.repository.GetClaGroupCorporateContributors", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "companyID": aws.StringValue(companyID), @@ -3175,3 +3153,31 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla return out, nil } + +// getGerritUsers is a helper function to fetch the list of gerrit users for the specified type - results are returned through the specified results channel +func (repo repository) getGerritUsers(ctx context.Context, authUser *auth.User, projectSFID string, claType string, gerritResultChannel chan *GerritUserResponse) { + f := logrus.Fields{ + "functionName": "v1.signatures.repository.getGerritUsers", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + } + log.WithFields(f).Debugf("querying gerrit for %s gerrit users...", claType) + gerritIclaUsers, getGerritQueryErr := repo.gerritService.GetUsersOfGroup(ctx, authUser, projectSFID, claType) + if getGerritQueryErr != nil { + msg := fmt.Sprintf("unable to fetch gerrit users for claGroup: %s , claType: %s ", projectSFID, claType) + log.WithFields(f).WithError(getGerritQueryErr).Warn(msg) + gerritResultChannel <- &GerritUserResponse{ + gerritGroupResponse: nil, + queryType: claType, + Error: errors.New(msg), + } + return + } + + log.WithFields(f).Debugf("retrieved %d gerrit users for CLA type: %s...", len(gerritIclaUsers.Members), claType) + gerritResultChannel <- &GerritUserResponse{ + gerritGroupResponse: gerritIclaUsers, + queryType: claType, + Error: nil, + } +} diff --git a/cla-backend-go/tests/utils_test.go b/cla-backend-go/tests/utils_test.go index 94a8c1578..a180ad0a3 100644 --- a/cla-backend-go/tests/utils_test.go +++ b/cla-backend-go/tests/utils_test.go @@ -381,13 +381,6 @@ func TestIsUUIDv4True(t *testing.T) { assert.True(t, utils.IsUUIDv4(v4.String()), fmt.Sprintf("%s is a v4 UUID", v4.String())) } -func TestIsUUIDv4LikeV2(t *testing.T) { - var b byte = 'b' - v2, err := uuid.NewV2(b) - assert.Nil(t, err, "NewV4 UUID is nil") - assert.False(t, utils.IsUUIDv4(v2.String()), fmt.Sprintf("%s is not a v4 UUID", v2.String())) -} - func TestIsUUIDv4LikeSFID(t *testing.T) { sfid := "0014100000TdznWAAR" assert.False(t, utils.IsUUIDv4(sfid), fmt.Sprintf("%s is not v4 UUID", sfid)) diff --git a/cla-backend-go/utils/signature_utils.go b/cla-backend-go/utils/signature_utils.go index 1f9f9b30c..c75da7a31 100644 --- a/cla-backend-go/utils/signature_utils.go +++ b/cla-backend-go/utils/signature_utils.go @@ -15,7 +15,7 @@ func CurrentUserInACL(authUser *auth.User, managers []v1Models.User) bool { f := logrus.Fields{ "functionName": "utils.CurrentUserInACL", } - log.WithFields(f).Debugf("checking if user: %+v is in the Signature ACL: %+v", authUser, managers) + log.WithFields(f).Debugf("checking if user: %s is in the Signature ACL: %+v", authUser.UserName, managers) var inACL = false for _, manager := range managers { if manager.LfUsername == authUser.UserName { From 5db178992b084c335269deb17292e0312bc8c68e Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 8 Apr 2021 17:41:22 -0700 Subject: [PATCH 0222/1276] Resolved Event Log Entry for Edit CLA Group (#2863) Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/handlers.go | 31 ++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 97370a19e..e1bcf5dde 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -121,9 +121,25 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to lookup CLA Group by ID: %s", params.ClaGroupID), err)) } + projectCLAGroupModels, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + if projectCLAGroupErr != nil { + msg := fmt.Sprintf("unable to load the Project to CLA Group mappings for CLA Group: %s - is this CLA Group configured?", params.ClaGroupID) + log.WithFields(f).Warn(msg) + return cla_group.NewUpdateClaGroupInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, projectCLAGroupErr)) + } + if len(projectCLAGroupModels) == 0 { + msg := fmt.Sprintf("unable to load the Project to CLA Group mappings for CLA Group: %s - is this CLA Group configured?", params.ClaGroupID) + log.WithFields(f).Warn(msg) + return cla_group.NewUpdateClaGroupInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerError(reqID, msg)) + } + var projectSFIDList []string + for _, model := range projectCLAGroupModels { + projectSFIDList = append(projectSFIDList, model.ProjectSFID) + } + // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, []string{claGroupModel.ProjectExternalID}, projectClaGroupsRepo) { - msg := fmt.Sprintf("user %s does not have access to update an existing CLA Group with project scope of: %s", authUser.UserName, claGroupModel.FoundationSFID) + if !isUserHaveAccessToCLAProject(ctx, authUser, projectCLAGroupModels[0].FoundationSFID, projectSFIDList, projectClaGroupsRepo) { + msg := fmt.Sprintf("user %s does not have access to update an existing CLA Group with project scope of: %s or any of these: %s", authUser.UserName, projectCLAGroupModels[0].FoundationSFID, strings.Join(projectSFIDList, ",")) log.WithFields(f).Warn(msg) return cla_group.NewUpdateClaGroupForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -148,14 +164,15 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // Log the event eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupUpdated, - ClaGroupModel: claGroupModel, - ProjectID: claGroup.ClaGroupID, - LfUsername: authUser.UserName, + EventType: events.CLAGroupUpdated, + ClaGroupModel: claGroupModel, + ProjectID: claGroup.ClaGroupID, + ProjectSFID: projectCLAGroupModels[0].ProjectSFID, + ParentProjectSFID: projectCLAGroupModels[0].FoundationSFID, + LfUsername: authUser.UserName, EventData: &events.CLAGroupUpdatedEventData{ NewClaGroupName: params.Body.ClaGroupName, NewClaGroupDescription: params.Body.ClaGroupDescription, - OldClaGroupName: oldCLAGroupName, OldClaGroupDescription: oldCLAGroupDescription, }, From 16bd5b4eeac3d2347526dae4507246b6b78cc793 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 9 Apr 2021 00:33:53 +0300 Subject: [PATCH 0223/1276] [#2792]Refactor/Individual Signature - Refactored logic with generic helper function for individual signature invalidation Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/models.go | 7 +- cla-backend-go/signatures/repository.go | 235 +++++++++++------------- cla-backend-go/utils/constants.go | 3 + 3 files changed, 118 insertions(+), 127 deletions(-) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 3faf360b3..41a3d0e2e 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -3,7 +3,10 @@ package signatures -import v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" +import ( + "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" +) // SignatureCompanyID is a simple data model to hold the signature ID and come company details for CCLA's type SignatureCompanyID struct { @@ -34,6 +37,8 @@ type ApprovalList struct { EmailApprovals []string GHUsernames []string GerritICLAECLAs []string + ICLAs []*models.IclaSignature + ECLAs []*models.Signature } // GerritUserResponse is a data structure to hold the gerrit user query response diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index a3a4046c0..30d071826 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -1982,7 +1982,6 @@ func (repo repository) RemoveCLAManager(ctx context.Context, signatureID, claMan func (repo repository) UpdateApprovalList(ctx context.Context, claManager *models.User, claGroupModel *models.ClaGroup, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error) { // nolint projectID := claGroupModel.ProjectID - projectName := claGroupModel.ProjectName f := logrus.Fields{ "functionName": "v1.signatures.repository.UpdateApprovalList", "projectID": projectID, @@ -2079,6 +2078,10 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debugf("removing email: %+v the approval list", params.RemoveDomainApprovalList) var wg sync.WaitGroup wg.Add(len(params.RemoveEmailApprovalList)) + approvalList.Criteria = utils.EmailCriteria + approvalList.ApprovalList = params.RemoveEmailApprovalList + approvalList.Action = utils.RemoveApprovals + approvalList.Version = claGroupModel.Version for _, email := range params.RemoveEmailApprovalList { go func(email string) { defer wg.Done() @@ -2103,81 +2106,21 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if len(signs.Signatures) > 0 { - log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) - note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to email approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveEmailApprovalList) - - signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) - if signErr != nil { - log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", cclaSignature.SignatureID, signErr) - return - } - - // update user by email - // send email - eclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for '%s' on project '%s'", email, projectName) - log.WithFields(f).Debugf("sending invalidation email to user: '%s'", email) - body, templateErr := utils.RenderTemplate(claGroupModel.Version, InvalidateSignatureTemplateName, - InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ - RecipientName: utils.GetBestUsername(claUser), - ClaType: utils.ClaTypeCCLA, - ClaManager: utils.GetBestUsername(claManager), - RemovalCriteria: approvalList.Criteria, - ProjectName: projectName, - }) - if templateErr != nil { - log.WithFields(f).WithError(templateErr).Debugf("unable to render invalidation notice for user: '%s'", email) - return - } - sendEmailErr := utils.SendEmail(eclaEmailSubject, body, []string{email}) - if sendEmailErr != nil { - log.WithFields(f).WithError(sendEmailErr).Warnf("unable to send email invalidation notice to user: '%s'", email) - return - } - - //Log Event - eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ - SignatureID: signs.Signatures[0].SignatureID, - Email: email, - CLAManager: claManager, - CLAGroupID: signs.Signatures[0].ProjectID, - } - repo.eventsService.LogEventWithContext(ctx, eventArgs) + approvalList.ECLAs = signs.Signatures } - // invalidate icla - log.WithFields(f).Debugf("invalidating icla for user: %+v", claUser) - if claUser != nil { icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) if iclaErr != nil || icla == nil { log.WithFields(f).Debugf("unable to get icla signature for user: '%s'", email) } if icla != nil { - note := fmt.Sprintf("Signature invalidated (approved set to false) by '%s' due to '%s' removal ", utils.GetBestUsername(claManager), email) - invalidateErr := repo.InvalidateProjectRecord(ctx, icla.SignatureID, note) - if invalidateErr != nil { - log.WithFields(f).WithError(invalidateErr).Warnf("unable to invalidate record for user: '%s' ", email) - } - // send email - log.WithFields(f).Debugf("sending invalidation email to user: '%s'", email) - body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, - InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ - RecipientName: utils.GetBestUsername(claUser), - ClaType: utils.ClaTypeICLA, - ClaManager: utils.GetBestUsername(claManager), - RemovalCriteria: approvalList.Criteria, - ProjectName: projectName, - }) - if renderErr != nil { - log.WithFields(f).Debugf("unable to render invalidation notice for user: '%s' ", email) - return - } - iclaEmailSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for '%s' on '%s'", email, projectName) - sendEmailErr := utils.SendEmail(iclaEmailSubject, body, []string{email}) - if sendEmailErr != nil { - log.WithFields(f).WithError(sendEmailErr).Warnf("unable to send email invalidation notice to user: '%s'", email) - return - } + // Convert to IclSignature instance to leverage invalidateSignatures helper function + approvalList.ICLAs = []*models.IclaSignature{{ + GithubUsername: icla.UserGHUsername, + LfUsername: icla.UserLFID, + SignatureID: icla.SignatureID, + }} } } @@ -2227,7 +2170,25 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model updateExpression = updateExpression + " #D = :d, " } if params.RemoveDomainApprovalList != nil { - var invalidateErr error + // Get ICLAs + log.WithFields(f).Debug("getting icla records... ") + iclas, iclaErr := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil, 0, "") + if iclaErr != nil { + log.WithFields(f).Warn("unable to get iclas") + } + // Get ECLAs + log.WithFields(f).Debug("getting ecla records... ") + companyProjectParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ + CompanyID: approvalList.CompanyID, + ProjectID: approvalList.ClaGroupID, + } + + criteria := ApprovalCriteria{} + eclas, eclaErr := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, &criteria, int64(10)) + if eclaErr != nil { + log.WithFields(f).Warnf("unable to get cclas for company: %s and project: %s ", approvalList.CompanyID, approvalList.ClaGroupID) + } + approvalList.Criteria = utils.EmailDomainCriteria approvalList.ApprovalList = params.RemoveDomainApprovalList approvalList.Action = utils.RemoveApprovals @@ -2236,11 +2197,14 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.ClaGroupName = claGroupModel.ProjectName approvalList.CompanyID = companyID approvalList.Version = claGroupModel.Version - invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) - if invalidateErr != nil { - msg := fmt.Sprintf("unable to invalidate signatures based on Approval List : %+v ", approvalList) - log.WithFields(f).Warn(msg) + if iclas != nil { + approvalList.ICLAs = iclas.List + } + if eclas != nil { + approvalList.ECLAs = eclas.Signatures } + + repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) } } @@ -2267,6 +2231,13 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // if email removal update signature approvals if params.RemoveGithubUsernameApprovalList != nil { var wg sync.WaitGroup + approvalList.Criteria = utils.GitHubUsernameCriteria + approvalList.ApprovalList = params.RemoveGithubUsernameApprovalList + approvalList.Action = utils.RemoveApprovals + approvalList.ClaGroupID = projectID + approvalList.ClaGroupName = claGroupModel.ProjectName + approvalList.CompanyID = companyID + approvalList.Version = claGroupModel.Version wg.Add(len(params.RemoveGithubUsernameApprovalList)) for _, ghUsername := range params.RemoveGithubUsernameApprovalList { go func(ghUsername string) { @@ -2280,22 +2251,32 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) return } - log.WithFields(f).Debugf("Updating signature : %s ", signs.Signatures[0].SignatureID) - note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to ghUsernames approval list removal : %+v", utils.GetBestUsername(claManager), params.RemoveGithubUsernameApprovalList) - signErr := repo.InvalidateProjectRecord(ctx, signs.Signatures[0].SignatureID, note) - if signErr != nil { - log.WithFields(f).Debugf("error invalidating signature ID: %s error: %+v ", cclaSignature.SignatureID, signErr) + if signs.Signatures != nil { + approvalList.ECLAs = signs.Signatures + } + // Get ICLAs + claUser, claErr := repo.usersRepo.GetUserByGitHubUsername(ghUsername) + if claErr != nil { + log.WithFields(f).Debugf("unable to get User by GH Username: %s ", ghUsername) return } - - // Log Event - eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ - SignatureID: signs.Signatures[0].SignatureID, - GHUsername: ghUsername, - CLAManager: claManager, - CLAGroupID: signs.Signatures[0].ProjectID, + if claUser != nil { + icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) + if iclaErr != nil || icla == nil { + log.WithFields(f).Debugf("unable to get icla signature for user with ghUsername: %s ", ghUsername) + } + if icla != nil { + // Convert to IclSignature instance to leverage invalidateSignatrues helper function + approvalList.ICLAs = []*models.IclaSignature{{ + GithubUsername: icla.UserGHUsername, + LfUsername: icla.UserLFID, + SignatureID: icla.SignatureID, + }} + } } - repo.eventsService.LogEventWithContext(ctx, eventArgs) + + repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) + }(ghUsername) } wg.Wait() @@ -2324,7 +2305,6 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if params.RemoveGithubOrgApprovalList != nil { - var invalidateErr error approvalList.Criteria = utils.GitHubOrgCriteria approvalList.ApprovalList = params.RemoveGithubOrgApprovalList approvalList.Action = utils.RemoveApprovals @@ -2367,12 +2347,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } approvalList.GHUsernames = utils.RemoveDuplicates(ghUsernames) - invalidateErr = repo.invalidateSignatures(ctx, &approvalList, claManager) - if invalidateErr != nil { - msg := fmt.Sprintf("unable to invalidate signatures based on Approval List: %+v ", approvalList) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) - } + repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) } } @@ -2422,26 +2397,19 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } // invalidateSignatures is a helper function that invalidates signature records based on approval list -func (repo repository) invalidateSignatures(ctx context.Context, approvalList *ApprovalList, claManager *models.User) error { +func (repo repository) invalidateSignatures(ctx context.Context, approvalList *ApprovalList, claManager *models.User, eventArgs *events.LogEventArgs) { f := logrus.Fields{ "functionName": "v1.signatures.repository.invalidateSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": &approvalList, } - // Get ICLAs - log.WithFields(f).Debug("getting icla records... ") - iclas, err := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil, 0, "") - if err != nil { - log.WithFields(f).Warn("unable to get iclas") - } - - if iclas != nil { + if approvalList.ICLAs != nil { var iclaWg sync.WaitGroup //Iterate iclas - iclaWg.Add(len(iclas.List)) + iclaWg.Add(len(approvalList.ICLAs)) log.WithFields(f).Debug("invalidating signature icla records... ") - for _, icla := range iclas.List { + for _, icla := range approvalList.ICLAs { go func(icla *models.IclaSignature) { defer iclaWg.Done() signature, sigErr := repo.GetSignature(ctx, icla.SignatureID) @@ -2461,7 +2429,7 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A } email := getBestEmail(user) // send email - domainSubject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, approvalList.ClaGroupName) + subject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, approvalList.ClaGroupName) log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ @@ -2474,36 +2442,31 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) return } - err = utils.SendEmail(domainSubject, body, []string{email}) + err := utils.SendEmail(subject, body, []string{email}) if err != nil { log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) return } + + // Log Event + eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ + SignatureID: icla.SignatureID, + CLAManager: claManager, + CLAGroupID: signature.ProjectID, + Email: email, + } + repo.eventsService.LogEventWithContext(ctx, eventArgs) }(icla) } iclaWg.Wait() } - // Get ECLAs - log.WithFields(f).Debug("getting ecla records... ") - companyProjectParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ - CompanyID: approvalList.CompanyID, - ProjectID: approvalList.ClaGroupID, - } - - criteria := ApprovalCriteria{} - eclas, err := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, &criteria, int64(10)) - if err != nil { - log.WithFields(f).Warnf("unable to get cclas for company: %s and project: %s ", approvalList.CompanyID, approvalList.ClaGroupID) - return err - } - - if eclas != nil { + if approvalList.ECLAs != nil { var eclaWg sync.WaitGroup log.WithFields(f).Debug("invalidating signature ecla records... ") // Iterate eclas - eclaWg.Add(len(eclas.Signatures)) - for _, ecla := range eclas.Signatures { + eclaWg.Add(len(approvalList.ECLAs)) + for _, ecla := range approvalList.ECLAs { go func(ecla *models.Signature) { defer eclaWg.Done() // Grab user record @@ -2518,23 +2481,36 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A } email := getBestEmail(user) // send email + subject := fmt.Sprintf("EasyCLA: Employee Acknowledgement invalidated for %s on %s", email, approvalList.ClaGroupName) log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) - _, err := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ RecipientName: utils.GetBestUsername(user), ClaType: utils.ClaTypeECLA, ClaManager: utils.GetBestUsername(claManager), RemovalCriteria: approvalList.Criteria, }) - if err != nil { + if renderErr != nil { log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) return } + err := utils.SendEmail(subject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) + return + } + // Log Event + eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ + SignatureID: ecla.SignatureID, + CLAManager: claManager, + CLAGroupID: ecla.ProjectID, + Email: email, + } + repo.eventsService.LogEventWithContext(ctx, eventArgs) }(ecla) } eclaWg.Wait() } - return nil } // getGerritUsersByEmail searches gerrit instances for users with given email @@ -2622,6 +2598,13 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur } } } + } else if approvalList.Criteria == utils.GitHubUsernameCriteria || approvalList.Criteria == utils.EmailCriteria { + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), approvalList.Criteria) + err := repo.InvalidateProjectRecord(ctx, signatureID, note) + if err != nil { + log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) + return user, err + } } return user, nil diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 2336e11db..c91af9f7a 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -164,3 +164,6 @@ const AddApprovals = "AddApprovals" //RemoveApprovals is an action for removing approvals const RemoveApprovals = "RemoveApprovals" + +//GitHubUsernameCriteria represents criteria based on GH username +const GitHubUsernameCriteria = "GitHubUsername" From 71b020cf81c911c607fd47b868d58205c9fbcf50 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Sat, 10 Apr 2021 21:20:51 +0300 Subject: [PATCH 0224/1276] [#2792] Bug/Update Approval List (#2864) - Resolved nil pointer issue caused with no gerrit users found - Resolved wrong user search by using user_email string set field rather than lf_email Signed-off-by: Harold Wanyama --- cla-backend-go/go.sum | 1 + cla-backend-go/signatures/models.go | 6 +++ cla-backend-go/signatures/repository.go | 60 ++++++++++++++++--------- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 70303eb34..d094865eb 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -103,6 +103,7 @@ github.com/communitybridge/easycla v1.0.145 h1:ikhBSsOeEL2u3/EoyDsufh/j3HkjfFTiX github.com/communitybridge/easycla v2.0.10+incompatible h1:6eRJ5fxrMxRZHBkg8piYo+zHTcSowMrP85nZXzp5mpA= github.com/communitybridge/easycla v2.0.16+incompatible h1:I0hEApDh4IvlwRPyHV1LOsSYlSPbqBsGszjSTHwkdak= github.com/communitybridge/easycla v2.0.19+incompatible h1:HLaNt3jGDXPh3Au+rW/HKbJNkQf3daboVIrP9G6WYQ4= +github.com/communitybridge/easycla v2.0.29+incompatible h1:kEply0yEyhsflUFDo7DZ0XBpncNPnsdgy6zn6ujkReY= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 41a3d0e2e..ae46a2304 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -47,3 +47,9 @@ type GerritUserResponse struct { queryType string Error error } + +// ICLAUserResponse is struct that supports ICLAUsers +type ICLAUserResponse struct { + ICLASignature *models.IclaSignature + Error error +} diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 30d071826..a8c9158b6 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2042,12 +2042,12 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debugf("received gerrit user query results response for %s - took: %+v", results.queryType, time.Since(gerritQueryStartTime)) if results.Error != nil { log.WithFields(f).WithError(results.Error).Warnf("problem retrieving gerrit users for %s, error: %+v", results.queryType, results.Error) - return nil, results.Error - } - for _, member := range results.gerritGroupResponse.Members { - gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + } else { + for _, member := range results.gerritGroupResponse.Members { + gerritICLAECLAs = append(gerritICLAECLAs, member.Username) + } + log.WithFields(f).Debugf("updated gerrit user query results response for %s - list size is %d...", results.queryType, len(gerritICLAECLAs)) } - log.WithFields(f).Debugf("updated gerrit user query results response for %s - list size is %d...", results.queryType, len(gerritICLAECLAs)) } log.WithFields(f).Debugf("received the gerrit user query results from %d go routines...", goRoutines) } @@ -2086,8 +2086,8 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model go func(email string) { defer wg.Done() log.WithFields(f).Debugf("getting cla user record for email: %s ", email) - claUser, userErr := repo.usersRepo.GetUserByEmail(email) - if userErr != nil || claUser == nil { + userSearch, userErr := repo.usersRepo.SearchUsers("user_emails", email, false) + if userErr != nil || userSearch == nil { log.WithFields(f).Debugf("error getting user by email: %s ", email) return } @@ -2109,21 +2109,41 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.ECLAs = signs.Signatures } - if claUser != nil { - icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) - if iclaErr != nil || icla == nil { - log.WithFields(f).Debugf("unable to get icla signature for user: '%s'", email) - } - if icla != nil { - // Convert to IclSignature instance to leverage invalidateSignatures helper function - approvalList.ICLAs = []*models.IclaSignature{{ - GithubUsername: icla.UserGHUsername, - LfUsername: icla.UserLFID, - SignatureID: icla.SignatureID, - }} + if len(userSearch.Users) > 0 { + // Try and grab iclaSignature records for users + results := make(chan *ICLAUserResponse, len(userSearch.Users)) + go func() { + defer close(results) + for _, user := range userSearch.Users { + icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, user.UserID) + if iclaErr != nil || icla == nil { + results <- &ICLAUserResponse{ + Error: fmt.Errorf("unable to get icla for user: %s ", user.UserID), + } + } else { + results <- &ICLAUserResponse{ + ICLASignature: &models.IclaSignature{ + GithubUsername: icla.UserGHUsername, + LfUsername: icla.UserLFID, + SignatureID: icla.SignatureID, + }, + } + } + } + }() + + for result := range results { + if result.Error == nil { + log.WithFields(f).Debug("processing icla...") + approvalList.ICLAs = append(approvalList.ICLAs, result.ICLASignature) + } } + } + // Invalidate signatures + repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) + //update gerrit permissions gerritUser, getGerritUserErr := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) if getGerritUserErr != nil || gerritUser == nil { @@ -3146,7 +3166,7 @@ func (repo repository) getGerritUsers(ctx context.Context, authUser *auth.User, } log.WithFields(f).Debugf("querying gerrit for %s gerrit users...", claType) gerritIclaUsers, getGerritQueryErr := repo.gerritService.GetUsersOfGroup(ctx, authUser, projectSFID, claType) - if getGerritQueryErr != nil { + if getGerritQueryErr != nil || gerritIclaUsers == nil { msg := fmt.Sprintf("unable to fetch gerrit users for claGroup: %s , claType: %s ", projectSFID, claType) log.WithFields(f).WithError(getGerritQueryErr).Warn(msg) gerritResultChannel <- &GerritUserResponse{ From 5221e5aba50bc6854448ae1aafeb6d329ba18e1f Mon Sep 17 00:00:00 2001 From: David Deal Date: Sat, 10 Apr 2021 11:34:34 -0700 Subject: [PATCH 0225/1276] Added/Updated Logging for GitHub/Repository APIs (#2865) Signed-off-by: David Deal --- .../github_organizations/helpers.go | 6 ++-- cla-backend-go/repositories/repository.go | 28 ++++++++--------- .../utils/utils_user_auth_lambda.go | 2 +- .../utils/utils_user_auth_standalone.go | 4 +-- cla-backend-go/v2/repositories/handlers.go | 1 + cla-backend-go/v2/repositories/service.go | 30 +++++++++++-------- 6 files changed, 39 insertions(+), 32 deletions(-) diff --git a/cla-backend-go/github_organizations/helpers.go b/cla-backend-go/github_organizations/helpers.go index 94ae540ce..29906cce9 100644 --- a/cla-backend-go/github_organizations/helpers.go +++ b/cla-backend-go/github_organizations/helpers.go @@ -34,7 +34,7 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations go func(ghorg *models.GithubOrganization) { defer wg.Done() ghorg.GithubInfo = &models.GithubOrganizationGithubInfo{} - log.WithFields(f).Debugf("Loading GitHub organization details: %s...", ghorg.OrganizationName) + log.WithFields(f).Debugf("loading GitHub organization details: %s...", ghorg.OrganizationName) user, err := github.GetUserDetails(ghorg.OrganizationName) if err != nil { ghorg.GithubInfo.Error = err.Error() @@ -59,7 +59,7 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations } if ghorg.OrganizationInstallationID != 0 { - log.WithFields(f).Debugf("Loading GitHub repository list directly from GitHub based on the installation id: %d...", ghorg.OrganizationInstallationID) + log.WithFields(f).Debugf("loading GitHub repository list directly from GitHub based on the installation id: %d...", ghorg.OrganizationInstallationID) list, err := github.GetInstallationRepositories(ctx, ghorg.OrganizationInstallationID) if err != nil { log.WithFields(f).Warnf("unable to get repositories from GitHub for the installation id: %d", ghorg.OrganizationInstallationID) @@ -67,7 +67,7 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations return } - log.WithFields(f).Debugf("Found %d repositories from GitHUb using the installation id: %d...", + log.WithFields(f).Debugf("found %d repositories from GitHUb using the installation id: %d...", len(list), ghorg.OrganizationInstallationID) for _, repoInfo := range list { ghorg.Repositories.List = append(ghorg.Repositories.List, &models.GithubRepositoryInfo{ diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 3c192a5c3..beaeeb1b6 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -85,7 +85,7 @@ type repo struct { // AddGithubRepository adds the specified repository func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.AddGitHubRepository", + "functionName": "v1.repositories.repository.AddGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "externalProjectID": externalProjectID, "projectSFID": projectSFID, @@ -158,7 +158,7 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, note := input.Note f := logrus.Fields{ - "functionName": "repositories.repository.UpdateGitHubRepository", + "functionName": "v1.repositories.repository.UpdateGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "externalProjectID": externalID, @@ -331,7 +331,7 @@ func (r repo) DisableRepositoriesOfGithubOrganization(ctx context.Context, exter // GetRepository by repository id func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.GetRepository", + "functionName": "v1.repositories.repository.GetRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, } @@ -368,7 +368,7 @@ func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models. // GetRepositoryByName fetches the repository by repository name func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.GetRepositoryByName", + "functionName": "v1.repositories.repository.GetRepositoryByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryName": repositoryName, } @@ -399,7 +399,7 @@ func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) ( } if len(results.Items) == 0 { - log.WithFields(f).Warn("no repositories found with repository name") + log.WithFields(f).Warnf("no repositories found with repository name: %s", repositoryName) return nil, &utils.GitHubRepositoryNotFound{ RepositoryName: repositoryName, } @@ -422,7 +422,7 @@ func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) ( // GetRepositoryByCLAGroup gets the list of repositories based on the CLA Group ID func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.GetRepositoryByCLAGroup", + "functionName": "v1.repositories.repository.GetRepositoryByCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "enabled": enabled, @@ -473,7 +473,7 @@ func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, func (r *repo) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.GetRepositoriesByOrganizationName", + "functionName": "v1.repositories.repository.GetRepositoriesByOrganizationName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitHubOrgName": gitHubOrgName, } @@ -547,7 +547,7 @@ func (r repo) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID // List github repositories of project by external/salesforce project id func (r repo) ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { f := logrus.Fields{ - "functionName": "repositories.repository.ListProjectRepositories", + "functionName": "v1.repositories.repository.ListProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "enabled": utils.BoolValue(enabled), @@ -601,7 +601,7 @@ func (r repo) ListProjectRepositories(ctx context.Context, projectSFID string, e // getProjectRepositories returns an array of GH repositories for the specified project ID func (r repo) getProjectRepositories(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.getProjectRepositories", + "functionName": "v1.repositories.repository.getProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectID": projectID, "enabled": enabled, @@ -649,7 +649,7 @@ func (r repo) getProjectRepositories(ctx context.Context, projectID string, enab // getRepositoriesByGithubOrg returns an array of GH repositories for the specified project ID func (r repo) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName string) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.getRepositoriesByGitHubOrg", + "functionName": "v1.repositories.repository.getRepositoriesByGitHubOrg", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "githubOrgName": githubOrgName, } @@ -694,7 +694,7 @@ func (r repo) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName stri // GetRepositoryByGithubID fetches the repository model by its external github id func (r repo) GetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.repository.GetRepositoryByGitHubID", + "functionName": "v1.repositories.repository.GetRepositoryByGitHubID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "externalID": externalID, "enabled": enabled, @@ -757,7 +757,7 @@ func (r repo) disableGithubRepository(ctx context.Context, repositoryID string) // setEnabledGithubRepository updates the existing repository record by setting the enabled flag to false func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID string, enabled bool) error { f := logrus.Fields{ - "functionName": "repositories.repository.setEnabledGitHubRepository", + "functionName": "v1.repositories.repository.setEnabledGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "enabled": enabled, @@ -827,7 +827,7 @@ func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID strin // setEnabledGithubRepositoryWithCLAGroupID updates the existing repository record by setting the enabled flag to false func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string, enabled bool) error { f := logrus.Fields{ - "functionName": "repositories.repository.setEnabledGitHubRepositoryWithCLAGroupID", + "functionName": "v1.repositories.repository.setEnabledGitHubRepositoryWithCLAGroupID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "claGroupID": claGroupID, @@ -902,7 +902,7 @@ func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repo // setEnabledGithubRepository updates the existing repository record by setting the enabled flag to false func (r repo) setClaGroupIDGithubRepository(ctx context.Context, repositoryID, claGroupID string) error { f := logrus.Fields{ - "functionName": "repositories.repository.setClaGroupIDGitHubRepository", + "functionName": "v1.repositories.repository.setClaGroupIDGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, "claGroupID": claGroupID, diff --git a/cla-backend-go/utils/utils_user_auth_lambda.go b/cla-backend-go/utils/utils_user_auth_lambda.go index 8e8570386..9b0ccee84 100644 --- a/cla-backend-go/utils/utils_user_auth_lambda.go +++ b/cla-backend-go/utils/utils_user_auth_lambda.go @@ -54,7 +54,7 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec log.WithFields(f).Debug("checking scope...") val := user.IsUserAuthorized(auth.Project, projectSFID, true) - log.WithFields(f).Debugf("user allowed: %T", val) + log.WithFields(f).Debugf("user allowed: %t", val) return val } diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index 09820aebf..b29e6a413 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -83,7 +83,7 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec log.WithFields(f).Debug("checking scope...") val := user.IsUserAuthorized(auth.Project, projectSFID, true) - log.WithFields(f).Debugf("user allowed: %T", val) + log.WithFields(f).Debugf("user allowed: %t", val) return val } @@ -111,7 +111,7 @@ func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFI log.WithFields(f).Debug("checking scope...") val := user.IsUserAuthorizedForProjectScope(projectSFID) - log.WithFields(f).Debugf("user allowed: %T", val) + log.WithFields(f).Debugf("user allowed: %t", val) return val } diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 9ca7c850e..0d1800f69 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -113,6 +113,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequest(reqID, msg)) } + log.WithFields(f).Debugf("Adding GitHub repositories for project: %s", params.ProjectSFID) results, err := service.AddGithubRepositories(ctx, params.ProjectSFID, params.GithubRepositoryInput) if err != nil { if _, ok := err.(*utils.GitHubRepositoryExists); ok { diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 92bc2b48c..ce5e73db2 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -72,7 +72,7 @@ func NewService(repo v1Repositories.Repository, pcgRepo projects_cla_groups.Repo func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "AddGithubRepositories", + "functionName": "v2.repositories.service.AddGithubRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "claGroupID": utils.StringValue(input.ClaGroupID), @@ -81,6 +81,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, "repositoryGithubIds": input.RepositoryGithubIds, } + log.WithFields(f).Debugf("loading project by SFID: %s", projectSFID) psc := v2ProjectService.GetClient() project, err := psc.GetProject(projectSFID) if err != nil { @@ -88,12 +89,12 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, return nil, err } - var externalProjectID string + var parentProjectSFID string if project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { - externalProjectID = projectSFID + parentProjectSFID = projectSFID } else { - externalProjectID = project.Parent + parentProjectSFID = project.Parent } allMappings, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(aws.StringValue(input.ClaGroupID)) @@ -111,6 +112,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, if !valid { return nil, fmt.Errorf("provided cla group id %s is not linked to project sfid %s", utils.StringValue(input.ClaGroupID), projectSFID) } + org, err := s.ghOrgRepo.GetGithubOrganizationByName(ctx, utils.StringValue(input.GithubOrganizationName)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to get organization by name") @@ -147,6 +149,9 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, log.WithFields(f).WithError(err).Warnf("unable to load repository by external ID: %d", repoGithubID) return nil, err } + f["repositoryName"] = ghRepo.FullName + f["repositoryURL"] = ghRepo.URL + f["repositoryGitHubID"] = repoGithubID log.WithFields(f).Debugf("loaded GitHub repository by external id: %d - url: %s", repoGithubID, utils.StringValue(ghRepo.URL)) // Check if this repository exists in our database @@ -206,8 +211,9 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, RepositoryURL: ghRepo.HTMLURL, } - addedModel, addErr := s.repo.AddGithubRepository(ctx, externalProjectID, projectSFID, in) + addedModel, addErr := s.repo.AddGithubRepository(ctx, parentProjectSFID, projectSFID, in) if addErr != nil { + log.WithFields(f).WithError(addErr).Warnf("unable to add github repository: %s for project: %s", *ghRepo.FullName, projectSFID) return nil, addErr } @@ -229,7 +235,7 @@ func (s *service) DisableRepository(ctx context.Context, repositoryID string) er func (s *service) ListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.ListGithubRepositories, error) { f := logrus.Fields{ - "functionName": "ListProjectRepositories", + "functionName": "v2.repositories.service.ListProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -348,7 +354,7 @@ func (s *service) GetRepositoryByName(ctx context.Context, repositoryName string func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) { f := logrus.Fields{ - "functionName": "repositories.service.GetProtectedBranch", + "functionName": "v2.repositories.service.GetProtectedBranch", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "repositoryID": repositoryID, @@ -402,7 +408,7 @@ func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositor func (s *service) UpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) { f := logrus.Fields{ - "functionName": "repositories.service.UpdateProtectedBranch", + "functionName": "v2.repositories.service.UpdateProtectedBranch", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "repositoryID": repositoryID, @@ -477,7 +483,7 @@ func (s *service) UpdateProtectedBranch(ctx context.Context, projectSFID, reposi func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID string) (*v1Models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "repositories.service.getGitHubRepo", + "functionName": "v2.repositories.service.getGitHubRepo", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "repositoryID": repositoryID, @@ -506,7 +512,7 @@ func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID s func (s *service) getBranchProtectionRepositoryForOrgName(ctx context.Context, githubOrgName string) (*branch_protection.BranchProtectionRepository, error) { f := logrus.Fields{ - "functionName": "repositories.service.getGitHubClientForOrgName", + "functionName": "v2.repositories.service.getGitHubClientForOrgName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "githubOrgName": githubOrgName, } @@ -541,7 +547,7 @@ func (s *service) getGithubOwner(ctx context.Context, branchProtectionRepository // getRequiredProtectedBranchCheckStatus func (s *service) getRequiredProtectedBranchCheckStatus(branchProtectionRule *branch_protection.BranchProtectionRule, requiredChecks []string) []*v2Models.GithubRepositoryBranchProtectionStatusChecks { f := logrus.Fields{ - "functionName": "repositories.service.getRequiredProtectedBranchCheckStatus", + "functionName": "v2.repositories.service.getRequiredProtectedBranchCheckStatus", } log.WithFields(f).Debug("querying GitHub for status checks...") @@ -575,7 +581,7 @@ func (s *service) getRequiredProtectedBranchCheckStatus(branchProtectionRule *br func (s *service) DisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { f := logrus.Fields{ - "functionName": "DisableCLAGroupRepositories", + "functionName": "v2.repositories.service.DisableCLAGroupRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, } From ea3627c42f9358c8e3cef4db798ca134b75794ed Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 12 Apr 2021 20:28:44 +0300 Subject: [PATCH 0226/1276] make default pageSize=100 for icla signature listing (#2867) Signed-off-by: makkalot --- cla-backend-go/signatures/repository.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index a8c9158b6..9e9d563bf 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -52,8 +52,8 @@ const ( SignatureReferenceSearchIndex = "reference-signature-search-index" HugePageSize = 10000 - DefaultPageSize = 10 - BigPageSize = 100 + DefaultPageSize = 100 + BigPageSize = 200 ) // SignatureRepository interface defines the functions for the github whitelist service From 5705a35e40f5b87ac02c4be814df234ba7a8fb7d Mon Sep 17 00:00:00 2001 From: makkalot Date: Tue, 13 Apr 2021 18:38:05 +0300 Subject: [PATCH 0227/1276] adding extra checks in case the old values are still passed Signed-off-by: makkalot --- cla-backend-go/events/event_data.go | 8 ++--- cla-backend-go/events/event_data_test.go | 38 ++++++++++++++++++++++++ cla-backend-go/v2/cla_groups/handlers.go | 7 +++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 2a770422e..ca0da3437 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -681,12 +681,12 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s var nameUpdated bool data := fmt.Sprintf("CLA Group ID: %s was updated by: %s", args.ProjectID, args.UserName) - if ed.NewClaGroupName != "" { + if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName{ data = fmt.Sprintf("%s with Name from : %s to : %s", data, ed.OldClaGroupName, ed.NewClaGroupName) nameUpdated = true } - if ed.NewClaGroupDescription != "" { + if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription{ if nameUpdated { data = data + "," } else { @@ -1477,12 +1477,12 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s var nameUpdated, descriptionUpdated bool message := "The CLA Group" - if ed.NewClaGroupName != "" { + if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName{ message = message + " name was updated to : " + ed.NewClaGroupName nameUpdated = true } - if ed.NewClaGroupDescription != "" { + if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription{ descriptionUpdated = true if nameUpdated { message = message + " and the description was updated to : " + ed.NewClaGroupDescription diff --git a/cla-backend-go/events/event_data_test.go b/cla-backend-go/events/event_data_test.go index 76c525572..e594bb0a0 100644 --- a/cla-backend-go/events/event_data_test.go +++ b/cla-backend-go/events/event_data_test.go @@ -32,6 +32,15 @@ func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { }, summaryStr: "The CLA Group name was updated to : updatedNameValue by the user john.", }, + { + name: "only name updated but old description still passed", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupName: "updatedNameValue", + NewClaGroupDescription: "oldDescriptionValue", + OldClaGroupDescription: "oldDescriptionValue", + }, + summaryStr: "The CLA Group name was updated to : updatedNameValue by the user john.", + }, { name: "only description updated", eventData: &CLAGroupUpdatedEventData{ @@ -39,6 +48,15 @@ func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { }, summaryStr: "The CLA Group description was updated to : updatedDescriptionValue by the user john.", }, + { + name: "only description updated but old name still passed", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupDescription: "updatedDescriptionValue", + NewClaGroupName: "oldNameValue", + OldClaGroupName: "oldNameValue", + }, + summaryStr: "The CLA Group description was updated to : updatedDescriptionValue by the user john.", + }, { name: "name and description updated", eventData: &CLAGroupUpdatedEventData{ @@ -78,6 +96,16 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { }, detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue.", }, + { + name: "only name updated but old description still passed", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupName: "updatedNameValue", + OldClaGroupName: "oldNameValue", + NewClaGroupDescription: "oldDescriptionValue", + OldClaGroupDescription: "oldDescriptionValue", + }, + detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue.", + }, { name: "only description updated", eventData: &CLAGroupUpdatedEventData{ @@ -86,6 +114,16 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { }, detailStr: "CLA Group ID: projectIDValue was updated by: john with Description from : oldDescriptionValue to : updatedDescriptionValue.", }, + { + name: "only description updated but old name still passed", + eventData: &CLAGroupUpdatedEventData{ + NewClaGroupDescription: "updatedDescriptionValue", + OldClaGroupDescription: "oldDescriptionValue", + NewClaGroupName: "oldNameValue", + OldClaGroupName: "oldNameValue", + }, + detailStr: "CLA Group ID: projectIDValue was updated by: john with Description from : oldDescriptionValue to : updatedDescriptionValue.", + }, { name: "name and description updated", eventData: &CLAGroupUpdatedEventData{ diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index e1bcf5dde..801e5ab2d 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -121,6 +121,13 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to lookup CLA Group by ID: %s", params.ClaGroupID), err)) } + // check if there's any change at all + if params.Body.ClaGroupName == claGroupModel.ProjectName && params.Body.ClaGroupDescription == claGroupModel.ProjectDescription { + log.WithFields(f).Warn("no new values passed, nothing to change, aborting.") + return cla_group.NewUpdateClaGroupBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequest(reqID, "no new values passed, nothing to change, aborting.")) + } + projectCLAGroupModels, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) if projectCLAGroupErr != nil { msg := fmt.Sprintf("unable to load the Project to CLA Group mappings for CLA Group: %s - is this CLA Group configured?", params.ClaGroupID) From 0dd618f8139d04d33dc13060fa0dda8823cf4fa0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 14 Apr 2021 17:35:10 -0700 Subject: [PATCH 0228/1276] Added Additional Debugging for User Permissions Issues (#2874) --- cla-backend-go/events/event_data.go | 8 +- cla-backend-go/v2/project/handlers.go | 112 ++++++++------- cla-backend-go/v2/signatures/handlers.go | 173 +++++++++++++---------- 3 files changed, 165 insertions(+), 128 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index ca0da3437..a99ffb2e3 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -681,12 +681,12 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s var nameUpdated bool data := fmt.Sprintf("CLA Group ID: %s was updated by: %s", args.ProjectID, args.UserName) - if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName{ + if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName { data = fmt.Sprintf("%s with Name from : %s to : %s", data, ed.OldClaGroupName, ed.NewClaGroupName) nameUpdated = true } - if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription{ + if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription { if nameUpdated { data = data + "," } else { @@ -1477,12 +1477,12 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s var nameUpdated, descriptionUpdated bool message := "The CLA Group" - if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName{ + if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName { message = message + " name was updated to : " + ed.NewClaGroupName nameUpdated = true } - if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription{ + if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription { descriptionUpdated = true if nameUpdated { message = message + " and the description was updated to : " + ed.NewClaGroupDescription diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 4bc2ba68d..a02d48683 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -31,9 +31,11 @@ import ( // Configure establishes the middleware handlers for the project service func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service Service, eventsService events.Service) { //nolint // Get Projects - api.ProjectGetProjectsHandler = project.GetProjectsHandlerFunc(func(params project.GetProjectsParams, user *auth.User) middleware.Responder { + api.ProjectGetProjectsHandler = project.GetProjectsHandlerFunc(func(params project.GetProjectsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + // No auth checks - anyone can request the list of projects projects, err := service.GetCLAGroups(ctx, &v1ProjectOps.GetProjectsParams{ HTTPRequest: params.HTTPRequest, @@ -56,10 +58,18 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service }) // Get Project By ID - api.ProjectGetProjectByIDHandler = project.GetProjectByIDHandlerFunc(func(params project.GetProjectByIDParams, user *auth.User) middleware.Responder { + api.ProjectGetProjectByIDHandler = project.GetProjectByIDHandlerFunc(func(params project.GetProjectByIDParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.project.handlers.ProjectGetProjectByIDHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSfdcID, + "userEmail": authUser.Email, + "userName": authUser.UserName, + } + claGroupModel, err := service.GetCLAGroupByID(ctx, params.ProjectSfdcID) if err != nil { @@ -73,34 +83,39 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewGetProjectByIDNotFound().WithXRequestID(reqID) } - if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { - return project.NewGetProjectByIDForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Project By ID with Project scope of %s", - user.UserName, claGroupModel.ProjectExternalID), - XRequestID: reqID, - }) + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user '%s' does not have access to Get Project By ID with Project scope of %s", + authUser.UserName, claGroupModel.ProjectExternalID) + return project.NewGetProjectByIDForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } result, err := v2ProjectModel(claGroupModel) if err != nil { - return project.NewGetProjectByIDInternalServerError().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) + msg := fmt.Sprintf("unable to convert CLA Group '%s' with ID: '%s' to a response model", claGroupModel.ProjectName, claGroupModel.ProjectID) + log.WithFields(f).WithError(err).Warn(msg) + return project.NewGetProjectByIDInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) } return project.NewGetProjectByIDOK().WithXRequestID(reqID).WithPayload(result) }) - api.ProjectGetProjectsByExternalIDHandler = project.GetProjectsByExternalIDHandlerFunc(func(params project.GetProjectsByExternalIDParams, user *auth.User) middleware.Responder { + api.ProjectGetProjectsByExternalIDHandler = project.GetProjectsByExternalIDHandlerFunc(func(params project.GetProjectsByExternalIDParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForProjectTree(ctx, user, params.ExternalID, utils.ALLOW_ADMIN_SCOPE) { - return project.NewGetProjectsByExternalIDForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Projects By External ID with Project scope of %s", - user.UserName, params.ExternalID), - XRequestID: reqID, - }) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.project.handlers.ProjectGetProjectsByExternalIDHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "externalID": params.ExternalID, + "userEmail": authUser.Email, + "userName": authUser.UserName, + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user '%s' does not have access to Get Projects By External ID with Project scope of '%s'", + authUser.UserName, params.ExternalID) + log.WithFields(f).Debug(msg) + return project.NewGetProjectsByExternalIDForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } claGroupModel, err := service.GetCLAGroupsByExternalID(ctx, &v1ProjectOps.GetProjectsByExternalIDParams{ @@ -119,20 +134,25 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewGetProjectsByExternalIDInternalServerError().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } if results.Projects == nil { - return project.NewGetProjectsByExternalIDNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "404", - Message: fmt.Sprintf("project not found with id. [%s]", params.ExternalID), - XRequestID: reqID, - }) + msg := fmt.Sprintf("project not found with id: '%s]", params.ExternalID) + log.WithFields(f).Debug(msg) + return project.NewGetProjectsByExternalIDNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) } return project.NewGetProjectsByExternalIDOK().WithXRequestID(reqID).WithPayload(results) }) // Get Project By Name - api.ProjectGetProjectByNameHandler = project.GetProjectByNameHandlerFunc(func(params project.GetProjectByNameParams, user *auth.User) middleware.Responder { + api.ProjectGetProjectByNameHandler = project.GetProjectByNameHandlerFunc(func(params project.GetProjectByNameParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.project.handlers.ProjectGetProjectByNameHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectName": params.ProjectName, + "userEmail": authUser.Email, + "userName": authUser.UserName, + } claGroupModel, err := service.GetCLAGroupByName(ctx, params.ProjectName) if err != nil { @@ -142,13 +162,11 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewGetProjectByNameNotFound().WithXRequestID(reqID) } - if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { - return project.NewGetProjectByNameForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Get Project By Name with Project scope of %s", - user.UserName, claGroupModel.ProjectExternalID), - XRequestID: reqID, - }) + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user '%s' does not have access to Get Projects By Name with Project scope of '%s'", + authUser.UserName, claGroupModel.ProjectExternalID) + log.WithFields(f).Debug(msg) + return project.NewGetProjectByNameForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } result, err := v2ProjectModel(claGroupModel) @@ -159,18 +177,18 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service }) // Delete Project By ID - api.ProjectDeleteProjectByIDHandler = project.DeleteProjectByIDHandlerFunc(func(params project.DeleteProjectByIDParams, user *auth.User) middleware.Responder { + api.ProjectDeleteProjectByIDHandler = project.DeleteProjectByIDHandlerFunc(func(params project.DeleteProjectByIDParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "ProjectDeleteProjectByIDHandler", + "functionName": "v2.project.handlers.ProjectDeleteProjectByIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSfdcID, - "userEmail": user.Email, - "userName": user.UserName, + "userEmail": authUser.Email, + "userName": authUser.UserName, } log.WithFields(f).Debug("Processing delete request") - utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) claGroupModel, err := service.GetCLAGroupByID(ctx, params.ProjectSfdcID) if err != nil { if err == ErrCLAGroupDoesNotExist { @@ -179,13 +197,11 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service return project.NewDeleteProjectByIDBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } - if !utils.IsUserAuthorizedForProjectTree(ctx, user, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { - return project.NewDeleteProjectByIDForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Delete Project By ID with Project scope of %s", - user.UserName, claGroupModel.ProjectExternalID), - XRequestID: reqID, - }) + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, claGroupModel.ProjectExternalID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user '%s' does not have access to Delete Project By ID with Project scope of %s", + authUser.UserName, claGroupModel.ProjectExternalID) + log.WithFields(f).Debug(msg) + return project.NewDeleteProjectByIDForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } err = service.DeleteCLAGroup(ctx, params.ProjectSfdcID) @@ -198,7 +214,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLAGroupDeleted, ClaGroupModel: claGroupModel, - LfUsername: user.UserName, + LfUsername: authUser.UserName, EventData: &events.CLAGroupDeletedEventData{}, }) @@ -283,7 +299,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "ProjectGetSFProjectInfoByIDHandler", + "functionName": "v2.project.handlers.ProjectGetSFProjectInfoByIDHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "userEmail": user.Email, diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 2ee9830b7..3364d6c09 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -50,8 +50,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesGetSignatureHandler = signatures.GetSignatureHandlerFunc(func(params signatures.GetSignatureParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetGitHubOrgWhitelistHandler", + "functionName": "v2.signatures.handlers.SignaturesGetGitHubOrgWhitelistHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": params.SignatureID, } @@ -95,7 +96,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesUpdateApprovalListHandler", + "functionName": "v2.signatures.handlers.SignaturesUpdateApprovalListHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "projectSFID": params.ProjectSFID, @@ -120,7 +121,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj // Must be in the Project|Organization Scope to see this - signature ACL is double-checked in the service level when the signature is loaded if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, companyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to update Project Company Approval List with Project|Organization scope of %s | %s", + msg := fmt.Sprintf("user '%s' does not have access to update Project Company Approval List with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) return signatures.NewUpdateApprovalListForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -189,8 +190,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesGetGitHubOrgWhitelistHandler = signatures.GetGitHubOrgWhitelistHandlerFunc(func(params signatures.GetGitHubOrgWhitelistParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetGitHubOrgWhitelistHandler", + "functionName": "v2.signatures.handlers.SignaturesGetGitHubOrgWhitelistHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": params.SignatureID, } @@ -226,8 +228,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesAddGitHubOrgWhitelistHandler = signatures.AddGitHubOrgWhitelistHandlerFunc(func(params signatures.AddGitHubOrgWhitelistParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesAddGitHubOrgWhitelistHandler", + "functionName": "v2.signatures.handlers.SignaturesAddGitHubOrgWhitelistHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": params.SignatureID, } @@ -294,8 +297,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesDeleteGitHubOrgWhitelistHandler = signatures.DeleteGitHubOrgWhitelistHandlerFunc(func(params signatures.DeleteGitHubOrgWhitelistParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesDeleteGitHubOrgWhitelistHandler", + "functionName": "v2.signatures.handlers.SignaturesDeleteGitHubOrgWhitelistHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": params.SignatureID, } @@ -359,8 +363,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesGetProjectSignaturesHandler = signatures.GetProjectSignaturesHandlerFunc(func(params signatures.GetProjectSignaturesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetProjectSignaturesHandler", + "functionName": "v2.signatures.handlers.SignaturesGetProjectSignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "signatureType": params.SignatureType, @@ -396,7 +401,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj if false { log.WithFields(f).Debug("checking access control permissions for user...") if !isUserHaveAccessToCLAGroupProjects(ctx, authUser, params.ClaGroupID, projectClaGroupsRepo, projectRepo) { - msg := fmt.Sprintf("user %s is not authorized to view project ICLA signatures any scope of project", authUser.UserName) + msg := fmt.Sprintf("user '%s' is not authorized to view project ICLA signatures any scope of project", authUser.UserName) log.Warn(msg) return signatures.NewGetProjectSignaturesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -438,8 +443,10 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesGetProjectCompanySignaturesHandler = signatures.GetProjectCompanySignaturesHandlerFunc(func(params signatures.GetProjectCompanySignaturesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetProjectCompanySignaturesHandler", + "functionName": "v2.signatures.handlers.SignaturesGetProjectCompanySignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, @@ -461,16 +468,11 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj }) } - // Must be in the one of the above scopes to see this - // - if project scope (like a PM) - // - if project|organization scope (like CLA Manager, CLA Signatory) - // - if organization scope (like company admin) if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { msg := fmt.Sprintf("user %s is not authorized to view project company signatures any scope of project: %s, organization %s", authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) - return signatures.NewGetProjectCompanySignaturesForbidden().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseForbidden(reqID, msg)) + return signatures.NewGetProjectCompanySignaturesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } log.WithFields(f).Debug("loading project company signatures...") @@ -489,8 +491,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesGetProjectCompanyEmployeeSignaturesHandler = signatures.GetProjectCompanyEmployeeSignaturesHandlerFunc(func(params signatures.GetProjectCompanyEmployeeSignaturesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetProjectCompanyEmployeeSignaturesHandler", + "functionName": "v2.signatures.handlers.SignaturesGetProjectCompanyEmployeeSignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, @@ -500,33 +503,25 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj companyModel, err := companyService.GetCompany(ctx, params.CompanyID) if err != nil { - msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) + msg := fmt.Sprintf("user lookup for company by ID: '%s' failed : %v", params.CompanyID, err) log.Warn(msg) if _, ok := err.(*utils.CompanyNotFound); ok { - return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 404 Not Found - error getting company - " + msg, - Code: "404", - }) + return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, - Code: "400", - }) + return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } if companyModel == nil { msg := fmt.Sprintf("problem loading company by ID: %s", params.CompanyID) log.WithFields(f).WithError(err).Warn(msg) - return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFound(reqID, msg)) + return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) } log.WithFields(f).Debug("checking access control permissions...") if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, params.ProjectSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { - msg := fmt.Sprintf("user %s is not authorized to view project company signatures any scope of project: %s, organization %s", + msg := fmt.Sprintf("user '%s' is not authorized to view project company signatures any scope of project or project|organization for project: '%s', organization '%s'", authUser.UserName, params.ProjectSFID, params.CompanyID) log.Warn(msg) - return signatures.NewGetProjectCompanyEmployeeSignaturesForbidden().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseForbidden(reqID, msg)) + return signatures.NewGetProjectCompanyEmployeeSignaturesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } // Locate the CLA Group for the provided project SFID @@ -576,7 +571,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetCompanySignaturesHandler", + "functionName": "v2.signatures.handlers.SignaturesGetCompanySignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": params.CompanyID, "companyName": aws.StringValue(params.CompanyName), @@ -663,8 +658,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesGetUserSignaturesHandler = signatures.GetUserSignaturesHandlerFunc(func(params signatures.GetUserSignaturesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetUserSignaturesHandler", + "functionName": "v2.signatures.handlers.SignaturesGetUserSignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "userID": params.UserID, "userName": aws.StringValue(params.UserName), @@ -702,8 +698,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesDownloadProjectSignatureEmployeeAsCSVHandler = signatures.DownloadProjectSignatureEmployeeAsCSVHandlerFunc(func(params signatures.DownloadProjectSignatureEmployeeAsCSVParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesDownloadProjectSignatureEmployeeAsCSVHandler", + "functionName": "v2.signatures.handlers.SignaturesDownloadProjectSignatureEmployeeAsCSVHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "companyID": params.CompanyID, @@ -757,10 +754,20 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj }) } + // Lookup the Project to CLA Group mapping table entries - this will have the correct details + projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + // Should have at least one entry if we're setup correctly - it will have the foundation (parent project/project group) and project details set + if projectCLAGroupErr != nil || len(projectCLAGroupEntries) == 0 { + msg := fmt.Sprintf("unable to load project CLA Group mappings for CLA Group: %s - has this project been migrated to v2?", params.ClaGroupID) + log.WithFields(f).Warn(msg) + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + } + // All the records will point to the same parent SFID + f["foundationSFID"] = projectCLAGroupEntries[0].FoundationSFID + log.WithFields(f).Debug("checking access control permissions for user...") - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, claGroupModel.FoundationSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { - msg := fmt.Sprintf(" user %s is not authorized to view project employee signatures any scope of project", - authUser.UserName) + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, projectCLAGroupEntries[0].FoundationSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { + msg := fmt.Sprintf(" user %s is not authorized to view project employee signatures any scope of project", authUser.UserName) log.Warn(msg) return signatures.NewDownloadProjectSignatureEmployeeAsCSVForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -799,8 +806,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesListClaGroupIclaSignatureHandler = signatures.ListClaGroupIclaSignatureHandlerFunc(func(params signatures.ListClaGroupIclaSignatureParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesListClaGroupIclaSignatureHandler", + "functionName": "v2.signatures.handlers.SignaturesListClaGroupIclaSignatureHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "searchTerm": utils.StringValue(params.SearchTerm), @@ -867,8 +875,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesListClaGroupCorporateContributorsHandler = signatures.ListClaGroupCorporateContributorsHandlerFunc(func(params signatures.ListClaGroupCorporateContributorsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesListClaGroupCorporateContributorsHandler", + "functionName": "v2.signatures.handlers.SignaturesListClaGroupCorporateContributorsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "companyID": params.CompanyID, @@ -919,14 +928,22 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj return signatures.NewListClaGroupCorporateContributorsOK().WithXRequestID(reqID).WithPayload(&models.CorporateContributorList{ List: []*models.CorporateContributor{}, // empty list }) - //return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload( - // utils.ErrorResponseBadRequest(reqID, msg)) } - f["foundationSFID"] = claGroupModel.FoundationSFID + + // Lookup the Project to CLA Group mapping table entries - this will have the correct details + projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + // Should have at least one entry if we're setup correctly - it will have the foundation (parent project/project group) and project details set + if projectCLAGroupErr != nil || len(projectCLAGroupEntries) == 0 { + msg := fmt.Sprintf("unable to load project CLA Group mappings for CLA Group: %s - has this project been migrated to v2?", params.ClaGroupID) + log.WithFields(f).Warn(msg) + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + } + // All the records will point to the same parent SFID + f["foundationSFID"] = projectCLAGroupEntries[0].FoundationSFID log.WithFields(f).Debug("checking access control permissions for user...") - if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, claGroupModel.FoundationSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { - msg := fmt.Sprintf("user %s is not authorized to view project CCLA signatures any scope of project or project|organization scope with company ID: %s", + if !isUserHaveAccessToCLAProjectOrganization(ctx, authUser, projectCLAGroupEntries[0].FoundationSFID, companyModel.CompanyExternalID, projectClaGroupsRepo) { + msg := fmt.Sprintf("user '%s' is not authorized to view project CCLA signatures project scope or project|organization scope for company ID: %s", authUser.UserName, companyModel.CompanyID) log.Warn(msg) return signatures.NewListClaGroupCorporateContributorsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -955,7 +972,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesGetSignatureSignedDocumentHandler", + "functionName": "v2.signatures.handlers.SignaturesGetSignatureSignedDocumentHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": params.SignatureID, } @@ -998,8 +1015,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesDownloadProjectSignatureICLAsHandler = signatures.DownloadProjectSignatureICLAsHandlerFunc(func(params signatures.DownloadProjectSignatureICLAsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesDownloadProjectSignatureICLAsHandler", + "functionName": "v2.signatures.handlers.SignaturesDownloadProjectSignatureICLAsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, } @@ -1052,8 +1070,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesDownloadProjectSignatureICLAAsCSVHandler = signatures.DownloadProjectSignatureICLAAsCSVHandlerFunc(func(params signatures.DownloadProjectSignatureICLAAsCSVParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesDownloadProjectSignatureICLAAsCSVHandler", + "functionName": "v2.signatures.handlers.SignaturesDownloadProjectSignatureICLAAsCSVHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, } @@ -1089,7 +1108,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj log.WithFields(f).Debug("checking access control permissions for user...") if !isUserHaveAccessToCLAGroupProjects(ctx, authUser, params.ClaGroupID, projectClaGroupsRepo, projectRepo) { - msg := fmt.Sprintf("user %s is not authorized to view project ICLA signatures any scope of project", authUser.UserName) + msg := fmt.Sprintf("user '%s' is not authorized to view project ICLA signatures any scope of project", authUser.UserName) log.Warn(msg) return signatures.NewDownloadProjectSignatureICLAAsCSVForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -1119,8 +1138,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesDownloadProjectSignatureCCLAsHandler = signatures.DownloadProjectSignatureCCLAsHandlerFunc(func(params signatures.DownloadProjectSignatureCCLAsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesDownloadProjectSignatureCCLAsHandler", + "functionName": "v2.signatures.handlers.SignaturesDownloadProjectSignatureCCLAsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, } @@ -1172,8 +1192,9 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj api.SignaturesDownloadProjectSignatureCCLAAsCSVHandler = signatures.DownloadProjectSignatureCCLAAsCSVHandlerFunc(func(params signatures.DownloadProjectSignatureCCLAAsCSVParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "SignaturesDownloadProjectSignatureCCLAAsCSVHandler", + "functionName": "v2.signatures.handlers.SignaturesDownloadProjectSignatureCCLAAsCSVHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, } @@ -1209,7 +1230,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj log.WithFields(f).Debug("checking access control permissions for user...") if !isUserHaveAccessToCLAGroupProjects(ctx, authUser, params.ClaGroupID, projectClaGroupsRepo, projectRepo) { - msg := fmt.Sprintf("user %s is not authorized to view project CCLA signatures any scope of project", authUser.UserName) + msg := fmt.Sprintf("user '%s' is not authorized to view project CCLA signatures any scope of project", authUser.UserName) log.Warn(msg) return signatures.NewDownloadProjectSignatureCCLAAsCSVForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -1253,7 +1274,7 @@ func getProjectIDsFromModels(f logrus.Fields, foundationSFID string, projectCLAG // isUserHaveAccessOfSignedSignaturePDF returns true if the specified user has access to the provided signature, false otherwise func isUserHaveAccessOfSignedSignaturePDF(ctx context.Context, authUser *auth.User, signature *v1Models.Signature, companyService company.IService, projectClaGroupRepo projects_cla_groups.Repository, projectRepo project.ProjectRepository) (bool, error) { f := logrus.Fields{ - "functionName": "isUserHaveAccessOfSignedSignaturePDF", + "functionName": "v2.signatures.handlers.isUserHaveAccessOfSignedSignaturePDF", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUserName": authUser.UserName, "authUserEmail": authUser.Email, @@ -1364,7 +1385,7 @@ func errorResponse(reqID string, err error) *models.ErrorResponse { // isUserHaveAccessToCLAGroupProjects is a helper function to determine if the user has access to the specified CLA Group projects func isUserHaveAccessToCLAGroupProjects(ctx context.Context, authUser *auth.User, claGroupID string, projectClaGroupsRepo projects_cla_groups.Repository, projectRepo project.ProjectRepository) bool { f := logrus.Fields{ - "functionName": "isUserHaveAccessToCLAGroupProjects", + "functionName": "v2.signatures.handlers.isUserHaveAccessToCLAGroupProjects", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "userName": authUser.UserName, @@ -1431,7 +1452,7 @@ func isUserHaveAccessToCLAGroupProjects(ctx context.Context, authUser *auth.User // isUserHaveAccessToCLAProject is a helper function to determine if the user has access to the specified project func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, projectSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { // nolint f := logrus.Fields{ - "functionName": "isUserHaveAccessToCLAProject", + "functionName": "v2.signatures.handlers.isUserHaveAccessToCLAProject", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "userName": authUser.UserName, @@ -1489,7 +1510,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj // isUserHaveAccessToCLAProjectOrganization is a helper function to determine if the user has access to the specified project and organization func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *auth.User, projectSFID, organizationSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { f := logrus.Fields{ - "functionName": "isUserHaveAccessToCLAProjectOrganization", + "functionName": "v2.signatures.handlers.isUserHaveAccessToCLAProjectOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "organizationSFID": organizationSFID, @@ -1497,40 +1518,40 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut "userEmail": authUser.Email, } - log.WithFields(f).Debug("testing if user has access to project SFID...") + log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID: %s...", authUser.UserName, authUser.Email, projectSFID) if utils.IsUserAuthorizedForProject(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to project SFID...") + log.WithFields(f).Debugf("user %s/%s has access to project SFID: %s...", authUser.UserName, authUser.Email, projectSFID) return true } - log.WithFields(f).Debug("testing if user has access to project SFID tree...") + log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID tree...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to project SFID tree...") + log.WithFields(f).Debugf("user %s/%s has access to project SFID tree...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("testing if user has access to project SFID and organization SFID...") + log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID and organization SFID...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProjectOrganization(authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to project SFID and organization SFID...") + log.WithFields(f).Debugf("user %s/%s has access to project SFID and organization SFID...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("testing if user has access to project SFID and organization SFID tree...") + log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID and organization SFID tree...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to project SFID and organization SFID tree...") + log.WithFields(f).Debugf("user %s/%s has access to project SFID and organization SFID tree...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("testing if user has access to organization SFID...") + log.WithFields(f).Debugf("testing if user %s/%s has access to organization SFID...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForOrganization(authUser, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to organization SFID...") + log.WithFields(f).Debugf("user %s/%s has access to organization SFID...", authUser.UserName, authUser.Email) return true } // No luck so far...let's load up the Project => CLA Group mapping and check to see if the user has access to the // other projects or the parent project group/foundation - log.WithFields(f).Debug("user doesn't have direct access to the project only, project + organization, or organization only - loading CLA Group from project id...") + log.WithFields(f).Debugf("user %s/%s doesn't have direct access to the project only, project + organization, or organization only - loading CLA Group from project id...", authUser.UserName, authUser.Email) projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") @@ -1543,26 +1564,26 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // Check the foundation permissions f["foundationSFID"] = projectCLAGroupModel.FoundationSFID - log.WithFields(f).Debug("testing if user has access to parent foundation...") + log.WithFields(f).Debugf("testing if user %s/%s has access to parent foundation...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to parent foundation...") + log.WithFields(f).Debugf("user %s/%s has access to parent foundation...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("testing if user has access to parent foundation tree...") + log.WithFields(f).Debugf("testing if user %s/%s has access to parent foundation tree...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to parent foundation tree...") + log.WithFields(f).Debugf("user %s/%s has access to parent foundation tree...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("testing if user has access to foundation SFID and organization SFID...") + log.WithFields(f).Debugf("testing if user %s/%s has access to foundation SFID and organization SFID...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProjectOrganization(authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to foundation SFID and organization SFID...") + log.WithFields(f).Debugf("user %s/%s has access to foundation SFID and organization SFID...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("testing if user has access to foundation SFID and organization SFID tree...") + log.WithFields(f).Debugf("testing if user %s/%s has access to foundation SFID and organization SFID tree...", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to foundation SFID and organization SFID tree...") + log.WithFields(f).Debugf("user %s/%s has access to foundation SFID and organization SFID tree...", authUser.UserName, authUser.Email) return true } @@ -1576,12 +1597,12 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) f["projectIDs"] = strings.Join(projectSFIDs, ",") - log.WithFields(f).Debug("testing if user has access to any cla group project + organization") + log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group project + organization", authUser.UserName, authUser.Email) if utils.IsUserAuthorizedForAnyProjectOrganization(authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to at least of of the projects...") + log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects...", authUser.UserName, authUser.Email) return true } - log.WithFields(f).Debug("exhausted project checks - user does not have access to project") + log.WithFields(f).Debugf("exhausted project checks - user %s/%s does not have access to project", authUser.UserName, authUser.Email) return false } From c1dcb10e705b78f2b88e226634921260683af573 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 16 Apr 2021 00:48:45 +0300 Subject: [PATCH 0229/1276] [#2866] Bug/UnEnforce CLA (#2875) - Updated Get GH project org repos logic that resolves the UI unenforcement of CLA Signed-off-by: Harold Wanyama --- cla-backend-go/github_organizations/service.go | 9 ++++++++- cla-backend-go/repositories/service.go | 15 ++++++++++++++- cla-backend-go/v2/github_organizations/service.go | 4 ++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index f810b7444..3fbdfb722 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -101,7 +101,14 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return nil, projErr } - if parentProjectSFID != projectSFID && parentProjectSFID != "" { + //Get SF Project + projectDetails, projDetailsErr := v2ProjectService.GetClient().GetProject(projectSFID) + if projDetailsErr != nil { + log.WithFields(f).Warnf("problem fetching parent project details for :%s ", projectSFID) + return nil, projDetailsErr + } + + if parentProjectSFID != projectSFID && (projectDetails != nil && !utils.IsProjectHasRootParent(projectDetails)) { log.WithFields(f).Debugf("found parent of projectSFID: %s to be %s. Searching github organization by parent SFID: %s...", projectSFID, parentProjectSFID, parentProjectSFID) parentGithubModels, parentErr := s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) if parentErr != nil { diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index 9c2cd36ed..4edfbcf59 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -156,7 +156,20 @@ func (s *service) ListProjectRepositories(ctx context.Context, externalProjectID } func (s *service) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { - return s.repo.GetRepository(ctx, repositoryID) + f := logrus.Fields{ + "functionName": "v1.repository.GetRepository", + "repositoryID": repositoryID, + } + log.WithFields(f).Debug("Searching for repository...") + ghRepo, err := s.repo.GetRepository(ctx, repositoryID) + if err != nil || ghRepo != nil { + log.WithFields(f).WithError(err).Debug("unable to get repository") + return nil, err + } + + log.WithFields(f).Debugf("Found repository : %+v ", ghRepo) + + return ghRepo, nil } func (s *service) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 6a65e22c9..94e4350c2 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -208,7 +208,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } rorg.Repositories = append(rorg.Repositories, &models.ProjectGithubRepository{ ConnectionStatus: Connected, - Enabled: true, + Enabled: repo.Enabled, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, RepositoryGithubID: repoGithubID, @@ -223,7 +223,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } else { rorg.Repositories = append(rorg.Repositories, &models.ProjectGithubRepository{ ConnectionStatus: ConnectionFailure, - Enabled: true, + Enabled: repo.Enabled, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, ClaGroupID: repo.RepositoryProjectID, From 4a50ad301b892214a6f6884fbf39d63b054287b9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 15 Apr 2021 17:42:21 -0700 Subject: [PATCH 0230/1276] Added Date Created/Modified to Event Log (#2879) - Added missing fields to match python event log Signed-off-by: David Deal --- cla-backend-go/events/repository.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index 5d8cec251..bfce950a4 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -88,7 +88,7 @@ func toDateFormat(t time.Time) string { return t.Format("02-01-2006") } -// Create event will create event in database. +// CreateEvent event will create event in database. func (repo *repository) CreateEvent(event *models.Event) error { f := logrus.Fields{ "functionName": "v1.events.repository.CreateEvent", @@ -145,6 +145,8 @@ func (repo *repository) CreateEvent(event *models.Event) error { addAttribute(input.Item, "event_time", currentTimeString) addAttribute(input.Item, "event_date", toDateFormat(currentTime)) addAttribute(input.Item, "event_date_and_contains_pii", eventDateAndContainsPII) + addAttribute(input.Item, "date_created", toDateFormat(currentTime)) + addAttribute(input.Item, "date_modified", toDateFormat(currentTime)) input.Item["contains_pii"] = &dynamodb.AttributeValue{BOOL: &event.ContainsPII} input.Item["event_time_epoch"] = &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(currentTime.Unix(), 10))} From 268ae5f18ba50a5cb5c8febe8d7fc4efd057df32 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 15 Apr 2021 19:00:20 -0700 Subject: [PATCH 0231/1276] Resolved [#2651] Event Log Updates (#2880) - Updated event log text with correct user information Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 432 ++++++++++++++++++----- cla-backend-go/events/event_data_test.go | 12 +- 2 files changed, 352 insertions(+), 92 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index a99ffb2e3..cfa984ff9 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -384,92 +384,153 @@ type ClaManagerRoleDeletedData struct { // GetEventDetailsString . . . func (ed *CLAGroupEnrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s (%s/%s) enabled the the project %s (%s) from the CLA Group %s (%s).", - args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false + data := fmt.Sprintf("The project %s (%s) was enrolled into the CLA Group %s (%s)", args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *CLAGroupUnenrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s (%s/%s) unenrolled the the project %s (%s) from the CLA Group %s (%s).", - args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID), false + data := fmt.Sprintf("The project %s (%s) was unenrolled from the CLA Group %s (%s)", args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *ProjectServiceCLAEnabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s (%s/%s) enabled the CLA Service for the project %s (%s)", - args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false + data := fmt.Sprintf("The CLA Service for the project %s (%s) was enabled", args.ProjectName, args.ProjectID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *ProjectServiceCLADisabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("%s (%s/%s) disabled the CLA Service for the project %s (%s)", - args.UserName, args.LFUser.Name, args.UserModel.LfEmail, args.ProjectName, args.ProjectID), false + data := fmt.Sprintf("The CLA Service for the project %s (%s) was disabled", args.ProjectName, args.ProjectID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository: %s was added for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository: %s was added for the project %s", ed.RepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was deleted for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was deleted for the project %s", ed.RepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was updated for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository %s was updated for the project %s", ed.RepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s", ed.RepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s", ed.RepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s by the user %s.", ed.RepositoryName, args.ProjectName, args.UserName) + data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s", ed.RepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *UserCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s added. User Details: %+v.", args.UserName, args.UserModel) + data := fmt.Sprintf("User was added : %+v", args.UserModel) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *UserUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("User: %s updated. User Details: %+v.", args.UserName, *args.UserModel), true + data := fmt.Sprintf("User details updated: %+v", *args.UserModel) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *UserDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s deleted. User ID: %s.", args.UserName, ed.DeletedUserID) + data := fmt.Sprintf("User ID: %s was deleted", ed.DeletedUserID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CompanyACLRequestAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s added pending invite with ID: %s and Email: %s for Company: %s.", + data := fmt.Sprintf("User: %s added pending invite with ID: %s and Email: %s for Company: %s", ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CompanyACLRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Access Aproved for User: %s, ID: %s, Email: %s Company Group: %s.", + data := fmt.Sprintf("Access Aproved for User: %s, ID: %s, Email: %s Company Group: %s", ed.UserName, args.CompanyName, ed.UserID, ed.UserEmail) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -477,19 +538,31 @@ func (ed *CompanyACLRequestApprovedEventData) GetEventDetailsString(args *LogEve func (ed *CompanyACLRequestDeniedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Access Denied for User: %s, ID: %s, Email: %s Company Group: %s.", ed.UserName, args.CompanyName, ed.UserID, ed.UserEmail) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CompanyACLUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User with LF Username: %s added to the ACL for Company: %s by: %s.", - args.LFUser.Name, args.CompanyName, args.UserName) + data := fmt.Sprintf("User with LF Username: %s added to the ACL for Company: %s", + args.LFUser.Name, args.CompanyName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLATemplateCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("PDF Templates created for Project: %s by: %s.", args.UserName, args.ProjectName) + data := fmt.Sprintf("PDF Templates created for Project: %s", args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -500,14 +573,20 @@ func (ed *GitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEvent if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf(" by: %s.", args.UserName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *GitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization: %s was deleted by: %s.", - ed.GitHubOrganizationName, args.UserName) + data := fmt.Sprintf("GitHub Organization: %s was deleted ", ed.GitHubOrganizationName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -518,7 +597,10 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEve if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } - data = data + fmt.Sprintf("by: %s.", args.UserName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -540,6 +622,10 @@ func (ed *CCLAApprovalListRequestRejectedEventData) GetEventDetailsString(args * func (ed *CLAManagerRequestCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s, LFID: %s, Email: %s added CLA Manager Request: %s for Company: %s, Project: %s.", ed.UserName, ed.UserLFID, ed.UserEmail, ed.RequestID, ed.CompanyName, ed.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -547,111 +633,175 @@ func (ed *CLAManagerRequestCreatedEventData) GetEventDetailsString(args *LogEven func (ed *CLAManagerCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s, LFID: %s, Email: %s was added as CLA Manager for Company: %s, Project: %s.", ed.UserName, ed.UserLFID, ed.UserEmail, ed.CompanyName, ed.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAManagerDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s LFID: %s, Email: %s was removed as CLA Manager for Company: %s, Project: %s.", + data := fmt.Sprintf("User: %s LFID: %s, Email: %s was removed as CLA Manager for Company: %s, Project: %s ", ed.UserName, ed.UserLFID, ed.UserEmail, ed.CompanyName, ed.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAManagerRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager Request: %s was approved for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager Request: %s was approved for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s", ed.RequestID, ed.UserName, ed.UserEmail, ed.ManagerName, ed.ManagerEmail, ed.CompanyName, ed.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAManagerRequestDeniedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager Request: %s was denied for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager Request: %s was denied for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s", ed.RequestID, ed.UserName, ed.UserEmail, ed.ManagerName, ed.ManagerEmail, ed.CompanyName, ed.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAManagerRequestDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager Request: %s was deleted for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager Request: %s was deleted for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s", ed.RequestID, ed.UserName, ed.UserEmail, ed.ManagerName, ed.ManagerEmail, ed.CompanyName, ed.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Email: %s to the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Email: %s to the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Email: %s from the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Email: %s from the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Domain: %s to the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Domain: %s to the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Domain %s from the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Domain %s from the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Username: %s to the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Username: %s to the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Username: %s from the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Username: %s from the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Organization: %s to the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Organization: %s to the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Organization: %s from the approval list for Company: %s, Project: %s.", + data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Organization: %s from the approval list for Company: %s, Project: %s", ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CCLAApprovalListRequestCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s created a CCLA Approval Request for Project: %s, Company: %s with Request ID: %s.", - args.UserName, args.ProjectName, args.CompanyName, ed.RequestID) + data := fmt.Sprintf("The CCLA Approval Request was created for the Project: %s, Company: %s with Request ID: %s", + args.ProjectName, args.CompanyName, ed.RequestID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s added GitHub Organization: %s to the whitelist for Company %s, Project: %s.", - args.UserName, ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The GitHub Organization: %s was added to the approval list for the Company %s, Project: %s", + ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s removed GitHub Organization: %s from the whitelist for Company: %s, Project: %s.", - args.UserName, ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The GitHub Organization: %s was removed from the approval list for the Company: %s, Project: %s", + ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -671,8 +821,11 @@ func (ed *ClaManagerAccessRequestDeletedEventData) GetEventDetailsString(args *L // GetEventDetailsString . . . func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Group ID: %s, Name: %s was created by: %s.", - args.ProjectID, args.ProjectName, args.UserName) + data := fmt.Sprintf("CLA Group ID: %s, Name: %s was created", args.ProjectID, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -680,7 +833,7 @@ func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (s func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { var nameUpdated bool - data := fmt.Sprintf("CLA Group ID: %s was updated by: %s", args.ProjectID, args.UserName) + data := fmt.Sprintf("CLA Group ID: %s was updated", args.ProjectID) if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName { data = fmt.Sprintf("%s with Name from : %s to : %s", data, ed.OldClaGroupName, ed.NewClaGroupName) nameUpdated = true @@ -694,59 +847,93 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s } data = fmt.Sprintf("%s Description from : %s to : %s", data, ed.OldClaGroupDescription, ed.NewClaGroupDescription) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } // GetEventDetailsString . . . func (ed *CLAGroupDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Group ID: %s was deleted by: %s.", - args.ProjectID, args.UserName) + data := fmt.Sprintf("CLA Group ID: %s was deleted", args.ProjectID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *GerritProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("%d Gerrit Repositories were deleted due to CLA Group/Project: %s deletion.", + data := fmt.Sprintf("%d Gerrit Repositories were deleted due to CLA Group/Project: %s deletion", ed.DeletedCount, args.ProjectName) - return data, false + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *GerritAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Gerrit Repository: %s was added by: %s.", ed.GerritRepositoryName, args.UserName) + data := fmt.Sprintf("Gerrit Repository: %s was added", ed.GerritRepositoryName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *GerritDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Gerrit Repository: %s was deleted by: %s.", ed.GerritRepositoryName, args.UserName) + data := fmt.Sprintf("Gerrit Repository: %s was deleted", ed.GerritRepositoryName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *GerritUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The username %s was add to the gerrit group %s by the user %s.", ed.Username, ed.GroupName, args.UserName) + data := fmt.Sprintf("The username %s was add to the gerrit group %s", ed.Username, ed.GroupName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *GerritUserRemovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The username %s was removed from the gerrit group %s by the user %s.", ed.Username, ed.GroupName, args.UserName) + data := fmt.Sprintf("The username %s was removed from the gerrit group %s", ed.Username, ed.GroupName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *GitHubProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("%d GitHub Repositories were deleted due to CLA Group/Project: [%s] deletion.", + data := fmt.Sprintf("%d GitHub Repositories were deleted due to CLA Group/Project: [%s] deletion", ed.DeletedCount, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *SignatureProjectInvalidatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("%d Signatures were invalidated (approved set to false) due to CLA Group/Project: %s deletion.", + data := fmt.Sprintf("%d Signatures were invalidated (approved set to false) due to CLA Group/Project: %s deletion", ed.InvalidatedCount, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -759,6 +946,10 @@ func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventDetailsString( reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) } data := fmt.Sprintf("Signature ID: %s invalidated by %s (approved set to false) due to %s ", utils.GetBestUsername(ed.CLAManager), ed.SignatureID, reason) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -780,24 +971,28 @@ func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs // GetEventDetailsString . . . func (ed *ContributorAssignCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User Name: %s, Email: %s was assigned as CLA Manager Designee for project Name: %s, ID: %s and Company Name: %s, ID: %s by: %s.", + data := fmt.Sprintf("User Name: %s, Email: %s was assigned as CLA Manager Designee for project Name: %s, ID: %s and Company Name: %s, ID: %s", ed.DesigneeName, ed.DesigneeEmail, args.ProjectName, args.ProjectSFID, - args.CompanyName, args.CompanyID, args.UserName) + args.CompanyName, args.CompanyID) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } // GetEventDetailsString . . . func (ed *UserConvertToContactData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s was converted to Contact state for Project: %s with ID: %s.", - args.LFUser.Name, args.ProjectName, args.ProjectSFID) + args.UserName, args.ProjectName, args.ProjectSFID) return data, true } // GetEventDetailsString . . . func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s was assigned Scope: %s with Role: %s for Project: %s with ID: %s.", - args.LFUser.Name, + args.UserName, ed.Scope, ed.Role, args.ProjectName, args.ProjectSFID) return data, true } @@ -818,19 +1013,27 @@ func (ed *ClaManagerRoleDeletedData) GetEventDetailsString(args *LogEventArgs) ( // GetEventDetailsString . . . func (ed *CLAGroupEnrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("The user %s enabled the the project %s from the CLA Group %s.", - args.UserName, args.ProjectName, args.CLAGroupName), false + data := fmt.Sprintf("The project %s was enrolled into the CLA Group %s", args.ProjectName, args.CLAGroupName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *CLAGroupUnenrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - return fmt.Sprintf("The user %s unenrolled the the project %s from the CLA Group %s.", - args.UserName, args.ProjectName, args.CLAGroupName), false + data := fmt.Sprintf("The project %s was unenrolled from the CLA Group %s", args.ProjectName, args.CLAGroupName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true } // GetEventDetailsString . . . func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s enabled the CLA Service", args.UserName) + data := "CLA Service was enabled" if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -840,13 +1043,16 @@ func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." - return data, false + return data, true } -// GetEventDetailsString . . . +// GetEventSummaryString function for ProjectServiceCLADisabledData func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s disabled the CLA Service", args.UserName) + data := "CLA Service was disabled" if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -856,8 +1062,11 @@ func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArg if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." - return data, false + return data, true } // GetEventSummaryString . . . @@ -1021,6 +1230,9 @@ func (ed *CompanyACLRequestApprovedEventData) GetEventSummaryString(args *LogEve if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } @@ -1038,6 +1250,9 @@ func (ed *CompanyACLRequestDeniedEventData) GetEventSummaryString(args *LogEvent if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } @@ -1220,6 +1435,9 @@ func (ed *CLAManagerRequestApprovedEventData) GetEventSummaryString(args *LogEve if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } @@ -1260,7 +1478,7 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventSummaryString(args *LogEven // GetEventSummaryString . . . func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the email %s to the approval list", ed.UserName, ed.ApprovalListEmail) + data := fmt.Sprintf("The CLA Manager %s added the email %s to the approval list", args.UserName, ed.ApprovalListEmail) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1276,7 +1494,7 @@ func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the email %s from the approval list", ed.UserName, ed.ApprovalListEmail) + data := fmt.Sprintf("The CLA Manager %s removed the email %s from the approval list", args.UserName, ed.ApprovalListEmail) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1292,7 +1510,7 @@ func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventAr // GetEventSummaryString . . . func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the domain %s to the approval list", ed.UserName, ed.ApprovalListDomain) + data := fmt.Sprintf("The CLA Manager %s added the domain %s to the approval list", args.UserName, ed.ApprovalListDomain) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1308,7 +1526,7 @@ func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the domain %s from the approval list", ed.UserName, ed.ApprovalListDomain) + data := fmt.Sprintf("The CLA Manager %s removed the domain %s from the approval list", args.UserName, ed.ApprovalListDomain) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1324,7 +1542,7 @@ func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventA // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the GitHub username %s to the approval list", ed.UserName, ed.ApprovalListGitHubUsername) + data := fmt.Sprintf("The CLA Manager %s added the GitHub username %s to the approval list", args.UserName, ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1340,7 +1558,7 @@ func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogE // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the GitHub username %s from the approval list", ed.UserName, ed.ApprovalListGitHubUsername) + data := fmt.Sprintf("The CLA Manager %s removed the GitHub username %s from the approval list", args.UserName, ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1356,7 +1574,7 @@ func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *L // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the GitHub organization %s to the approval list", ed.UserName, ed.ApprovalListGitHubOrg) + data := fmt.Sprintf("The CLA Manager %s added the GitHub organization %s to the approval list", args.UserName, ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1372,7 +1590,7 @@ func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventA // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the GitHub organization %s from the approval list", ed.UserName, ed.ApprovalListGitHubOrg) + data := fmt.Sprintf("The CLA Manager %s removed the GitHub organization %s from the approval list", args.UserName, ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1502,7 +1720,11 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString . . . func (ed *CLAGroupDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was deleted by the user %s.", args.ProjectName, args.UserName) + data := fmt.Sprintf("The CLA Group %s was deleted", args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -1515,32 +1737,41 @@ func (ed *GerritProjectDeletedEventData) GetEventSummaryString(args *LogEventArg if args.ProjectName != "" { data = data + fmt.Sprintf(" for the project %s", args.ProjectName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." - return data, false + return data, true } // GetEventSummaryString . . . func (ed *GerritAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gerrit repository %s was added by the user %s", ed.GerritRepositoryName, args.UserName) + data := fmt.Sprintf("The Gerrit repository %s was added", ed.GerritRepositoryName) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } if args.ProjectName != "" { data = data + fmt.Sprintf(" for the project %s", args.ProjectName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *GerritDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gerrit repository %s was deleted by the user %s", ed.GerritRepositoryName, args.UserName) + data := fmt.Sprintf("The Gerrit repository %s was deleted", ed.GerritRepositoryName) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } if args.ProjectName != "" { data = data + fmt.Sprintf(" for the project %s", args.ProjectName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } @@ -1587,14 +1818,30 @@ func (ed *GitHubProjectDeletedEventData) GetEventSummaryString(args *LogEventArg if args.ProjectName != "" { data = data + fmt.Sprintf(" for the project %s", args.ProjectName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *SignatureProjectInvalidatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("%d signatures were invalidated (approved set to false) due to CLA Group/Project %s deletion.", + data := fmt.Sprintf("%d signatures were invalidated (approved set to false) due to CLA Group/Project %s deletion", ed.InvalidatedCount, args.ProjectName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -1606,7 +1853,20 @@ func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventSummaryString( } else if ed.GHUsername != "" { reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) } - data := fmt.Sprintf("Signature ID: %s invalidated (approved set to false) due to %s ", ed.SignatureID, reason) + data := fmt.Sprintf("Signature ID: %s invalidated (approved set to false) due to %s", ed.SignatureID, reason) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -1664,7 +1924,7 @@ func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs // GetEventSummaryString . . . func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was converted to a contact", args.LFUser.Name) + data := fmt.Sprintf("The user %s was converted to a contact", args.UserName) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1680,7 +1940,7 @@ func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString . . . func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added to the role %s", args.LFUser.Name, ed.Role) + data := fmt.Sprintf("The user %s was added to the role %s", args.UserName, ed.Role) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } diff --git a/cla-backend-go/events/event_data_test.go b/cla-backend-go/events/event_data_test.go index e594bb0a0..e81d3d27c 100644 --- a/cla-backend-go/events/event_data_test.go +++ b/cla-backend-go/events/event_data_test.go @@ -86,7 +86,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { { name: "empty", eventData: &CLAGroupUpdatedEventData{}, - detailStr: "CLA Group ID: projectIDValue was updated by: john.", + detailStr: "CLA Group ID: projectIDValue was updated by the user john.", }, { name: "only name updated", @@ -94,7 +94,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupName: "updatedNameValue", OldClaGroupName: "oldNameValue", }, - detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue.", + detailStr: "CLA Group ID: projectIDValue was updated with Name from : oldNameValue to : updatedNameValue by the user john.", }, { name: "only name updated but old description still passed", @@ -104,7 +104,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupDescription: "oldDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue.", + detailStr: "CLA Group ID: projectIDValue was updated with Name from : oldNameValue to : updatedNameValue by the user john.", }, { name: "only description updated", @@ -112,7 +112,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupDescription: "updatedDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - detailStr: "CLA Group ID: projectIDValue was updated by: john with Description from : oldDescriptionValue to : updatedDescriptionValue.", + detailStr: "CLA Group ID: projectIDValue was updated with Description from : oldDescriptionValue to : updatedDescriptionValue by the user john.", }, { name: "only description updated but old name still passed", @@ -122,7 +122,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupName: "oldNameValue", OldClaGroupName: "oldNameValue", }, - detailStr: "CLA Group ID: projectIDValue was updated by: john with Description from : oldDescriptionValue to : updatedDescriptionValue.", + detailStr: "CLA Group ID: projectIDValue was updated with Description from : oldDescriptionValue to : updatedDescriptionValue by the user john.", }, { name: "name and description updated", @@ -132,7 +132,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupDescription: "updatedDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - detailStr: "CLA Group ID: projectIDValue was updated by: john with Name from : oldNameValue to : updatedNameValue, Description from : oldDescriptionValue to : updatedDescriptionValue.", + detailStr: "CLA Group ID: projectIDValue was updated with Name from : oldNameValue to : updatedNameValue, Description from : oldDescriptionValue to : updatedDescriptionValue by the user john.", }, } From 25a05f15f4a55c4ae6a1edaef734601a73a0d975 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 19 Apr 2021 17:59:38 +0300 Subject: [PATCH 0232/1276] [#2872] removing extra cla_group.updated event which is confusing for UI (#2881) event since it's confusing on the UI Signed-off-by: makkalot --- cla-backend-go/v2/cla_groups/handlers.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 801e5ab2d..9541a4a66 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -385,16 +385,6 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P utils.ErrorResponseInternalServerErrorWithError(reqID, fmt.Sprintf("unable to unenroll projects for CLA Group ID: %s", params.ClaGroupID), err)) } - eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupUpdated, - ClaGroupModel: cg, - LfUsername: authUser.UserName, - EventData: &events.CLAGroupUpdatedEventData{ - OldClaGroupName: cg.ProjectName, - OldClaGroupDescription: cg.ProjectDescription, - }, - }) - return cla_group.NewUnenrollProjectsOK().WithXRequestID(reqID) }) From 30b9a7c3a287595e3c2c785295bb070d46b47ea9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 19 Apr 2021 15:32:28 -0700 Subject: [PATCH 0233/1276] Added Logging (#2885) - Added request_employee_signature_gerrit and request_employee_signature logging to help track down issue with employee ack for onap Signed-off-by: David Deal --- cla-backend/cla/controllers/signing.py | 10 +++ cla-backend/cla/models/docusign_models.py | 79 +++++++++++++---------- cla-backend/cla/models/event_types.py | 5 +- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index 4cd846b3d..1b761a184 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -104,13 +104,23 @@ def request_employee_signature(project_id, company_id, user_id, return_url_type, :type return_url_type: string :param return_url: The URL to return the user to after signing is complete. """ + fn = 'cla.controllers.signing.request_employee_signature' signing_service = get_signing_service() if return_url_type is not None and return_url_type.lower() == "gerrit": + cla.log.error(f'{fn} - return type is gerrit - invoking: request_employee_signature_gerrit') return signing_service.request_employee_signature_gerrit(str(project_id), str(company_id), str(user_id), return_url) elif return_url_type is not None and return_url_type.lower() == "github": + cla.log.error(f'{fn} - return type is github - invoking: request_employee_signature') return signing_service.request_employee_signature(str(project_id), str(company_id), str(user_id), return_url) + else: + msg = (f'{fn} - unsupported return type {return_url_type} for ' + f'cla group: {project_id}, ' + f'company: {company_id}, ' + f'user: {user_id}') + cla.log.error(msg) + raise falcon.HTTPBadRequest(title=msg) def check_and_prepare_employee_signature(project_id, company_id, user_id): diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 836187325..3404dbe28 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -26,7 +26,7 @@ from cla.models import signing_service_interface, DoesNotExist from cla.models.dynamo_models import Signature, User, \ Project, Company, Gerrit, \ - Document, Event, ProjectCLAGroupModel + Document, Event from cla.models.event_types import EventType from cla.models.s3_storage import S3Storage from cla.user_service import UserService @@ -631,13 +631,14 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic def request_employee_signature(self, project_id, company_id, user_id, return_url=None): - request_info = f'project: {project_id}, company: {company_id}, user: {user_id} with return_url: {return_url}' - cla.log.info(f'Processing request_employee_signature request with {request_info}') + fn = 'docusign_models.check_and_prepare_employee_signature' + request_info = f'cla group: {project_id}, company: {company_id}, user: {user_id} with return_url: {return_url}' + cla.log.info(f'{fn} - processing request_employee_signature request with {request_info}') check_and_prepare_signature = self.check_and_prepare_employee_signature(project_id, company_id, user_id) # Check if there are any errors while preparing the signature. if 'errors' in check_and_prepare_signature: - cla.log.warning(f'Error in check_and_prepare_signature with: {request_info} - ' + cla.log.warning(f'{fn} - error in check_and_prepare_signature with: {request_info} - ' f'signatures: {check_and_prepare_signature}') return check_and_prepare_signature @@ -645,28 +646,30 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url company_id=company_id, project_id=project_id, user_id=user_id) # Return existing signature if employee has signed it if employee_signature is not None: - cla.log.info(f'Employee has signed for company: {company_id}, ' - f'request_info: {request_info} - signature: {employee_signature}') + cla.log.info(f'{fn} - employee has previously acknowledged their company affiliation ' + f'for request_info: {request_info} - signature: {employee_signature}') return employee_signature.to_dict() - cla.log.info(f'Employee has NOT signed it for: {request_info}') + cla.log.info(f'{fn} - employee has NOT previously acknowledged their company affiliation for : {request_info}') # Requires us to know where the user came from. signature_metadata = cla.utils.get_active_signature_metadata(user_id) if return_url is None: - cla.log.info(f'No return URL for: {request_info}') + cla.log.debug(f'{fn} - no return URL for: {request_info}') return_url = cla.utils.get_active_signature_return_url(user_id, signature_metadata) - cla.log.info(f'Set return URL for: {request_info} to: {return_url}') + cla.log.debug(f'{fn} - set return URL for: {request_info} to: {return_url}') # project has already been checked from check_and_prepare_employee_signature. Load project with project ID. project = Project() + cla.log.info(f'{fn} - loading cla group details for: {request_info}') project.load(project_id) - cla.log.info(f'Loaded project details for: {request_info}') + cla.log.info(f'{fn} - loaded cla group details for: {request_info}') # company has already been checked from check_and_prepare_employee_signature. Load company with company ID. company = Company() + cla.log.info(f'{fn} - loading company details for: {request_info}') company.load(company_id) - cla.log.info(f'Loaded company details for: {request_info}') + cla.log.info(f'{fn} - loaded company details for: {request_info}') # user has already been checked from check_and_prepare_employee_signature. Load user with user ID. user = User() @@ -674,9 +677,10 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url # Get project's latest corporate document to get major/minor version numbers. last_document = project.get_latest_corporate_document() - cla.log.info(f'Loaded last project document details for: {request_info}') + cla.log.info(f'{fn} - loaded the current cla document document details for: {request_info}') # return_url may still be empty at this point - the console will deal with it + cla.log.info(f'{fn} - creating a new signature document for: {request_info}') new_signature = Signature(signature_id=str(uuid.uuid4()), signature_project_id=project_id, signature_document_minor_version=last_document.get_document_minor_version(), @@ -689,20 +693,22 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url signature_approved=True, signature_return_url=return_url, signature_user_ccla_company_id=company_id) - cla.log.info(f'Created new signature document for: {request_info} - signature: {new_signature}') + cla.log.info(f'{fn} - created new signature document for: {request_info} - signature: {new_signature}') # Set signature ACL - new_signature.set_signature_acl(f'github:{user.get_user_github_id()}') + acl_value = f'github:{user.get_user_github_id()}' + cla.log.info(f'{fn} - assigning signature acl with value: {acl_value} for: {request_info}') + new_signature.set_signature_acl(acl_value) # Save signature new_signature.save() - cla.log.info(f'Set and saved signature for: {request_info}') - event_data = (f'The user {user.get_user_name()} acknowledged the CLA affiliation for ' + cla.log.info(f'{fn} - saved signature for: {request_info}') + event_data = (f'The user {user.get_user_name()} acknowledged the CLA employee affiliation for ' f'company {company.get_company_name()} with ID {company.get_company_id()}, ' - f'project {project.get_project_name()} with ID {project.get_project_id()}.') - event_summary = (f'The user {user.get_user_name()} acknowledged the CLA affiliation for ' + f'cla group {project.get_project_name()} with ID {project.get_project_id()}.') + event_summary = (f'The user {user.get_user_name()} acknowledged the CLA employee affiliation for ' f'company {company.get_company_name()} and ' - f'project {project.get_project_name()}.') + f'cla group {project.get_project_name()}.') Event.create_event( event_type=EventType.EmployeeSignatureCreated, event_company_id=company_id, @@ -717,7 +723,7 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url # If the project does not require an ICLA to be signed, update the pull request and remove the active # signature metadata. if not project.get_project_ccla_requires_icla_signature(): - cla.log.info('Project does not require ICLA signature from the employee - updating PR') + cla.log.info(f'{fn} - cla group does not require a separate ICLA signature from the employee - updating PR') github_repository_id = signature_metadata['repository_id'] change_request_id = signature_metadata['pull_request_id'] @@ -727,23 +733,23 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url return {'errors': {'github_repository_id': 'The given github repository ID does not exist. '}} update_repository_provider(installation_id, github_repository_id, change_request_id) - cla.utils.delete_active_signature_metadata(user_id) else: - cla.log.info('Project requires ICLA signature from employee - PR has been left unchanged') + cla.log.info(f'{fn} - cla group requires ICLA signature from employee - PR has been left unchanged') - cla.log.info(f'Returning new signature for: {request_info} - signature: {new_signature}') + cla.log.info(f'{fn} - returning new signature for: {request_info} - signature: {new_signature}') return new_signature.to_dict() def request_employee_signature_gerrit(self, project_id, company_id, user_id, return_url=None): - request_info = f'project: {project_id}, company: {company_id}, user: {user_id} with return_url: {return_url}' - cla.log.info(f'Processing request_employee_signature_gerrit request with {request_info}') + fn = 'docusign_models.request_employee_signature_gerrit' + request_info = f'cla group: {project_id}, company: {company_id}, user: {user_id} with return_url: {return_url}' + cla.log.info(f'{fn} - processing request_employee_signature_gerrit request with {request_info}') check_and_prepare_signature = self.check_and_prepare_employee_signature(project_id, company_id, user_id) # Check if there are any errors while preparing the signature. if 'errors' in check_and_prepare_signature: - cla.log.warning(f'Error in request_employee_signature_gerrit with: {request_info} - ' + cla.log.warning(f'{fn} - error in request_employee_signature_gerrit with: {request_info} - ' f'signatures: {check_and_prepare_signature}') return check_and_prepare_signature @@ -752,27 +758,31 @@ def request_employee_signature_gerrit(self, project_id, company_id, user_id, ret company_id=company_id, project_id=project_id, user_id=user_id) # Return existing signature if employee has signed it if employee_signature is not None: - cla.log.info(f'Employee has signed for company: {company_id}, ' + cla.log.info(f'{fn} - employee has signed for company: {company_id}, ' f'request_info: {request_info} - signature: {employee_signature}') return employee_signature.to_dict() - cla.log.info(f'Employee has NOT signed it for: {request_info}') + cla.log.info(f'{fn} - employee has NOT previously acknowledged their company affiliation for : {request_info}') # Retrieve Gerrits by Project reference ID try: + cla.log.info(f'{fn} - loading gerrits for: {request_info}') gerrits = Gerrit().get_gerrit_by_project_id(project_id) except DoesNotExist as err: - cla.log.error(f'Cannot load Gerrit instance for: {request_info}') + cla.log.error(f'{fn} - cannot load Gerrit instance for: {request_info}') return {'errors': {'missing_gerrit': str(err)}} # project has already been checked from check_and_prepare_employee_signature. Load project with project ID. project = Project() + cla.log.info(f'{fn} - loading cla group for: {request_info}') project.load(project_id) - cla.log.info(f'Loaded project for: {request_info}') + cla.log.info(f'{fn} - loaded cla group for: {request_info}') + # company has already been checked from check_and_prepare_employee_signature. Load company with company ID. company = Company() + cla.log.info(f'{fn} - loading company details for: {request_info}') company.load(company_id) - cla.log.info(f'Loaded company details for: {request_info}') + cla.log.info(f'{fn} - loaded company details for: {request_info}') # user has already been checked from check_and_prepare_employee_signature. Load user with user ID. user = User() @@ -798,7 +808,7 @@ def request_employee_signature_gerrit(self, project_id, company_id, user_id, ret # Save signature before adding user to the LDAP Group. new_signature.save() - cla.log.info(f'Set and saved signature for: {request_info}') + cla.log.info(f'{fn} - saved signature for: {request_info}') event_data = (f'The user {user.get_user_name()} acknowledged the CLA company affiliation for ' f'company {company.get_company_name()} with ID {company.get_company_id()}, ' f'project {project.get_project_name()} with ID {project.get_project_id()}.') @@ -822,9 +832,10 @@ def request_employee_signature_gerrit(self, project_id, company_id, user_id, ret group_id = gerrit.get_group_id_ccla() # Add the user to the LDAP Group try: + cla.log.debug(f'{fn} - adding user to group: {group_id}') lf_group.add_user_to_group(group_id, user.get_lf_username()) except Exception as e: - cla.log.error('Failed in adding user to the LDAP group.{} - {}'.format(e, request_info)) + cla.log.error(f'{fn} - failed in adding user to the LDAP group.{e} - {request_info}') return return new_signature.to_dict() @@ -1295,7 +1306,7 @@ def populate_sign_url(self, signature, callback_url=None, cla_manager_email=cla_manager_email, company_name=company_name, project_version=project.get_version(), - project_names=project_names)) + 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, diff --git a/cla-backend/cla/models/event_types.py b/cla-backend/cla/models/event_types.py index cd6d03fe6..4f6ca5d66 100644 --- a/cla-backend/cla/models/event_types.py +++ b/cla-backend/cla/models/event_types.py @@ -6,9 +6,8 @@ class EventType(Enum): """ - Enumeraters representing type of CLA events + Enumerator representing type of CLA events across projects, users, signatures, whitelists - """ CreateUser = "Create User" UpdateUser = "Update User" @@ -24,7 +23,7 @@ class EventType(Enum): CreateProjectDocumentTemplate = "Create Project Document with Template" DeleteProjectDocument = "Delete Project Document" AddPermission = "Add Permission" - RemovePermission = "Remove Pemrission" + RemovePermission = "Remove Permission" AddProjectManager = "Add Project Manager" RemoveProjectManager = "Remove Project Manager" RequestCompanyWL = "Request Company Whitelist" From 2cab1d8a612771c494d0cc837deb579d15fa796d Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 20 Apr 2021 13:20:05 -0700 Subject: [PATCH 0234/1276] Added String to DateTime Python Helper Function (#2888) - Added string to datetime python helper function to assist with event log processing - added get_events_type_by_week in dynamo events class - Added additional logging to docusign routines Signed-off-by: David Deal --- cla-backend/cla/models/docusign_models.py | 2 ++ cla-backend/cla/models/dynamo_models.py | 27 +++++++++++++++++++++++ cla-backend/cla/utils.py | 16 ++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 3404dbe28..83cc5aed9 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -1476,6 +1476,7 @@ def signed_individual_callback(self, content, installation_id, github_repository # Log the event try: # Load the Project by ID and send audit event + cla.log.debug(f'{fn} - creating an event log entry for event_type: {EventType.IndividualSignatureSigned}') project = Project() project.load(signature.get_signature_project_id()) event_data = (f'The user {user.get_user_name()} signed an individual CLA for ' @@ -1492,6 +1493,7 @@ def signed_individual_callback(self, content, installation_id, github_repository event_summary=event_summary, contains_pii=False, ) + cla.log.debug(f'{fn} - created an event log entry for event_type: {EventType.IndividualSignatureSigned}') except DoesNotExist as err: msg = (f'{fn} - unable to load project by CLA Group ID: {signature.get_signature_project_id()}, ' f'unable to send audit event, error: {err}') diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 4ef900489..36949775c 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -4420,6 +4420,33 @@ def search_missing_event_data_lower(self, limit: Optional[int] = None, last_eval # ret.append(evt) # return ret, result_iterator.last_evaluated_key, result_iterator.total_count + def get_events_type_by_week(self, event_type: EventType) -> dict: + filter_attributes = { + "event_type": event_type.name, + } + filter_condition = create_filter(filter_attributes, EventModel) + projection = ["event_id", "event_type", "date_created"] + cla.log.debug(f'querying events using filter: {filter_condition}...') + result_iterator = self.model.scan(filter_condition=filter_condition, attributes_to_get=projection) + + ret = {} + + for event_record in result_iterator: + date_time_value = cla.utils.get_time_from_string(str(event_record.date_created)) + year = date_time_value.year + week_number = date_time_value.isocalendar()[1] + cla.log.debug(f'processing events - ' + f'{event_record.event_id} - ' + f'{event_record.event_type} - ' + f'{event_record.date_created} - ' + f'{year} - {week_number:02d}') + key = f'{year} {week_number:02d}' + if key in ret: + ret[key] += 1 + else: + ret[key] = 1 + return ret + def set_event_data(self, event_data: str): self.model.event_data = event_data self.model.event_data_lower = event_data.lower() diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 1f9a9a0d4..9c7431d34 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1602,6 +1602,22 @@ def get_formatted_time(the_time: datetime) -> str: return the_time.strftime("%Y-%m-%dT%H:%M:%S.%f%z") + "+0000" +def get_time_from_string(date_string: str) -> Optional[datetime]: + """ + Helper function to return the specified datetime object from an ISO standard format string + :return: + """ + # Try these formats + formats = ['%Y-%m-%d %H:%M:%S.%f%z', '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S.%f%z'] + for fmt in formats: + try: + return datetime.strptime(date_string, fmt) + except (ValueError, TypeError) as e: + pass + # print(f'unable to parse time {date_string} using {fmt}, error: {e}') + return None + + def get_public_email(user): """ Helper function to return public user email to send emails From 54d6b5e52c824c4f94f66909ce6fea9e76147ddd Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 20 Apr 2021 23:55:25 +0300 Subject: [PATCH 0235/1276] [#2842] Feature/Email for Approval Update (#2887) - Added email for CCLA+ICLA, CCLA+ICLA+ECLA, CCLA cases Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/email.go | 74 ++++++++++- cla-backend-go/signatures/models.go | 14 ++ cla-backend-go/signatures/repository.go | 166 ++++++++++++++++++------ cla-backend-go/tests/signatures_test.go | 23 ++-- 4 files changed, 219 insertions(+), 58 deletions(-) diff --git a/cla-backend-go/signatures/email.go b/cla-backend-go/signatures/email.go index 84a69759e..9a5b62eeb 100644 --- a/cla-backend-go/signatures/email.go +++ b/cla-backend-go/signatures/email.go @@ -3,6 +3,12 @@ package signatures +// ClaManagerInfoParams represents the CLAManagerInfo used inside of the Email Templates +type ClaManagerInfoParams struct { + Username string + Email string +} + //InvalidateSignatureTemplateParams representing params when invalidating icla/ecla type InvalidateSignatureTemplateParams struct { RecipientName string @@ -10,16 +16,70 @@ type InvalidateSignatureTemplateParams struct { ClaManager string RemovalCriteria string ProjectName string + CLAManagers []ClaManagerInfoParams + CLaManager string + CLAGroupName string + Company string } const ( - //InvalidateSignatureTemplateName is email template for InvalidateSignatureTemplate - InvalidateSignatureTemplateName = "InvalidateSignatureTemplate" - //InvalidateSignatureTemplate ... - InvalidateSignatureTemplate = ` + //InvalidateCCLAICLASignatureTemplateName is email template for InvalidateSignatureTemplate + InvalidateCCLAICLASignatureTemplateName = "InvalidateSignatureTemplate" + //InvalidateCCLAICLASignatureTemplate ... + InvalidateCCLAICLASignatureTemplate = ` +

      Hello {{.RecipientName}}

      +

      This is a notification email from EasyCLA regarding the CLA Group {{.ProjectName}}

      +

      You were previously authorized to contribute on behalf of your company {{COMPANY-NAME}} under its CLA. However, a CLA Manager has now removed you from the authorization list. This has additionally resulted in invalidating your current signed Individual CLA (ICLA).

      +

      As a result, you will no longer be able to contribute until you are again authorized under another signed CLA.

      +

      Please contact one of the CLA Managers from your company if you have questions about why you were removed. The CLA Managers from your company for this CLA are:

      +
        + {{range .CLAManagers}} +
      • {{.Username}} {{.Email}}
      • + {{end}} +
      + ` + + //InvalidateCCLASignatureTemplateName is email template upon approval list removal for ccla use case + InvalidateCCLASignatureTemplateName = "InvalidateCCLAICLASignatureTemplate" + //InvalidateCCLASignatureTemplate ... + InvalidateCCLASignatureTemplate = ` +

      Hello {{.RecipientName}}

      +

      This is a notification email from EasyCLA regarding the CLA Group {{.CLAGroupName}}.

      +

      You were previously authorized to contribute on behalf of your company {{.Company}} under its CLA. However, a CLA Manager {{.ClaManager}} has now removed you from the authorization list.

      +

      As a result, you will no longer be able to contribute until you are again authorized under another signed CLA.

      +

      Please contact one of the CLA Managers from your company if you have questions about why you were removed. The CLA Managers from your company for this CLA are:

      +
        + {{range .CLAManagers}} +
      • {{.Username}} {{.Email}}
      • + {{end}} +
      + ` + + //InvalidateICLASignatureTemplateName is email template upon approval list removal for ccla use case + InvalidateICLASignatureTemplateName = "InvalidateICLASignatureTemplate" + //InvalidateICLASignatureTemplate ... + InvalidateICLASignatureTemplate = ` +

      Hello {{.RecipientName}}

      +

      This is a notification email from EasyCLA regarding the CLA Group {{.CLAGroupName}}.

      +

      You had previously signed an Individual CLA (ICLA) to contribute to the project on your own behalf. However, the Project Manager has marked your ICLA as invalidated. This might be because the ICLA may have been signed in error, if your contributions should have been on behalf of your employer rather than on your own behalf.

      +

      As a result, you will no longer be able to contribute until you are again authorized under another signed CLA.

      +

      Please contact the Project Manager for this project if you have questions about why you were removed.

      + + ` + + //InvalidateCCLAICLAECLASignatureTemplateName is email template upon approval list removal for ccla use case + InvalidateCCLAICLAECLASignatureTemplateName = "InvalidateCCLAICLAECLASignatureTemplate" + //InvalidateCCLAICLAECLASignatureTemplate ... + InvalidateCCLAICLAECLASignatureTemplate = `

      Hello {{.RecipientName}}

      -

      This is a notification email from EasyCLA regarding the claGroup {{.ProjectName}}

      -

      The ICLA signature for {{.RecipientName}} has been invalidated.

      -

      Please contact Project Manager for the claGroup {{.ProjectName}} and/or CLA Manager from your company if you have more questions.

      +

      This is a notification email from EasyCLA regarding the CLA Group {{.CLAGroupName}}.

      +

      You were previously authorized to contribute on behalf of your company {{.Company}} under its CLA. However, a CLA Manager has now removed you from the authorization list. This has additionally resulted in invalidating your current signed Individual CLA (ICLA) and your acknowledgement.

      +

      As a result, you will no longer be able to contribute until you are again authorized under another signed CLA.

      +

      Please contact one of the CLA Managers from your company if you have questions about why you were removed. The CLA Managers from your company for this CLA are:

      +
        + {{range .CLAManagers}} +
      • {{.Username}} {{.Email}}
      • + {{end}} +
      ` ) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index ae46a2304..e3d8c6468 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -39,6 +39,9 @@ type ApprovalList struct { GerritICLAECLAs []string ICLAs []*models.IclaSignature ECLAs []*models.Signature + CLAManager *models.User + ManagersInfo []ClaManagerInfoParams + CCLASignature *models.Signature } // GerritUserResponse is a data structure to hold the gerrit user query response @@ -53,3 +56,14 @@ type ICLAUserResponse struct { ICLASignature *models.IclaSignature Error error } + +const ( + //CCLAICLA representing user removal under CCLA + ICLA + CCLAICLA = "CCLAICLA" + //CCLAICLAECLA representing user removal under CCLA + ICLA +ECLA + CCLAICLAECLA = "CCLAICLAECLA" + //CCLA representing normal use case of user under CCLA + CCLA = "ICLA" + //ICLA representing individual use case + ICLA = "ICLA" +) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 9e9d563bf..ba294813b 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2000,12 +2000,26 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return nil, errors.New(msg) } + // Get CLA Manager + var cclaManagers []ClaManagerInfoParams + for i := range cclaSignature.SignatureACL { + cclaManagers = append(cclaManagers, ClaManagerInfoParams{ + Username: utils.GetBestUsername(&cclaSignature.SignatureACL[i]), + Email: getBestEmail(&cclaSignature.SignatureACL[i]), + }) + } + // Keep track of existing company approvals approvalList := ApprovalList{ DomainApprovals: cclaSignature.DomainApprovalList, GHOrgApprovals: cclaSignature.GithubOrgApprovalList, GitHubUsernameApprovals: cclaSignature.GithubUsernameApprovalList, EmailApprovals: cclaSignature.EmailApprovalList, + CLAManager: claManager, + ICLAs: make([]*models.IclaSignature, 0), + ECLAs: make([]*models.Signature, 0), + ManagersInfo: cclaManagers, + CCLASignature: cclaSignature, } // Just grab and use the first one - need to figure out conflict resolution if more than one @@ -2085,6 +2099,8 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model for _, email := range params.RemoveEmailApprovalList { go func(email string) { defer wg.Done() + var iclas []*models.IclaSignature + var eclas []*models.Signature log.WithFields(f).Debugf("getting cla user record for email: %s ", email) userSearch, userErr := repo.usersRepo.SearchUsers("user_emails", email, false) if userErr != nil || userSearch == nil { @@ -2107,6 +2123,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if len(signs.Signatures) > 0 { approvalList.ECLAs = signs.Signatures + eclas = signs.Signatures } if len(userSearch.Users) > 0 { @@ -2136,6 +2153,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if result.Error == nil { log.WithFields(f).Debug("processing icla...") approvalList.ICLAs = append(approvalList.ICLAs, result.ICLASignature) + iclas = append(iclas, result.ICLASignature) } } @@ -2144,6 +2162,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Invalidate signatures repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) + // Send email + repo.sendEmail(ctx, email, &approvalList, iclas, eclas) + //update gerrit permissions gerritUser, getGerritUserErr := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) if getGerritUserErr != nil || gerritUser == nil { @@ -2262,6 +2283,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model for _, ghUsername := range params.RemoveGithubUsernameApprovalList { go func(ghUsername string) { defer wg.Done() + var iclas []*models.IclaSignature + var eclas []*models.Signature + criteria := &ApprovalCriteria{ GitHubUsername: ghUsername, } @@ -2273,6 +2297,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if signs.Signatures != nil { approvalList.ECLAs = signs.Signatures + eclas = signs.Signatures } // Get ICLAs claUser, claErr := repo.usersRepo.GetUserByGitHubUsername(ghUsername) @@ -2286,7 +2311,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debugf("unable to get icla signature for user with ghUsername: %s ", ghUsername) } if icla != nil { - // Convert to IclSignature instance to leverage invalidateSignatrues helper function + // Convert to IclSignature instance to leverage invalidateSignatures helper function approvalList.ICLAs = []*models.IclaSignature{{ GithubUsername: icla.UserGHUsername, LfUsername: icla.UserLFID, @@ -2297,6 +2322,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) + // Send Email + repo.sendEmail(ctx, getBestEmail(claUser), &approvalList, iclas, eclas) + }(ghUsername) } wg.Wait() @@ -2416,6 +2444,95 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return updatedSig, nil } +// sendEmail is a helper function used to render email for (CCLA, ICLA, ECLA cases) +func (repo repository) sendEmail(ctx context.Context, email string, approvalList *ApprovalList, iclas []*models.IclaSignature, eclas []*models.Signature) { + f := logrus.Fields{ + "functionName": "v1.signatures.repository.sendEmail", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + companyName := "" + company, companyErr := repo.companyRepo.GetCompany(ctx, approvalList.CompanyID) + if companyErr != nil { + log.WithFields(f).Debugf("unable to get company") + } + if company != nil { + companyName = company.CompanyName + } + + params := InvalidateSignatureTemplateParams{ + Company: companyName, + RecipientName: email, + ClaManager: utils.GetBestUsername(approvalList.CLAManager), + CLAManagers: approvalList.ManagersInfo, + CLAGroupName: approvalList.ClaGroupName, + } + + // check for signature type (CCLA, ICLA, ECLA) + var removalType string = "" + + // case 1 CCLA + if len(iclas) == 0 && len(eclas) == 0 { + removalType = CCLA + } else if len(iclas) > 0 && len(eclas) == 0 { + // case 2 ccla + icla + removalType = CCLAICLA + } else if len(iclas) > 0 && len(eclas) > 0 { + // case 3 ccla + icla + ecla + removalType = CCLAICLAECLA + } + + // Send CCLA Email + if removalType == CCLA { + subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) + log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateCCLASignatureTemplateName, InvalidateCCLASignatureTemplate, params) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) + } else { + err := utils.SendEmail(subject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send approval list update email to : %s ", email) + } + } + } else if removalType == ICLA { + subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) + log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateICLASignatureTemplateName, InvalidateICLASignatureTemplate, params) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) + } else { + err := utils.SendEmail(subject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send approval list update email to : %s ", email) + } + } + } else if removalType == CCLAICLA { + subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) + log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateCCLAICLASignatureTemplateName, InvalidateCCLASignatureTemplate, params) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) + } else { + err := utils.SendEmail(subject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send approval list update email to : %s ", email) + } + } + } else if removalType == CCLAICLAECLA { + subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) + log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateCCLAICLAECLASignatureTemplateName, InvalidateCCLAICLAECLASignatureTemplate, params) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) + } else { + err := utils.SendEmail(subject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send approval list update email to : %s ", email) + } + } + } +} + // invalidateSignatures is a helper function that invalidates signature records based on approval list func (repo repository) invalidateSignatures(ctx context.Context, approvalList *ApprovalList, claManager *models.User, eventArgs *events.LogEventArgs) { f := logrus.Fields{ @@ -2442,32 +2559,14 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A log.WithFields(f).Warnf("no signatureReferenceID for signature: %+v ", signature) return } + user, verifyErr := repo.verifyUserApprovals(ctx, signature.SignatureReferenceID, signature.SignatureID, claManager, approvalList) if verifyErr != nil { log.WithFields(f).Warnf("unable to verify user: %s ", signature.SignatureReferenceID) return } + // Map representing CLA types against email .... email := getBestEmail(user) - // send email - subject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s on %s", email, approvalList.ClaGroupName) - log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) - body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, - InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ - RecipientName: utils.GetBestUsername(user), - ClaType: utils.ClaTypeICLA, - ClaManager: utils.GetBestUsername(claManager), - RemovalCriteria: approvalList.Criteria, - }) - if renderErr != nil { - log.WithFields(f).Debugf("unable to render invalidation notice for user : %s ", email) - return - } - err := utils.SendEmail(subject, body, []string{email}) - if err != nil { - log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) - return - } - // Log Event eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ SignatureID: icla.SignatureID, @@ -2500,25 +2599,6 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A return } email := getBestEmail(user) - // send email - subject := fmt.Sprintf("EasyCLA: Employee Acknowledgement invalidated for %s on %s", email, approvalList.ClaGroupName) - log.WithFields(f).Debugf("sending invalidation email to user: %s ", email) - body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateSignatureTemplateName, - InvalidateSignatureTemplate, InvalidateSignatureTemplateParams{ - RecipientName: utils.GetBestUsername(user), - ClaType: utils.ClaTypeECLA, - ClaManager: utils.GetBestUsername(claManager), - RemovalCriteria: approvalList.Criteria, - }) - if renderErr != nil { - log.WithFields(f).Debugf("unable to send invalidation signature email to : %s ", email) - return - } - err := utils.SendEmail(subject, body, []string{email}) - if err != nil { - log.WithFields(f).Debugf("unable to send email invalidation notice to user: %s", email) - return - } // Log Event eventArgs.EventData = &events.SignatureInvalidatedApprovalRejectionEventData{ SignatureID: ecla.SignatureID, @@ -2570,6 +2650,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur log.WithFields(f).Warnf("unable to get user record for ID: %s ", userID) return nil, err } + email := getBestEmail(user) authUser := auth.User{ Email: claManager.LfEmail, @@ -2578,10 +2659,9 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur if approvalList.Criteria == utils.EmailDomainCriteria { // Handle Domains - email := getBestEmail(user) domain := strings.Split(email, "@")[1] if utils.StringInSlice(domain, approvalList.DomainApprovals) { - if !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) && !utils.StringInSlice(getBestEmail(user), approvalList.EmailApprovals) { + if !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) && !utils.StringInSlice(email, approvalList.EmailApprovals) { //Invalidate record note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), utils.EmailDomainCriteria) err := repo.InvalidateProjectRecord(ctx, signatureID, note) @@ -2610,6 +2690,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur if utils.StringInSlice(user.GithubUsername, approvalList.GHUsernames) { if !utils.StringInSlice(getBestEmail(user), approvalList.EmailApprovals) && !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) { //Invalidate record + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), utils.GitHubOrgCriteria) err := repo.InvalidateProjectRecord(ctx, signatureID, note) if err != nil { @@ -2625,6 +2706,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur log.WithFields(f).Warnf("unable to invalidate record for signatureID: %s ", signatureID) return user, err } + } return user, nil diff --git a/cla-backend-go/tests/signatures_test.go b/cla-backend-go/tests/signatures_test.go index b2eed5317..fac60faa7 100644 --- a/cla-backend-go/tests/signatures_test.go +++ b/cla-backend-go/tests/signatures_test.go @@ -11,19 +11,24 @@ import ( "github.com/stretchr/testify/assert" ) -//TestInvalidateSignatureTemplate validates email sent when signature is invalidated -func TestInvalidateSignatureTemplate(t *testing.T) { +func TestCCLAInvalidateSignatureTemplate(t *testing.T) { params := signatures.InvalidateSignatureTemplateParams{ - RecipientName: "TestUser", - ClaType: utils.ClaTypeICLA, + RecipientName: "CCLATest", + ClaType: utils.ClaTypeCCLA, ClaManager: "claManager", + CLAGroupName: "claGroup test", RemovalCriteria: "email removal", - ProjectName: "testProject", + CLAManagers: []signatures.ClaManagerInfoParams{ + {Username: "mgr_one", Email: "mgr_one_email"}, + {Username: "mgr_two", Email: "mgr_two_email"}, + }, + Company: "TestCompany", } - result, err := utils.RenderTemplate(utils.V1, signatures.InvalidateSignatureTemplateName, signatures.InvalidateSignatureTemplate, params) + result, err := utils.RenderTemplate(utils.V2, signatures.InvalidateCCLASignatureTemplateName, signatures.InvalidateCCLASignatureTemplate, params) assert.NoError(t, err) - assert.Contains(t, result, "Hello TestUser") - assert.Contains(t, result, "The ICLA signature for TestUser has been invalidated.") - assert.Contains(t, result, "Please contact Project Manager for the claGroup testProject and/or CLA Manager from your company if you have more questions.") + assert.Contains(t, result, "This is a notification email from EasyCLA regarding the CLA Group claGroup test") + assert.Contains(t, result, "You were previously authorized to contribute on behalf of your company TestCompany under its CLA. However, a CLA Manager claManager has now removed you from the authorization list") + assert.Contains(t, result, "
    4. mgr_one mgr_one_email
    5. ") + } From 944748504a76b38df2cb7ef48005a8450c26c589 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Apr 2021 15:44:53 -0700 Subject: [PATCH 0236/1276] Added Python Note Field for CLA Group (#2890) Signed-off-by: David Deal --- cla-backend/cla/models/dynamo_models.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 36949775c..a462d4eeb 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -1023,6 +1023,7 @@ class Meta: project_live = BooleanAttribute(default=False) foundation_sfid = UnicodeAttribute(null=True) root_project_repositories_count = NumberAttribute(null=True) + note = UnicodeAttribute(null=True) # Indexes project_external_id_index = ExternalProjectIndex() project_name_search_index = ProjectNameIndex() @@ -1050,6 +1051,7 @@ def __init__( project_ccla_requires_icla_signature=False, project_acl=None, project_live=False, + note=None ): super(Project).__init__() self.model = ProjectModel() @@ -1062,6 +1064,7 @@ def __init__( self.model.project_ccla_requires_icla_signature = project_ccla_requires_icla_signature self.model.project_acl = project_acl self.model.project_live = project_live + self.model.note = note def __str__(self): return ( @@ -1270,6 +1273,9 @@ def get_date_created(self): def get_date_modified(self): return self.model.date_modified + def get_note(self) -> Optional[str]: + return self.model.note + def set_project_id(self, project_id): self.model.project_id = str(project_id) @@ -1297,6 +1303,9 @@ def set_project_ccla_enabled(self, project_ccla_enabled): def set_project_live(self, project_live): self.model.project_live = project_live + def set_note(self, note: str) -> None: + self.model.note = note + def add_project_individual_document(self, document): self.model.project_individual_documents.append(document.model) @@ -2071,6 +2080,15 @@ def get_repository_models_by_repository_sfdc_id(self, project_sfid) -> List[Repo repositories.append(repository) return repositories + def get_repository_models_by_repository_cla_group_id(self, cla_group_id: str) -> List[Repository]: + repository_generator = self.model.repository_project_index.query(cla_group_id) + repositories = [] + for repository_model in repository_generator: + repository = Repository() + repository.model = repository_model + repositories.append(repository) + return repositories + def delete(self): self.model.delete() From 79a02fc67d3ddec8e997aad05bb845db30a8ccb3 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Thu, 22 Apr 2021 19:39:50 +0300 Subject: [PATCH 0237/1276] [#2884] adding github actions handlers for repository events (#2892) Signed-off-by: makkalot --- cla-backend-go/Makefile | 4 + cla-backend-go/cmd/server.go | 3 +- cla-backend-go/emails/github_actions.go | 144 +++++++++ cla-backend-go/emails/github_actions_test.go | 130 ++++++++ cla-backend-go/emails/prefill.go | 17 + cla-backend-go/emails/service.go | 49 +++ cla-backend-go/events/event_data.go | 71 +++++ cla-backend-go/events/event_types.go | 2 + cla-backend-go/events/mock.go | 169 ++++++++++ .../github/branch_protection/mock.go | 3 +- cla-backend-go/github_organizations/mock.go | 110 +++---- .../repositories/mock/mock_repository.go | 11 +- .../repositories/mock/mock_service.go | 3 +- cla-backend-go/v2/github_activity/service.go | 295 +++++++++++++++++- .../v2/github_activity/service_test.go | 187 +++++++++++ 15 files changed, 1130 insertions(+), 68 deletions(-) create mode 100644 cla-backend-go/emails/github_actions.go create mode 100644 cla-backend-go/emails/github_actions_test.go create mode 100644 cla-backend-go/emails/service.go create mode 100644 cla-backend-go/events/mock.go create mode 100644 cla-backend-go/v2/github_activity/service_test.go diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index e4579a430..ea7d1476c 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -179,6 +179,10 @@ mock: @cd $(MAKEFILE_DIR) && mkdir -p repositories/mock @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -source=repositories/service.go -package=mock -destination=repositories/mock/mock_service.go @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -source=repositories/repository.go -package=mock -destination=repositories/mock/mock_repository.go + # github organizations + @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -package=github_organizations -destination=github_organizations/mock.go -self_package=github.com/communitybridge/easycla/cla-backend-go/github_organizations github.com/communitybridge/easycla/cla-backend-go/github_organizations Repository + # events + @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -package=events -destination=events/mock.go -self_package=github.com/communitybridge/easycla/cla-backend-go/events github.com/communitybridge/easycla/cla-backend-go/events Service # mocks for github @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -package=branch_protection -destination=github/branch_protection/mock.go -self_package=github.com/communitybridge/easycla/cla-backend-go/github/branch_protection github.com/communitybridge/easycla/cla-backend-go/github/branch_protection CombinedRepository diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 08761f72c..97ae9b630 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -275,6 +275,7 @@ func server(localMode bool) http.Handler { templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) v1ProjectService := project.NewService(v1CLAGroupRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) emailTemplateService := emails.NewEmailTemplateService(v1CLAGroupRepo, projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) + emailService := emails.NewService(emailTemplateService, v1ProjectService) v2ProjectService := v2Project.NewService(v1ProjectService, v1CLAGroupRepo, projectClaGroupRepo) v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, v1CLAGroupRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) @@ -291,7 +292,7 @@ func server(localMode bool) http.Handler { githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, v1ProjectService) - v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, eventsService, autoEnableService) + v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, githubOrganizationsRepo, eventsService, autoEnableService, emailService) v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, projectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) diff --git a/cla-backend-go/emails/github_actions.go b/cla-backend-go/emails/github_actions.go new file mode 100644 index 000000000..e28140b8a --- /dev/null +++ b/cla-backend-go/emails/github_actions.go @@ -0,0 +1,144 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +// GithubRepositoryActionTemplateParams is email params for GithubRepositoryActionTemplate +type GithubRepositoryActionTemplateParams struct { + CommonEmailParams + CLAGroupTemplateParams + RepositoryName string +} + +// GithubRepositoryDisabledTemplateParams is email params for GithubRepositoryDisabledTemplate +type GithubRepositoryDisabledTemplateParams struct { + GithubRepositoryActionTemplateParams + GithubAction string +} + +const ( + // GithubRepositoryDisabledTemplateName is email template name for GithubRepositoryDisabledTemplate + GithubRepositoryDisabledTemplateName = "GithubRepositoryDisabledTemplate" + // GithubRepositoryDisabledTemplate is email template for + GithubRepositoryDisabledTemplate = ` +

      Hello {{.RecipientName}},

      +

      This is a notification email from EasyCLA regarding the Github Repository {{.RepositoryName}} associated with the CLA Group {{.CLAGroupName}}.

      +

      EasyCLA was notified that the Github Repository {{.RepositoryName}} was {{.GithubAction}} from Github. It's now disabled from EasyCLA platform.

      +` +) + +// RenderGithubRepositoryDisabledTemplate renders GithubRepositoryDisabledTemplate +func RenderGithubRepositoryDisabledTemplate(svc EmailTemplateService, claGroupID string, params GithubRepositoryDisabledTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromCLAGroup(claGroupID) + if err != nil { + return "", err + } + + // assign the prefilled struct + params.CLAGroupTemplateParams = claGroupParams + return RenderTemplate(params.CLAGroupTemplateParams.Version, GithubRepositoryDisabledTemplateName, GithubRepositoryDisabledTemplate, params) +} + +// GithubRepositoryArchivedTemplateParams renders GithubRepositoryArchivedTemplate +type GithubRepositoryArchivedTemplateParams struct { + GithubRepositoryActionTemplateParams +} + +const ( + // GithubRepositoryArchivedTemplateName is email template name for GithubRepositoryArchivedTemplate + GithubRepositoryArchivedTemplateName = "GithubRepositoryArchivedTemplate" + // GithubRepositoryArchivedTemplate is email template for + GithubRepositoryArchivedTemplate = ` +

      Hello {{.RecipientName}},

      +

      This is a notification email from EasyCLA regarding the Github Repository {{.RepositoryName}} associated with the CLA Group {{.CLAGroupName}}.

      +

      EasyCLA was notified that the Github Repository {{.RepositoryName}} was archived from Github. No action was taken on EasyCLA platform.

      +` +) + +// RenderGithubRepositoryArchivedTemplate renders GithubRepositoryArchivedTemplate +func RenderGithubRepositoryArchivedTemplate(svc EmailTemplateService, claGroupID string, params GithubRepositoryArchivedTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromCLAGroup(claGroupID) + if err != nil { + return "", err + } + + // assign the prefilled struct + params.CLAGroupTemplateParams = claGroupParams + return RenderTemplate(params.CLAGroupTemplateParams.Version, GithubRepositoryArchivedTemplateName, GithubRepositoryArchivedTemplate, params) +} + +// GithubRepositoryRenamedTemplateParams is email params for GithubRepositoryRenamedTemplate +type GithubRepositoryRenamedTemplateParams struct { + GithubRepositoryActionTemplateParams + OldRepositoryName string + NewRepositoryName string +} + +const ( + // GithubRepositoryRenamedTemplateName is email template name for GithubRepositoryRenamedTemplate + GithubRepositoryRenamedTemplateName = "GithubRepositoryRenamedTemplate" + // GithubRepositoryRenamedTemplate is email template for + GithubRepositoryRenamedTemplate = ` +

      Hello {{.RecipientName}},

      +

      This is a notification email from EasyCLA regarding the Github Repository {{.RepositoryName}} associated with the CLA Group {{.CLAGroupName}}.

      +

      EasyCLA was notified that the Github Repository {{.OldRepositoryName}} was renamed to {{.NewRepositoryName}} from Github. The change was reflected to EasyCLA platform.

      +` +) + +// RenderGithubRepositoryRenamedTemplate renders GithubRepositoryRenamedTemplate +func RenderGithubRepositoryRenamedTemplate(svc EmailTemplateService, claGroupID string, params GithubRepositoryRenamedTemplateParams) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromCLAGroup(claGroupID) + if err != nil { + return "", err + } + + // assign the prefilled struct + params.CLAGroupTemplateParams = claGroupParams + return RenderTemplate(params.CLAGroupTemplateParams.Version, GithubRepositoryRenamedTemplateName, GithubRepositoryRenamedTemplate, params) +} + +// GithubRepositoryTransferredTemplateParams is email params GithubRepositoryTransferredTemplate +type GithubRepositoryTransferredTemplateParams struct { + GithubRepositoryActionTemplateParams + OldGithubOrgName string + NewGithubOrgName string +} + +const ( + // GithubRepositoryTransferredTemplateName is email template name for GithubRepositoryTransferredTemplate + GithubRepositoryTransferredTemplateName = "GithubRepositoryTransferredTemplate" + // GithubRepositoryTransferredTemplate is email template for + GithubRepositoryTransferredTemplate = ` +

      Hello {{.RecipientName}},

      +

      This is a notification email from EasyCLA regarding the Github Repository {{.RepositoryName}} associated with the CLA Group {{.CLAGroupName}}.

      +

      EasyCLA was notified that the Github Repository {{.RepositoryName}} was transferred from {{.OldGithubOrgName}} Organization to {{.NewGithubOrgName}} Organization from Github. The change was reflected to EasyCLA platform.

      +` +) + +const ( + // GithubRepositoryTransferredFailedTemplateName is email template name for GithubRepositoryTransferredFailedTemplate + GithubRepositoryTransferredFailedTemplateName = "GithubRepositoryTransferredFailedTemplate" + // GithubRepositoryTransferredFailedTemplate is email template for + GithubRepositoryTransferredFailedTemplate = ` +

      Hello {{.RecipientName}},

      +

      This is a notification email from EasyCLA regarding the Github Repository {{.RepositoryName}} associated with the CLA Group {{.CLAGroupName}}.

      +

      EasyCLA was notified that the Github Repository {{.RepositoryName}} was transferred from {{.OldGithubOrgName}} Organization to {{.NewGithubOrgName}} Organization from Github.

      +

      However, we detected that EasyCLA is not enabled for the new Github Organization {{.NewGithubOrgName}}. The Github Repository {{.RepositoryName}} is now disabled from EasyCLA platform.

      +` +) + +// RenderGithubRepositoryTransferredTemplate renders GithubRepositoryTransferredFailedTemplate or GithubRepositoryTransferredTemplate +func RenderGithubRepositoryTransferredTemplate(svc EmailTemplateService, claGroupID string, params GithubRepositoryTransferredTemplateParams, success bool) (string, error) { + claGroupParams, err := svc.GetCLAGroupTemplateParamsFromCLAGroup(claGroupID) + if err != nil { + return "", err + } + + // assign the prefilled struct + params.CLAGroupTemplateParams = claGroupParams + if success { + return RenderTemplate(params.CLAGroupTemplateParams.Version, GithubRepositoryTransferredTemplateName, GithubRepositoryTransferredTemplate, params) + } + return RenderTemplate(params.CLAGroupTemplateParams.Version, GithubRepositoryTransferredFailedTemplateName, GithubRepositoryTransferredFailedTemplate, params) + +} diff --git a/cla-backend-go/emails/github_actions_test.go b/cla-backend-go/emails/github_actions_test.go new file mode 100644 index 000000000..89418ee98 --- /dev/null +++ b/cla-backend-go/emails/github_actions_test.go @@ -0,0 +1,130 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestGithubRepositoryDisabledTemplate(t *testing.T) { + params := GithubRepositoryDisabledTemplateParams{ + GithubRepositoryActionTemplateParams: GithubRepositoryActionTemplateParams{ + CommonEmailParams: CommonEmailParams{ + RecipientName: "CLA Manager", + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + CLAGroupName: "JohnsProject", + }, + RepositoryName: "johnsRepository", + }, + GithubAction: "deleted", + } + + result, err := RenderTemplate(utils.V2, GithubRepositoryDisabledTemplateName, GithubRepositoryDisabledTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello CLA Manager") + assert.Contains(t, result, "regarding the Github Repository johnsRepository") + assert.Contains(t, result, "associated with the CLA Group JohnsProject") + assert.Contains(t, result, "Github Repository johnsRepository was deleted") +} + +func TestGithubRepositoryArchivedTemplate(t *testing.T) { + params := GithubRepositoryArchivedTemplateParams{ + GithubRepositoryActionTemplateParams: GithubRepositoryActionTemplateParams{ + CommonEmailParams: CommonEmailParams{ + RecipientName: "CLA Manager", + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + CLAGroupName: "JohnsProject", + }, + RepositoryName: "johnsRepository", + }, + } + + result, err := RenderTemplate(utils.V2, GithubRepositoryArchivedTemplateName, GithubRepositoryArchivedTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello CLA Manager") + assert.Contains(t, result, "regarding the Github Repository johnsRepository") + assert.Contains(t, result, "associated with the CLA Group JohnsProject") + assert.Contains(t, result, "Github Repository johnsRepository was archived") +} + +func TestGithubRepositoryRenamedTemplate(t *testing.T) { + params := GithubRepositoryRenamedTemplateParams{ + GithubRepositoryActionTemplateParams: GithubRepositoryActionTemplateParams{ + CommonEmailParams: CommonEmailParams{ + RecipientName: "CLA Manager", + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + CLAGroupName: "JohnsProject", + }, + RepositoryName: "johnsNewRepository", + }, + OldRepositoryName: "johnsOldRepository", + NewRepositoryName: "johnsNewRepository", + } + + result, err := RenderTemplate(utils.V2, GithubRepositoryRenamedTemplateName, GithubRepositoryRenamedTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello CLA Manager") + assert.Contains(t, result, "regarding the Github Repository johnsNewRepository") + assert.Contains(t, result, "associated with the CLA Group JohnsProject") + assert.Contains(t, result, "Github Repository johnsOldRepository was renamed to johnsNewRepository") +} + +func TestGithubRepositoryTransferredTemplate(t *testing.T) { + params := GithubRepositoryTransferredTemplateParams{ + GithubRepositoryActionTemplateParams: GithubRepositoryActionTemplateParams{ + CommonEmailParams: CommonEmailParams{ + RecipientName: "CLA Manager", + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + CLAGroupName: "JohnsProject", + }, + RepositoryName: "johnsNewRepository", + }, + OldGithubOrgName: "johnsOldGithubOrg", + NewGithubOrgName: "johnsNewGithubOrg", + } + + result, err := RenderTemplate(utils.V2, GithubRepositoryTransferredTemplateName, GithubRepositoryTransferredTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello CLA Manager") + assert.Contains(t, result, "regarding the Github Repository johnsNewRepository") + assert.Contains(t, result, "associated with the CLA Group JohnsProject") + assert.Contains(t, result, "Github Repository johnsNewRepository was transferred from johnsOldGithubOrg Organization to johnsNewGithubOrg Organization") +} + +func TestGithubRepositoryTransferredFailedTemplate(t *testing.T) { + params := GithubRepositoryTransferredTemplateParams{ + GithubRepositoryActionTemplateParams: GithubRepositoryActionTemplateParams{ + CommonEmailParams: CommonEmailParams{ + RecipientName: "CLA Manager", + }, + CLAGroupTemplateParams: CLAGroupTemplateParams{ + CLAGroupName: "JohnsProject", + }, + RepositoryName: "johnsNewRepository", + }, + OldGithubOrgName: "johnsOldGithubOrg", + NewGithubOrgName: "johnsNewGithubOrg", + } + + result, err := RenderTemplate(utils.V2, GithubRepositoryTransferredFailedTemplateName, GithubRepositoryTransferredFailedTemplate, + params) + assert.NoError(t, err) + assert.Contains(t, result, "Hello CLA Manager") + assert.Contains(t, result, "regarding the Github Repository johnsNewRepository") + assert.Contains(t, result, "associated with the CLA Group JohnsProject") + assert.Contains(t, result, "Github Repository johnsNewRepository was transferred from johnsOldGithubOrg Organization to johnsNewGithubOrg Organization") + assert.Contains(t, result, "EasyCLA is not enabled for the new Github Organization johnsNewGithubOrg") + assert.Contains(t, result, "The Github Repository johnsNewRepository is now disabled") +} diff --git a/cla-backend-go/emails/prefill.go b/cla-backend-go/emails/prefill.go index 790a1ef14..507330f15 100644 --- a/cla-backend-go/emails/prefill.go +++ b/cla-backend-go/emails/prefill.go @@ -20,6 +20,7 @@ import ( type EmailTemplateService interface { PrefillV2CLAProjectParams(projectSFIDs []string) ([]CLAProjectParams, error) GetCLAGroupTemplateParamsFromProjectSFID(claGroupVersion, projectSFID string) (CLAGroupTemplateParams, error) + GetCLAGroupTemplateParamsFromCLAGroup(claGroupID string) (CLAGroupTemplateParams, error) } type emailTemplateServiceProvider struct { @@ -86,6 +87,22 @@ func (s *emailTemplateServiceProvider) GetCLAGroupTemplateParamsFromProjectSFID( return s.getV1CLAGroupTemplateParamsFromProjectSFID(projectSFID) } +// GetCLAGroupTemplateParamsFromCLAGroup fills up the CLAGroupTemplateParams with the basic information, it's missing the +// project information, if needed can be added later on... +func (s *emailTemplateServiceProvider) GetCLAGroupTemplateParamsFromCLAGroup(claGroupID string) (CLAGroupTemplateParams, error) { + claGroupModel, err := s.claGroupRepository.GetCLAGroupByID(context.Background(), claGroupID, false) + if err != nil { + return CLAGroupTemplateParams{}, err + } + + params := CLAGroupTemplateParams{} + params.CLAGroupName = claGroupModel.ProjectName + params.CorporateConsole = s.corporateConsoleV2 + params.Version = claGroupModel.Version + + return params, nil +} + func (s *emailTemplateServiceProvider) getV2CLAGroupTemplateParamsFromProjectSFID(projectSFID string) (CLAGroupTemplateParams, error) { projectCLAGroup, err := s.repository.GetClaGroupIDForProject(projectSFID) if err != nil { diff --git a/cla-backend-go/emails/service.go b/cla-backend-go/emails/service.go new file mode 100644 index 000000000..dfefe72a3 --- /dev/null +++ b/cla-backend-go/emails/service.go @@ -0,0 +1,49 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package emails + +import ( + "context" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/utils" +) + +// Service is a service with some helper functions for rendering templates and also sending emails +type Service interface { + EmailTemplateService + NotifyClaManagersForClaGroupID(ctx context.Context, claGrpoupID, subject, body string) error +} + +type service struct { + EmailTemplateService + claService project.Service +} + +// NewService is constructor for emails.Service +func NewService(emailTemplateService EmailTemplateService, claService project.Service) Service { + return &service{ + EmailTemplateService: emailTemplateService, + claService: claService, + } +} + +func (s *service) NotifyClaManagersForClaGroupID(ctx context.Context, claGrpoupID, subject, body string) error { + claManagers, err := s.claService.GetCLAManagers(ctx, claGrpoupID) + if err != nil { + return fmt.Errorf("fetching cla manager for cla group : %s failed : %v", claGrpoupID, err) + } + + if len(claManagers) == 0 { + return fmt.Errorf("no cla managers registered for the claGroup : %s, none to notify", claGrpoupID) + } + + var recipientEmails []string + for _, claManager := range claManagers { + recipientEmails = append(recipientEmails, claManager.UserEmail) + } + + return utils.SendEmail(subject, body, recipientEmails) +} diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index cfa984ff9..e06b6ffca 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -38,6 +38,19 @@ type RepositoryDisabledEventData struct { RepositoryName string } +// RepositoryRenamedEventData . . . +type RepositoryRenamedEventData struct { + NewRepositoryName string + OldRepositoryName string +} + +// RepositoryTransferredEventData . . . +type RepositoryTransferredEventData struct { + RepositoryName string + OldGithubOrgName string + NewGithubOrgName string +} + // RepositoryUpdatedEventData . . . type RepositoryUpdatedEventData struct { RepositoryName string @@ -442,6 +455,26 @@ func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) return data, true } +// GetEventDetailsString . . . +func (ed *RepositoryRenamedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitHub repository renamed from %s to %s for the project %s", ed.OldRepositoryName, ed.NewRepositoryName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventDetailsString . . . +func (ed *RepositoryTransferredEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitHub repository : %s transferred from %s to %s Github Organization for the project %s", ed.RepositoryName, ed.OldGithubOrgName, ed.NewGithubOrgName, args.ProjectName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventDetailsString . . . func (ed *RepositoryUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was updated for the project %s", ed.RepositoryName, args.ProjectName) @@ -1107,6 +1140,44 @@ func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) return data, true } +// GetEventSummaryString . . . +func (ed *RepositoryRenamedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitHub repository was renamed from %s to %s", ed.OldRepositoryName, ed.NewRepositoryName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString . . . +func (ed *RepositoryTransferredEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitHub repository : %s was transferred from %s to %s Github Organization", ed.RepositoryName, ed.OldGithubOrgName, ed.NewGithubOrgName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventSummaryString . . . func (ed *RepositoryUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was updated", ed.RepositoryName) diff --git a/cla-backend-go/events/event_types.go b/cla-backend-go/events/event_types.go index ae6f84e73..4f36d5459 100644 --- a/cla-backend-go/events/event_types.go +++ b/cla-backend-go/events/event_types.go @@ -32,6 +32,8 @@ const ( UserDeleted = "user.deleted" RepositoryAdded = "repository.added" + RepositoryRenamed = "repository.renamed" + RepositoryTransferred = "repository.transferred" RepositoryDisabled = "repository.disabled" RepositoryUpdated = "repository.updated" RepositoryBranchProtectionAdded = "repository.branchprotection.updated" diff --git a/cla-backend-go/events/mock.go b/cla-backend-go/events/mock.go new file mode 100644 index 000000000..93c0baa68 --- /dev/null +++ b/cla-backend-go/events/mock.go @@ -0,0 +1,169 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/communitybridge/easycla/cla-backend-go/events (interfaces: Service) + +// Package events is a generated GoMock package. +package events + +import ( + context "context" + models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + events0 "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockService is a mock of Service interface +type MockService struct { + ctrl *gomock.Controller + recorder *MockServiceMockRecorder +} + +// MockServiceMockRecorder is the mock recorder for MockService +type MockServiceMockRecorder struct { + mock *MockService +} + +// NewMockService creates a new mock instance +func NewMockService(ctrl *gomock.Controller) *MockService { + mock := &MockService{ctrl: ctrl} + mock.recorder = &MockServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockService) EXPECT() *MockServiceMockRecorder { + return m.recorder +} + +// GetClaGroupEvents mocks base method +func (m *MockService) GetClaGroupEvents(arg0 string, arg1 *string, arg2 *int64, arg3 bool, arg4 *string) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClaGroupEvents", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClaGroupEvents indicates an expected call of GetClaGroupEvents +func (mr *MockServiceMockRecorder) GetClaGroupEvents(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClaGroupEvents", reflect.TypeOf((*MockService)(nil).GetClaGroupEvents), arg0, arg1, arg2, arg3, arg4) +} + +// GetCompanyClaGroupEvents mocks base method +func (m *MockService) GetCompanyClaGroupEvents(arg0, arg1, arg2 string, arg3 *string, arg4 *int64, arg5 bool) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCompanyClaGroupEvents", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCompanyClaGroupEvents indicates an expected call of GetCompanyClaGroupEvents +func (mr *MockServiceMockRecorder) GetCompanyClaGroupEvents(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCompanyClaGroupEvents", reflect.TypeOf((*MockService)(nil).GetCompanyClaGroupEvents), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// GetCompanyEvents mocks base method +func (m *MockService) GetCompanyEvents(arg0, arg1 string, arg2 *string, arg3 *int64, arg4 bool) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCompanyEvents", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCompanyEvents indicates an expected call of GetCompanyEvents +func (mr *MockServiceMockRecorder) GetCompanyEvents(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCompanyEvents", reflect.TypeOf((*MockService)(nil).GetCompanyEvents), arg0, arg1, arg2, arg3, arg4) +} + +// GetCompanyFoundationEvents mocks base method +func (m *MockService) GetCompanyFoundationEvents(arg0, arg1, arg2 string, arg3 *string, arg4 *int64, arg5 bool) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCompanyFoundationEvents", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCompanyFoundationEvents indicates an expected call of GetCompanyFoundationEvents +func (mr *MockServiceMockRecorder) GetCompanyFoundationEvents(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCompanyFoundationEvents", reflect.TypeOf((*MockService)(nil).GetCompanyFoundationEvents), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// GetFoundationEvents mocks base method +func (m *MockService) GetFoundationEvents(arg0 string, arg1 *string, arg2 *int64, arg3 bool, arg4 *string) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFoundationEvents", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFoundationEvents indicates an expected call of GetFoundationEvents +func (mr *MockServiceMockRecorder) GetFoundationEvents(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFoundationEvents", reflect.TypeOf((*MockService)(nil).GetFoundationEvents), arg0, arg1, arg2, arg3, arg4) +} + +// GetRecentEvents mocks base method +func (m *MockService) GetRecentEvents(arg0 *int64) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRecentEvents", arg0) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRecentEvents indicates an expected call of GetRecentEvents +func (mr *MockServiceMockRecorder) GetRecentEvents(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRecentEvents", reflect.TypeOf((*MockService)(nil).GetRecentEvents), arg0) +} + +// LogEvent mocks base method +func (m *MockService) LogEvent(arg0 *LogEventArgs) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "LogEvent", arg0) +} + +// LogEvent indicates an expected call of LogEvent +func (mr *MockServiceMockRecorder) LogEvent(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogEvent", reflect.TypeOf((*MockService)(nil).LogEvent), arg0) +} + +// LogEventWithContext mocks base method +func (m *MockService) LogEventWithContext(arg0 context.Context, arg1 *LogEventArgs) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "LogEventWithContext", arg0, arg1) +} + +// LogEventWithContext indicates an expected call of LogEventWithContext +func (mr *MockServiceMockRecorder) LogEventWithContext(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogEventWithContext", reflect.TypeOf((*MockService)(nil).LogEventWithContext), arg0, arg1) +} + +// SearchEvents mocks base method +func (m *MockService) SearchEvents(arg0 *events0.SearchEventsParams) (*models.EventList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SearchEvents", arg0) + ret0, _ := ret[0].(*models.EventList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SearchEvents indicates an expected call of SearchEvents +func (mr *MockServiceMockRecorder) SearchEvents(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchEvents", reflect.TypeOf((*MockService)(nil).SearchEvents), arg0) +} diff --git a/cla-backend-go/github/branch_protection/mock.go b/cla-backend-go/github/branch_protection/mock.go index 456925c0e..ca127ac4e 100644 --- a/cla-backend-go/github/branch_protection/mock.go +++ b/cla-backend-go/github/branch_protection/mock.go @@ -10,11 +10,10 @@ package branch_protection import ( context "context" - reflect "reflect" - gomock "github.com/golang/mock/gomock" github "github.com/google/go-github/v33/github" githubv4 "github.com/shurcooL/githubv4" + reflect "reflect" ) // MockCombinedRepository is a mock of CombinedRepository interface diff --git a/cla-backend-go/github_organizations/mock.go b/cla-backend-go/github_organizations/mock.go index e446e0434..8eb3841ba 100644 --- a/cla-backend-go/github_organizations/mock.go +++ b/cla-backend-go/github_organizations/mock.go @@ -1,18 +1,18 @@ // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT +// // Code generated by MockGen. DO NOT EDIT. -// Source: repository.go +// Source: github.com/communitybridge/easycla/cla-backend-go/github_organizations (interfaces: Repository) // Package github_organizations is a generated GoMock package. package github_organizations import ( context "context" - reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockRepository is a mock of Repository interface @@ -39,118 +39,118 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { } // AddGithubOrganization mocks base method -func (m *MockRepository) AddGithubOrganization(ctx context.Context, parentProjectSFID, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +func (m *MockRepository) AddGithubOrganization(arg0 context.Context, arg1, arg2 string, arg3 *models.CreateGithubOrganization) (*models.GithubOrganization, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddGithubOrganization", ctx, parentProjectSFID, projectSFID, input) + ret := m.ctrl.Call(m, "AddGithubOrganization", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*models.GithubOrganization) ret1, _ := ret[1].(error) return ret0, ret1 } // AddGithubOrganization indicates an expected call of AddGithubOrganization -func (mr *MockRepositoryMockRecorder) AddGithubOrganization(ctx, parentProjectSFID, projectSFID, input interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) AddGithubOrganization(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubOrganization", reflect.TypeOf((*MockRepository)(nil).AddGithubOrganization), ctx, parentProjectSFID, projectSFID, input) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubOrganization", reflect.TypeOf((*MockRepository)(nil).AddGithubOrganization), arg0, arg1, arg2, arg3) } -// GetGithubOrganizations mocks base method -func (m *MockRepository) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { +// DeleteGithubOrganization mocks base method +func (m *MockRepository) DeleteGithubOrganization(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizations", ctx, projectSFID) - ret0, _ := ret[0].(*models.GithubOrganizations) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "DeleteGithubOrganization", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 } -// GetGithubOrganizations indicates an expected call of GetGithubOrganizations -func (mr *MockRepositoryMockRecorder) GetGithubOrganizations(ctx, projectSFID interface{}) *gomock.Call { +// DeleteGithubOrganization indicates an expected call of DeleteGithubOrganization +func (mr *MockRepositoryMockRecorder) DeleteGithubOrganization(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizations", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizations), ctx, projectSFID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGithubOrganization", reflect.TypeOf((*MockRepository)(nil).DeleteGithubOrganization), arg0, arg1, arg2) } -// GetGithubOrganizationsByParent mocks base method -func (m *MockRepository) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { +// DeleteGithubOrganizationByParent mocks base method +func (m *MockRepository) DeleteGithubOrganizationByParent(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizationsByParent", ctx, parentProjectSFID) - ret0, _ := ret[0].(*models.GithubOrganizations) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "DeleteGithubOrganizationByParent", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 } -// GetGithubOrganizationsByParent indicates an expected call of GetGithubOrganizationsByParent -func (mr *MockRepositoryMockRecorder) GetGithubOrganizationsByParent(ctx, parentProjectSFID interface{}) *gomock.Call { +// DeleteGithubOrganizationByParent indicates an expected call of DeleteGithubOrganizationByParent +func (mr *MockRepositoryMockRecorder) DeleteGithubOrganizationByParent(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationsByParent", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizationsByParent), ctx, parentProjectSFID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGithubOrganizationByParent", reflect.TypeOf((*MockRepository)(nil).DeleteGithubOrganizationByParent), arg0, arg1, arg2) } // GetGithubOrganization mocks base method -func (m *MockRepository) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { +func (m *MockRepository) GetGithubOrganization(arg0 context.Context, arg1 string) (*models.GithubOrganization, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganization", ctx, githubOrganizationName) + ret := m.ctrl.Call(m, "GetGithubOrganization", arg0, arg1) ret0, _ := ret[0].(*models.GithubOrganization) ret1, _ := ret[1].(error) return ret0, ret1 } // GetGithubOrganization indicates an expected call of GetGithubOrganization -func (mr *MockRepositoryMockRecorder) GetGithubOrganization(ctx, githubOrganizationName interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) GetGithubOrganization(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganization", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganization), ctx, githubOrganizationName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganization", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganization), arg0, arg1) } // GetGithubOrganizationByName mocks base method -func (m *MockRepository) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { +func (m *MockRepository) GetGithubOrganizationByName(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizationByName", ctx, githubOrganizationName) + ret := m.ctrl.Call(m, "GetGithubOrganizationByName", arg0, arg1) ret0, _ := ret[0].(*models.GithubOrganizations) ret1, _ := ret[1].(error) return ret0, ret1 } // GetGithubOrganizationByName indicates an expected call of GetGithubOrganizationByName -func (mr *MockRepositoryMockRecorder) GetGithubOrganizationByName(ctx, githubOrganizationName interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) GetGithubOrganizationByName(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationByName", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizationByName), ctx, githubOrganizationName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationByName", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizationByName), arg0, arg1) } -// UpdateGithubOrganization mocks base method -func (m *MockRepository) UpdateGithubOrganization(ctx context.Context, projectSFID, organizationName string, autoEnabled, branchProtectionEnabled bool) error { +// GetGithubOrganizations mocks base method +func (m *MockRepository) GetGithubOrganizations(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateGithubOrganization", ctx, projectSFID, organizationName, autoEnabled, branchProtectionEnabled) - ret0, _ := ret[0].(error) - return ret0 + ret := m.ctrl.Call(m, "GetGithubOrganizations", arg0, arg1) + ret0, _ := ret[0].(*models.GithubOrganizations) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// UpdateGithubOrganization indicates an expected call of UpdateGithubOrganization -func (mr *MockRepositoryMockRecorder) UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, branchProtectionEnabled interface{}) *gomock.Call { +// GetGithubOrganizations indicates an expected call of GetGithubOrganizations +func (mr *MockRepositoryMockRecorder) GetGithubOrganizations(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubOrganization", reflect.TypeOf((*MockRepository)(nil).UpdateGithubOrganization), ctx, projectSFID, organizationName, autoEnabled, branchProtectionEnabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizations", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizations), arg0, arg1) } -// DeleteGithubOrganization mocks base method -func (m *MockRepository) DeleteGithubOrganization(ctx context.Context, projectSFID, githubOrgName string) error { +// GetGithubOrganizationsByParent mocks base method +func (m *MockRepository) GetGithubOrganizationsByParent(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteGithubOrganization", ctx, projectSFID, githubOrgName) - ret0, _ := ret[0].(error) - return ret0 + ret := m.ctrl.Call(m, "GetGithubOrganizationsByParent", arg0, arg1) + ret0, _ := ret[0].(*models.GithubOrganizations) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// DeleteGithubOrganization indicates an expected call of DeleteGithubOrganization -func (mr *MockRepositoryMockRecorder) DeleteGithubOrganization(ctx, projectSFID, githubOrgName interface{}) *gomock.Call { +// GetGithubOrganizationsByParent indicates an expected call of GetGithubOrganizationsByParent +func (mr *MockRepositoryMockRecorder) GetGithubOrganizationsByParent(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGithubOrganization", reflect.TypeOf((*MockRepository)(nil).DeleteGithubOrganization), ctx, projectSFID, githubOrgName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationsByParent", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizationsByParent), arg0, arg1) } -// DeleteGithubOrganizationByParent mocks base method -func (m *MockRepository) DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID, githubOrgName string) error { +// UpdateGithubOrganization mocks base method +func (m *MockRepository) UpdateGithubOrganization(arg0 context.Context, arg1, arg2 string, arg3 bool, arg4 string, arg5 bool) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteGithubOrganizationByParent", ctx, parentProjectSFID, githubOrgName) + ret := m.ctrl.Call(m, "UpdateGithubOrganization", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(error) return ret0 } -// DeleteGithubOrganizationByParent indicates an expected call of DeleteGithubOrganizationByParent -func (mr *MockRepositoryMockRecorder) DeleteGithubOrganizationByParent(ctx, parentProjectSFID, githubOrgName interface{}) *gomock.Call { +// UpdateGithubOrganization indicates an expected call of UpdateGithubOrganization +func (mr *MockRepositoryMockRecorder) UpdateGithubOrganization(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGithubOrganizationByParent", reflect.TypeOf((*MockRepository)(nil).DeleteGithubOrganizationByParent), ctx, parentProjectSFID, githubOrgName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubOrganization", reflect.TypeOf((*MockRepository)(nil).UpdateGithubOrganization), arg0, arg1, arg2, arg3, arg4, arg5) } diff --git a/cla-backend-go/repositories/mock/mock_repository.go b/cla-backend-go/repositories/mock/mock_repository.go index 88862f212..568756836 100644 --- a/cla-backend-go/repositories/mock/mock_repository.go +++ b/cla-backend-go/repositories/mock/mock_repository.go @@ -10,10 +10,9 @@ package mock import ( context "context" - reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockRepository is a mock of Repository interface @@ -244,16 +243,16 @@ func (mr *MockRepositoryMockRecorder) GetCLAGroupRepositoriesGroupByOrgs(ctx, pr } // ListProjectRepositories mocks base method -func (m *MockRepository) ListProjectRepositories(ctx context.Context, externalProjectID, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { +func (m *MockRepository) ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListProjectRepositories", ctx, externalProjectID, projectSFID, enabled) + ret := m.ctrl.Call(m, "ListProjectRepositories", ctx, projectSFID, enabled) ret0, _ := ret[0].(*models.ListGithubRepositories) ret1, _ := ret[1].(error) return ret0, ret1 } // ListProjectRepositories indicates an expected call of ListProjectRepositories -func (mr *MockRepositoryMockRecorder) ListProjectRepositories(ctx, externalProjectID, projectSFID, enabled interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) ListProjectRepositories(ctx, projectSFID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectRepositories", reflect.TypeOf((*MockRepository)(nil).ListProjectRepositories), ctx, externalProjectID, projectSFID, enabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectRepositories", reflect.TypeOf((*MockRepository)(nil).ListProjectRepositories), ctx, projectSFID, enabled) } diff --git a/cla-backend-go/repositories/mock/mock_service.go b/cla-backend-go/repositories/mock/mock_service.go index e5c1a9ce0..339b28dff 100644 --- a/cla-backend-go/repositories/mock/mock_service.go +++ b/cla-backend-go/repositories/mock/mock_service.go @@ -10,10 +10,9 @@ package mock import ( context "context" - reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockService is a mock of Service interface diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index f4682e6b4..af29b3f98 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -7,6 +7,9 @@ import ( "context" "errors" "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/communitybridge/easycla/cla-backend-go/emails" + v1GithubOrg "github.com/communitybridge/easycla/cla-backend-go/github_organizations" "strconv" "github.com/sirupsen/logrus" @@ -33,18 +36,36 @@ type Service interface { type eventHandlerService struct { githubRepo repositories.Repository + githubOrgRepo v1GithubOrg.Repository eventService events.Service autoEnableService dynamo_events.AutoEnableService + emailService emails.Service + sendEmail bool } // NewService creates a new instance of the Event Handler Service func NewService(githubRepo repositories.Repository, + githubOrgRepo v1GithubOrg.Repository, eventService events.Service, - autoEnableService dynamo_events.AutoEnableService) Service { + autoEnableService dynamo_events.AutoEnableService, + emailService emails.Service) Service { + + return newService(githubRepo, githubOrgRepo, eventService, autoEnableService, emailService, true) +} + +func newService(githubRepo repositories.Repository, + githubOrgRepo v1GithubOrg.Repository, + eventService events.Service, + autoEnableService dynamo_events.AutoEnableService, + emailService emails.Service, + sendEmail bool) Service { return &eventHandlerService{ githubRepo: githubRepo, + githubOrgRepo: githubOrgRepo, eventService: eventService, autoEnableService: autoEnableService, + emailService: emailService, + sendEmail: sendEmail, } } @@ -54,15 +75,29 @@ func (s *eventHandlerService) ProcessRepositoryEvent(event *github.RepositoryEve "functionName": "v2.github_activity.service.ProcessRepositoryEvent", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - log.Debugf("ProcessRepositoryEvent called for action : %s", *event.Action) if event.Action == nil { return fmt.Errorf("no action found in event payload") } + + if event.Repo == nil { + return fmt.Errorf("missing repository object in event payload") + } + + log.Debugf("ProcessRepositoryEvent called for action : %s", *event.Action) switch *event.Action { case "created": return s.handleRepositoryAddedAction(ctx, event.Sender, event.Repo) + case "renamed": + return s.handleRepositoryRenamedAction(ctx, event.Sender, event.Repo) + case "transferred": + if event.Org == nil { + return fmt.Errorf("missing org object in event payload") + } + return s.handleRepositoryTransferredAction(ctx, event.Sender, event.Repo, event.Org) case "deleted": return s.handleRepositoryRemovedAction(ctx, event.Sender, event.Repo) + case "archived": + return s.handleRepositoryArchivedAction(ctx, event.Sender, event.Repo) default: log.WithFields(f).Warnf("no handler for action : %s", *event.Action) } @@ -155,6 +190,262 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, }, }) + if s.sendEmail { + subject := fmt.Sprintf("EasyCLA: Github Repository Was Removed") + body, err := emails.RenderGithubRepositoryDisabledTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryDisabledTemplateParams{ + GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: "CLA Manager", + }, + RepositoryName: repoModel.RepositoryName, + }, + GithubAction: "deleted", + }) + + if err != nil { + log.WithFields(f).Warnf("rendering email template failed : %v", err) + return nil + } + + if err := s.emailService.NotifyClaManagersForClaGroupID(context.Background(), repoModel.RepositoryProjectID, subject, body); err != nil { + log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) + } + + } + + return nil +} + +// handles the event when a repository is renamed so we rename the repo in our records as well +func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, sender *github.User, repo *github.Repository) error { + f := logrus.Fields{ + "functionName": "v2.github_activity.service.handleRepositoryRenamedAction", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + if repo.ID == nil || *repo.ID == 0 { + return fmt.Errorf("missing repo id") + } + repositoryExternalID := strconv.FormatInt(*repo.ID, 10) + repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + if err != nil { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) + return nil + } + return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) + } + + if _, err := s.githubRepo.UpdateGithubRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ + RepositoryName: repo.Name, + Note: "repository was renamed externally", + }); err != nil { + log.WithFields(f).Warnf("renaming repo : %s failed : %v", *repo.FullName, err) + return err + } + + if sender == nil || sender.Login == nil { + return fmt.Errorf("missing sender can not log the event") + } + + // sending event for the action + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryRenamed, + ProjectID: repoModel.RepositoryProjectID, + UserID: *sender.Login, + EventData: &events.RepositoryRenamedEventData{ + NewRepositoryName: *repo.Name, + OldRepositoryName: repoModel.RepositoryName, + }, + }) + + if s.sendEmail { + subject := fmt.Sprintf("EasyCLA: Github Repository Was Renamed") + body, err := emails.RenderGithubRepositoryRenamedTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryRenamedTemplateParams{ + GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: "CLA Manager", + }, + RepositoryName: repoModel.RepositoryName, + }, + NewRepositoryName: *repo.Name, + OldRepositoryName: repoModel.RepositoryName, + }) + + if err != nil { + log.WithFields(f).Warnf("rendering email template failed : %v", err) + return nil + } + + if err := s.emailService.NotifyClaManagersForClaGroupID(context.Background(), repoModel.RepositoryProjectID, subject, body); err != nil { + log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) + } + + } + + return nil +} + +func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Context, sender *github.User, repo *github.Repository, org *github.Organization) error { + f := logrus.Fields{ + "functionName": "v2.github_activity.service.handleRepositoryTransferredAction", + "repositoryName": *repo.Name, + "newGithubOrganization": *org.Name, + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + if repo.ID == nil || *repo.ID == 0 { + return fmt.Errorf("missing repo id") + } + repositoryExternalID := strconv.FormatInt(*repo.ID, 10) + repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + if err != nil { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) + return nil + } + return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) + } + + newOrganizationName := *org.Name + oldOrganizationName := repoModel.RepositoryOrganizationName + // first check if it's a different organization name (could be a duplicate event) + if oldOrganizationName == *org.Name { + msg := fmt.Sprintf("nothing to change for github repo : %s, probably duplicate event was sent", repoModel.RepositoryName) + log.WithFields(f).Warnf(msg) + return fmt.Errorf(msg) + } + + // fetch the old and the new github orgs from the db + oldGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, oldOrganizationName) + if err != nil { + return fmt.Errorf("fetching the old organization name : %s failed : %v", oldOrganizationName, err) + } + + newGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, *org.Name) + if err != nil { + return fmt.Errorf("fetching the new organization name : %s failed : %v", newOrganizationName, err) + } + + // we need to check if the new org name has autoenabled and have a cla group set otherwise we can't proceed + if !newGithubOrg.AutoEnabled || newGithubOrg.AutoEnabledClaGroupID == "" { + log.WithFields(f).Warnf("can't proceed with repo transfer operation because the new org doesn't have autoenabled=true, disabling the repo : %s", repoModel.RepositoryName) + if err := s.githubRepo.DisableRepository(ctx, repoModel.RepositoryID); err != nil { + return fmt.Errorf("disabling the repo : %s failed : %v", repoModel.RepositoryID, err) + } + + // send event for the disabled repository. + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryDisabled, + ProjectID: repoModel.RepositoryProjectID, + UserID: *sender.Login, + EventData: &events.RepositoryDisabledEventData{ + RepositoryName: repoModel.RepositoryName, + }, + }) + + if s.sendEmail{ + if err := s.notifyForGithubRepositoryTransferred(ctx, repoModel, oldGithubOrg, newGithubOrg, false); err != nil { + log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) + } + } + + return fmt.Errorf("aborting the repository : %s transfer, new githubOrg : %s doesn't have claGroupID set", repoModel.RepositoryName, newGithubOrg.OrganizationName) + } + + _, err = s.githubRepo.UpdateGithubRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ + Note: fmt.Sprintf("repository was transferred from org : %s to : %s", oldGithubOrg.OrganizationName, newGithubOrg.OrganizationName), + RepositoryOrganizationName: aws.String(newGithubOrg.OrganizationName), + RepositoryURL: repo.URL, + }) + + if err != nil { + return fmt.Errorf("repository : %s transfer failed for new github org : %s : %v", repoModel.RepositoryID, newGithubOrg.OrganizationName, err) + } + + // sending event for the action + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryTransferred, + ProjectID: repoModel.RepositoryProjectID, + UserID: *sender.Login, + EventData: &events.RepositoryTransferredEventData{ + RepositoryName: repoModel.RepositoryName, + OldGithubOrgName: oldGithubOrg.OrganizationName, + NewGithubOrgName: newGithubOrg.OrganizationName, + }, + }) + + if s.sendEmail { + if err := s.notifyForGithubRepositoryTransferred(ctx, repoModel, oldGithubOrg, newGithubOrg, true); err != nil { + log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) + } + } + + return nil +} + +func (s *eventHandlerService) notifyForGithubRepositoryTransferred(ctx context.Context, repoModel *models.GithubRepository, oldGithubOrg *models.GithubOrganization, newGithubOrg *models.GithubOrganization, success bool) error { + subject := fmt.Sprintf("EasyCLA: Github Repository Was Transferred") + body, err := emails.RenderGithubRepositoryTransferredTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryTransferredTemplateParams{ + GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: "CLA Manager", + }, + RepositoryName: repoModel.RepositoryName, + }, + OldGithubOrgName: oldGithubOrg.OrganizationName, + NewGithubOrgName: newGithubOrg.OrganizationName, + }, success) + + if err != nil { + return fmt.Errorf("rendering email template failed : %v", err) + } + + err = s.emailService.NotifyClaManagersForClaGroupID(ctx, repoModel.RepositoryProjectID, subject, body) + return err +} + +func (s *eventHandlerService) handleRepositoryArchivedAction(ctx context.Context, sender *github.User, repo *github.Repository) error { + f := logrus.Fields{ + "functionName": "v2.github_activity.service.handleRepositoryArchivedAction", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + if repo.ID == nil || *repo.ID == 0 { + return fmt.Errorf("missing repo id") + } + repositoryExternalID := strconv.FormatInt(*repo.ID, 10) + repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + if err != nil { + if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { + log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) + return nil + } + return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) + } + + if s.sendEmail { + subject := fmt.Sprintf("EasyCLA: Github Repository Was Archived") + body, err := emails.RenderGithubRepositoryArchivedTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryArchivedTemplateParams{ + GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ + CommonEmailParams: emails.CommonEmailParams{ + RecipientName: "CLA Manager", + }, + RepositoryName: repoModel.RepositoryName, + }, + }) + + if err != nil { + log.WithFields(f).Warnf("rendering email template failed : %v", err) + return nil + } + + if err := s.emailService.NotifyClaManagersForClaGroupID(ctx, repoModel.RepositoryProjectID, subject, body); err != nil { + log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) + } + + } + return nil } diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go new file mode 100644 index 000000000..16f09806e --- /dev/null +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -0,0 +1,187 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package github_activity + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/github_organizations" + "github.com/communitybridge/easycla/cla-backend-go/repositories/mock" + "github.com/golang/mock/gomock" + "github.com/google/go-github/v33/github" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryRenamedAction(t *testing.T) { + repoID := "1f15f478-0659-43f3-bcf1-383052de7616" + repoName := "org1/repo-name" + newRepoName := "org1/repo-name-new" + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + githubOrganizationRepo := github_organizations.NewMockRepository(ctrl) + githubRepo := mock.NewMockRepository(ctrl) + githubRepo.EXPECT(). + GetRepositoryByGithubID(gomock.Any(), "1", true). + Return(&models.GithubRepository{ + Enabled: true, + RepositoryExternalID: "1", + RepositoryID: repoID, + RepositoryName: repoName, + RepositoryOrganizationName: "org1", + }, nil) + + githubRepo.EXPECT(). + UpdateGithubRepository(gomock.Any(), repoID, &models.GithubRepositoryInput{ + RepositoryName: &newRepoName, + Note: "repository was renamed externally", + }).Return(nil, nil) + + eventsService := events.NewMockService(ctrl) + eventsService.EXPECT(). + LogEventWithContext(gomock.Any(), &events.LogEventArgs{ + EventType: events.RepositoryRenamed, + UserID: "githubLoginValue", + ProjectID: "", + EventData: &events.RepositoryRenamedEventData{ + NewRepositoryName: newRepoName, + OldRepositoryName: repoName, + }, + }).Return() + + activityService := newService(githubRepo, githubOrganizationRepo, eventsService, nil, nil, false) + err := activityService.ProcessRepositoryEvent(&github.RepositoryEvent{ + Action: aws.String("renamed"), + Repo: &github.Repository{ + ID: aws.Int64(1), + Name: &newRepoName, + }, + Org: nil, + Sender: &github.User{ + Login: aws.String("githubLoginValue"), + }, + Installation: nil, + }) + + assert.NoError(t, err) +} + +func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredAction(t *testing.T) { + repoID := "1f15f478-0659-43f3-bcf1-383052de7616" + repoName := "org1/repo-name" + oldOrgName := "org1" + newOrgName := "org2" + newRepoUrl := "org2/repo-name" + + testCases := []struct { + name string + newGithubOrg *models.GithubOrganization + }{ + { + name: "success new org is enabled and and has cla group", + newGithubOrg: &models.GithubOrganization{ + OrganizationName: newOrgName, + AutoEnabled: true, + AutoEnabledClaGroupID: "c057ed9a-4235-4acf-80bd-c7b4c235eff9", + }, + }, + { + name: "failure new org is disabled and no cla group", + newGithubOrg: &models.GithubOrganization{ + OrganizationName: newOrgName, + AutoEnabled: false, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + githubOrganizationRepo := github_organizations.NewMockRepository(ctrl) + githubRepo := mock.NewMockRepository(ctrl) + githubRepo.EXPECT(). + GetRepositoryByGithubID(gomock.Any(), "1", true). + Return(&models.GithubRepository{ + Enabled: true, + RepositoryExternalID: "1", + RepositoryID: repoID, + RepositoryName: repoName, + RepositoryOrganizationName: oldOrgName, + }, nil) + + // return the old one + githubOrganizationRepo.EXPECT(). + GetGithubOrganization(gomock.Any(), oldOrgName). + Return(&models.GithubOrganization{ + OrganizationName: oldOrgName, + }, nil) + + // return the new one + githubOrganizationRepo.EXPECT(). + GetGithubOrganization(gomock.Any(), newOrgName). + Return(tc.newGithubOrg, nil) + + eventsService := events.NewMockService(ctrl) + if tc.newGithubOrg.AutoEnabled { + githubRepo.EXPECT(). + UpdateGithubRepository(gomock.Any(), repoID, &models.GithubRepositoryInput{ + RepositoryOrganizationName: &newOrgName, + RepositoryURL: &newRepoUrl, + Note: fmt.Sprintf("repository was transferred from org : %s to : %s", oldOrgName, newOrgName), + }).Return(nil, nil) + + eventsService.EXPECT(). + LogEventWithContext(gomock.Any(), &events.LogEventArgs{ + EventType: events.RepositoryTransferred, + UserID: "githubLoginValue", + ProjectID: "", + EventData: &events.RepositoryTransferredEventData{ + RepositoryName: repoName, + OldGithubOrgName: oldOrgName, + NewGithubOrgName: newOrgName, + }, + }).Return() + }else{ + githubRepo.EXPECT(). + DisableRepository(gomock.Any(), repoID).Return(nil) + eventsService.EXPECT(). + LogEventWithContext(gomock.Any(), &events.LogEventArgs{ + EventType: events.RepositoryDisabled, + UserID: "githubLoginValue", + ProjectID: "", + EventData: &events.RepositoryDisabledEventData{ + RepositoryName: repoName, + }, + }).Return() + } + + activityService := newService(githubRepo, githubOrganizationRepo, eventsService, nil, nil, false) + err := activityService.ProcessRepositoryEvent(&github.RepositoryEvent{ + Action: aws.String("transferred"), + Repo: &github.Repository{ + ID: aws.Int64(1), + Name: &repoName, + URL: &newRepoUrl, + }, + Org: &github.Organization{ + Name: &newOrgName, + }, + Sender: &github.User{ + Login: aws.String("githubLoginValue"), + }, + Installation: nil, + }) + + if tc.newGithubOrg.AutoEnabled{ + assert.NoError(tt, err) + }else{ + assert.Error(tt, err) + } + }) + } +} From 9955f36dd94e626156f40b7b009d4ba7200f6609 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 22 Apr 2021 10:07:06 -0700 Subject: [PATCH 0238/1276] Added Permission Checks Logging (#2893) - Added additional permission check logging - Added permission check for child projects (if any have access, we allow) - Added missing ctx parameter to permission check functions - to be consistent and have proper logging Signed-off-by: David Deal --- .../utils/utils_user_auth_lambda.go | 103 +++++++++++++--- .../utils/utils_user_auth_standalone.go | 112 +++++++++++++++--- cla-backend-go/v2/cla_manager/handlers.go | 6 +- cla-backend-go/v2/company/handlers.go | 24 ++-- cla-backend-go/v2/events/handlers.go | 2 +- cla-backend-go/v2/metrics/handlers.go | 2 +- cla-backend-go/v2/sign/handlers.go | 2 +- cla-backend-go/v2/signatures/handlers.go | 53 +++++---- 8 files changed, 231 insertions(+), 73 deletions(-) diff --git a/cla-backend-go/utils/utils_user_auth_lambda.go b/cla-backend-go/utils/utils_user_auth_lambda.go index 9b0ccee84..0e9bfbd88 100644 --- a/cla-backend-go/utils/utils_user_auth_lambda.go +++ b/cla-backend-go/utils/utils_user_auth_lambda.go @@ -27,13 +27,28 @@ func IsUserAdmin(user *auth.User) bool { } // IsUserAuthorizedForOrganization helper function for determining if the user is authorized for this company -func IsUserAuthorizedForOrganization(user *auth.User, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForOrganization(ctx context.Context, user *auth.User, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForOrganization", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - return user.IsUserAuthorizedForOrganizationScope(companySFID) + val := user.IsUserAuthorizedForOrganizationScope(companySFID) + if val { + log.WithFields(f).Debugf("user is authorized for companySFID: %s", companySFID) + } else { + log.WithFields(f).Debugf("user is not authorized for companySFID: %s", companySFID) + } + return val } // IsUserAuthorizedForProjectTree helper function for determining if the user is authorized for this project hierarchy/tree @@ -48,13 +63,17 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec } if adminScopeAllowed && user.Admin { - log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - log.WithFields(f).Debug("checking scope...") + log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorized(auth.Project, projectSFID, true) - log.WithFields(f).Debugf("user allowed: %t", val) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + } return val } @@ -70,13 +89,17 @@ func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFI } if adminScopeAllowed && user.Admin { - log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - log.WithFields(f).Debug("checking scope...") + log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorizedForProjectScope(projectSFID) - log.WithFields(f).Debugf("user allowed: %t", val) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + } return val } @@ -109,35 +132,85 @@ func IsUserAuthorizedForAnyProjects(ctx context.Context, user *auth.User, projec } // IsUserAuthorizedForProjectOrganization helper function for determining if the user is authorized for this project organization scope -func IsUserAuthorizedForProjectOrganization(user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProjectOrganization(ctx context.Context, user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProjectOrganization", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - return user.IsUserAuthorizedByProject(projectSFID, companySFID) + val := user.IsUserAuthorizedByProject(projectSFID, companySFID) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + } + return val } // IsUserAuthorizedForAnyProjectOrganization helper function for determining if the user is authorized for any of the specified projects with scope of project + organization -func IsUserAuthorizedForAnyProjectOrganization(user *auth.User, projectSFIDs []string, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForAnyProjectOrganization(ctx context.Context, user *auth.User, projectSFIDs []string, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForAnyProjectOrganization", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFIDs": strings.Join(projectSFIDs, ","), + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } + + if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") + return true + } + for _, projectSFID := range projectSFIDs { - if IsUserAuthorizedForProjectOrganizationTree(user, projectSFID, companySFID, adminScopeAllowed) { + if IsUserAuthorizedForProjectOrganizationTree(ctx, user, projectSFID, companySFID, adminScopeAllowed) { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) return true } - if IsUserAuthorizedForProjectOrganization(user, projectSFID, companySFID, adminScopeAllowed) { + if IsUserAuthorizedForProjectOrganization(ctx, user, projectSFID, companySFID, adminScopeAllowed) { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) return true } } + log.WithFields(f).Debugf("user is not authorized for any projectSFID: %s + companySFID: %s", strings.Join(projectSFIDs, ","), companySFID) return false } // IsUserAuthorizedForProjectOrganizationTree helper function for determining if the user is authorized for this project organization scope and nested projects/orgs -func IsUserAuthorizedForProjectOrganizationTree(user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProjectOrganizationTree(ctx context.Context, user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProjectOrganizationTree", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - return user.IsUserAuthorized(auth.ProjectOrganization, projectSFID+"|"+companySFID, true) + val := user.IsUserAuthorized(auth.ProjectOrganization, projectSFID+"|"+companySFID, true) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + } + return val } diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index b29e6a413..8654d9d7e 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -46,17 +46,34 @@ func skipPermissionChecks() bool { } // IsUserAuthorizedForOrganization helper function for determining if the user is authorized for this company -func IsUserAuthorizedForOrganization(user *auth.User, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForOrganization(ctx context.Context, user *auth.User, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForOrganization", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } + // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") return true } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - return user.IsUserAuthorizedForOrganizationScope(companySFID) + val := user.IsUserAuthorizedForOrganizationScope(companySFID) + if val { + log.WithFields(f).Debugf("user is authorized for companySFID: %s", companySFID) + } else { + log.WithFields(f).Debugf("user is not authorized for companySFID: %s", companySFID) + } + return val } // IsUserAuthorizedForProjectTree helper function for determining if the user is authorized for this project hierarchy/tree @@ -77,13 +94,17 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec } if adminScopeAllowed && user.Admin { - log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - log.WithFields(f).Debug("checking scope...") + log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorized(auth.Project, projectSFID, true) - log.WithFields(f).Debugf("user allowed: %t", val) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + } return val } @@ -105,13 +126,17 @@ func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFI } if adminScopeAllowed && user.Admin { - log.WithFields(f).Debug("admin scope is allowed and admin scope set for user") + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - log.WithFields(f).Debug("checking scope...") + log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorizedForProjectScope(projectSFID) - log.WithFields(f).Debugf("user allowed: %t", val) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + } return val } @@ -149,51 +174,102 @@ func IsUserAuthorizedForAnyProjects(ctx context.Context, user *auth.User, projec } // IsUserAuthorizedForProjectOrganization helper function for determining if the user is authorized for this project organization scope -func IsUserAuthorizedForProjectOrganization(user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProjectOrganization(ctx context.Context, user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProjectOrganization", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") return true } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - // Previously, we checked for user.Allowed, which is currently not used (future flag that is currently not implemented) - return user.IsUserAuthorizedByProject(projectSFID, companySFID) + val := user.IsUserAuthorizedByProject(projectSFID, companySFID) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + } + return val } // IsUserAuthorizedForAnyProjectOrganization helper function for determining if the user is authorized for any of the specified projects with scope of project + organization -func IsUserAuthorizedForAnyProjectOrganization(user *auth.User, projectSFIDs []string, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForAnyProjectOrganization(ctx context.Context, user *auth.User, projectSFIDs []string, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForAnyProjectOrganization", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFIDs": strings.Join(projectSFIDs, ","), + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } + // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") + return true + } + + if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } for _, projectSFID := range projectSFIDs { - if IsUserAuthorizedForProjectOrganizationTree(user, projectSFID, companySFID, adminScopeAllowed) { + if IsUserAuthorizedForProjectOrganizationTree(ctx, user, projectSFID, companySFID, adminScopeAllowed) { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) return true } - if IsUserAuthorizedForProjectOrganization(user, projectSFID, companySFID, adminScopeAllowed) { + if IsUserAuthorizedForProjectOrganization(ctx, user, projectSFID, companySFID, adminScopeAllowed) { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) return true } } + log.WithFields(f).Debugf("user is not authorized for any projectSFID: %s + companySFID: %s", strings.Join(projectSFIDs, ","), companySFID) return false } // IsUserAuthorizedForProjectOrganizationTree helper function for determining if the user is authorized for this project organization scope and nested projects/orgs -func IsUserAuthorizedForProjectOrganizationTree(user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { +func IsUserAuthorizedForProjectOrganizationTree(ctx context.Context, user *auth.User, projectSFID, companySFID string, adminScopeAllowed bool) bool { + f := logrus.Fields{ + "functionName": "utils.IsUserAuthorizedForProjectOrganizationTree", + XREQUESTID: ctx.Value(XREQUESTID), + "userName": user.UserName, + "userEmail": user.Email, + "projectSFID": projectSFID, + "companySFID": companySFID, + "adminScopeAllowed": adminScopeAllowed, + } + // If we are running locally and want to disable permission checks if skipPermissionChecks() { + log.WithFields(f).Debug("skipping permissions check") return true } if adminScopeAllowed && user.Admin { + log.WithFields(f).Debug("user is authorized - admin scope is allowed and admin scope set for user") return true } - // Previously, we checked for user.Admin - admins should be in a separate role - // Previously, we checked for user.Allowed, which is currently not used (future flag that is currently not implemented) - return user.IsUserAuthorized(auth.ProjectOrganization, projectSFID+"|"+companySFID, true) + val := user.IsUserAuthorized(auth.ProjectOrganization, projectSFID+"|"+companySFID, true) + if val { + log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + } else { + log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + } + return val } diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 87db4a8f6..4f134468b 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -61,7 +61,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C } log.WithFields(f).Debug("checking permissions...") - if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to DeleteCLAManager with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) return cla_manager.NewCreateCLAManagerForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -113,7 +113,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C } log.WithFields(f).Debug("checking permissions...") - if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, params.ProjectSFID, v1CompanyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to DeleteCLAManager with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -320,7 +320,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C } // Check perms... - if !utils.IsUserAuthorizedForOrganization(authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v1CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to CreateCLAManagerRequest with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, v1CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index fc1704d87..73c0790c5 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -61,7 +61,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to CompanyGetCompanyByInternalIDHandler with Organization scope of %s", authUser.UserName, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) @@ -107,7 +107,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to CompanyGetCompanyByExternalIDHandler with Organization scope of %s", authUser.UserName, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) @@ -141,7 +141,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to GetCompanyProjectClaManagers with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) @@ -210,7 +210,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to GetCompanyProjectActiveCla with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) @@ -508,7 +508,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } // finally, we can check permissions for the delete operation - if !utils.IsUserAuthorizedForOrganization(authUser, companyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, companyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf(" user %s does not have access to company %s with Organization scope of %s", authUser.UserName, companyModel.CompanyName, companyModel.CompanyExternalID) log.Warn(msg) @@ -565,7 +565,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo // finally, we can check permissions for the delete operation log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(authUser, companyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, companyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf(" user %s does not have access to company %s with Organization scope of %s", authUser.UserName, companyModel.CompanyName, companyModel.CompanyExternalID) log.WithFields(f).Warn(msg) @@ -740,19 +740,19 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut } log.WithFields(f).Debug("testing if user has access to project SFID and organization SFID...") - if utils.IsUserAuthorizedForProjectOrganization(authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganization(ctx, authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to project SFID and organization SFID...") return true } log.WithFields(f).Debug("testing if user has access to project SFID and organization SFID tree...") - if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to project SFID and organization SFID tree...") return true } log.WithFields(f).Debug("testing if user has access to organization SFID...") - if utils.IsUserAuthorizedForOrganization(authUser, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForOrganization(ctx, authUser, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to organization SFID...") return true } @@ -785,13 +785,13 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut } log.WithFields(f).Debug("testing if user has access to foundation SFID and organization SFID...") - if utils.IsUserAuthorizedForProjectOrganization(authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganization(ctx, authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to foundation SFID and organization SFID...") return true } log.WithFields(f).Debug("testing if user has access to foundation SFID and organization SFID tree...") - if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to foundation SFID and organization SFID tree...") return true } @@ -807,7 +807,7 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) f["projectIDs"] = strings.Join(projectSFIDs, ",") log.WithFields(f).Debug("testing if user has access to any cla group project + organization") - if utils.IsUserAuthorizedForAnyProjectOrganization(authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForAnyProjectOrganization(ctx, authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debug("user has access to at least of of the projects...") return true } diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index 224b5c3f3..4feb29cf6 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -291,7 +291,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe return events.NewGetCompanyProjectEventsBadRequest().WithPayload(errorResponse(reqID, compErr)) } - if !utils.IsUserAuthorizedForOrganization(authUser, v1Company.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v1Company.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { return events.NewGetCompanyProjectEventsForbidden().WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to GetCompanyProject Events with Organization scope of %s", diff --git a/cla-backend-go/v2/metrics/handlers.go b/cla-backend-go/v2/metrics/handlers.go index 60106acc4..b69c9369a 100644 --- a/cla-backend-go/v2/metrics/handlers.go +++ b/cla-backend-go/v2/metrics/handlers.go @@ -111,7 +111,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp return metrics.NewListCompanyProjectMetricsBadRequest().WithPayload(errorResponse(reqID, compErr)) } utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForOrganization(authUser, company.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, company.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { return metrics.NewListCompanyProjectMetricsForbidden().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to List Company Project Metrics with Organization scope of %s", diff --git a/cla-backend-go/v2/sign/handlers.go b/cla-backend-go/v2/sign/handlers.go index cbcb846e1..822a549d4 100644 --- a/cla-backend-go/v2/sign/handlers.go +++ b/cla-backend-go/v2/sign/handlers.go @@ -28,7 +28,7 @@ func Configure(api *operations.EasyclaAPI, service Service) { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) - if !utils.IsUserAuthorizedForProjectOrganizationTree(user, utils.StringValue(params.Input.ProjectSfid), utils.StringValue(params.Input.CompanySfid), utils.DISALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectOrganizationTree(ctx, user, utils.StringValue(params.Input.ProjectSfid), utils.StringValue(params.Input.CompanySfid), utils.DISALLOW_ADMIN_SCOPE) { return sign.NewRequestCorporateSignatureForbidden().WithPayload(&models.ErrorResponse{ Code: "403", Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Request Corporate Signature with Project|Organization scope of %s | %s", diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 3364d6c09..6f6cce439 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -120,7 +120,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj } // Must be in the Project|Organization Scope to see this - signature ACL is double-checked in the service level when the signature is loaded - if !utils.IsUserAuthorizedForProjectOrganizationTree(authUser, params.ProjectSFID, companyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, params.ProjectSFID, companyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user '%s' does not have access to update Project Company Approval List with Project|Organization scope of %s | %s", authUser.UserName, params.ProjectSFID, params.CompanyID) log.WithFields(f).Warn(msg) @@ -444,7 +444,6 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ "functionName": "v2.signatures.handlers.SignaturesGetProjectCompanySignaturesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -606,7 +605,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj }) } - if !utils.IsUserAuthorizedForOrganization(authUser, companyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, companyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("%s - user %s is not authorized to view company signatures with Organization scope: %s", utils.EasyCLA403Forbidden, authUser.UserName, companyModel.CompanyExternalID) log.WithFields(f).Warn(msg) @@ -1346,13 +1345,13 @@ func isUserHaveAccessOfSignedSignaturePDF(ctx context.Context, authUser *auth.Us } // Check the project|org tree starting with the foundation - if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, foundationID, comp.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, foundationID, comp.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { return true, nil } // In case the project organization tree didn't pass, let's check the project list individually - if any has access, we return true for _, proj := range projects { - if utils.IsUserAuthorizedForProjectOrganization(authUser, proj.ProjectSFID, comp.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganization(ctx, authUser, proj.ProjectSFID, comp.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user is authorized for %s scope for project ID: %s, org iD: %s", utils.ProjectOrgScope, proj.ProjectSFID, comp.CompanyExternalID) return true, nil } @@ -1531,19 +1530,19 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut } log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID and organization SFID...", authUser.UserName, authUser.Email) - if utils.IsUserAuthorizedForProjectOrganization(authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganization(ctx, authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user %s/%s has access to project SFID and organization SFID...", authUser.UserName, authUser.Email) return true } log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID and organization SFID tree...", authUser.UserName, authUser.Email) - if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, projectSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user %s/%s has access to project SFID and organization SFID tree...", authUser.UserName, authUser.Email) return true } log.WithFields(f).Debugf("testing if user %s/%s has access to organization SFID...", authUser.UserName, authUser.Email) - if utils.IsUserAuthorizedForOrganization(authUser, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForOrganization(ctx, authUser, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user %s/%s has access to organization SFID...", authUser.UserName, authUser.Email) return true } @@ -1564,26 +1563,27 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // Check the foundation permissions f["foundationSFID"] = projectCLAGroupModel.FoundationSFID - log.WithFields(f).Debugf("testing if user %s/%s has access to parent foundation...", authUser.UserName, authUser.Email) + log.WithFields(f).Debugf("testing if user %s/%s has access to parent foundation SFID: %s...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID) if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to parent foundation...", authUser.UserName, authUser.Email) + log.WithFields(f).Debugf("user %s/%s has access to parent foundation SFID: %s...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID) return true } - log.WithFields(f).Debugf("testing if user %s/%s has access to parent foundation tree...", authUser.UserName, authUser.Email) + + log.WithFields(f).Debugf("testing if user %s/%s has access to parent foundation SFID: %s tree...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID) if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to parent foundation tree...", authUser.UserName, authUser.Email) + log.WithFields(f).Debugf("user %s/%s has access to parent foundation SFID: %s tree...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID) return true } - log.WithFields(f).Debugf("testing if user %s/%s has access to foundation SFID and organization SFID...", authUser.UserName, authUser.Email) - if utils.IsUserAuthorizedForProjectOrganization(authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to foundation SFID and organization SFID...", authUser.UserName, authUser.Email) + log.WithFields(f).Debugf("testing if user %s/%s has access to foundation SFID %s and organization SFID %s ...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID, organizationSFID) + if utils.IsUserAuthorizedForProjectOrganization(ctx, authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user %s/%s has access to foundation SFID %s and organization SFID %s...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID, organizationSFID) return true } - log.WithFields(f).Debugf("testing if user %s/%s has access to foundation SFID and organization SFID tree...", authUser.UserName, authUser.Email) - if utils.IsUserAuthorizedForProjectOrganizationTree(authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to foundation SFID and organization SFID tree...", authUser.UserName, authUser.Email) + log.WithFields(f).Debugf("testing if user %s/%s has access to foundation SFID %s and organization SFID %s tree...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID, organizationSFID) + if utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user %s/%s has access to foundation SFID %s and organization SFID %s tree...", authUser.UserName, authUser.Email, projectCLAGroupModel.FoundationSFID, organizationSFID) return true } @@ -1595,11 +1595,20 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut return false } + // Get the list of the project group and projects associated with this CLA Group projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) - f["projectIDs"] = strings.Join(projectSFIDs, ",") - log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group project + organization", authUser.UserName, authUser.Email) - if utils.IsUserAuthorizedForAnyProjectOrganization(authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects...", authUser.UserName, authUser.Email) + projectSFIDsCSV := strings.Join(projectSFIDs, ",") // Create a project SFID CSV for printout + f["projectIDs"] = projectSFIDsCSV + + log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group projects: %s", authUser.UserName, authUser.Email, projectSFIDsCSV) + if utils.IsUserAuthorizedForAnyProjectOrganization(ctx, authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects: %s...", authUser.UserName, authUser.Email, projectSFIDsCSV) + return true + } + + log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group projects: %s + organization SFID: %s", authUser.UserName, authUser.Email, projectSFIDsCSV, organizationSFID) + if utils.IsUserAuthorizedForAnyProjectOrganization(ctx, authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects: %s + organization SFID: %s...", authUser.UserName, authUser.Email, projectSFIDsCSV, organizationSFID) return true } From eeba6a7304b1fc2c89944bd02a7ee56746f36051 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 22 Apr 2021 11:46:26 -0700 Subject: [PATCH 0239/1276] Resolved Issue with Permission Check for Project List (#2894) - Updated check for IsUserAuthorizedForAnyProjects when querying for signatures Signed-off-by: David Deal --- cla-backend-go/v2/signatures/handlers.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 6f6cce439..e07f27f39 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -1458,12 +1458,14 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj "userEmail": authUser.Email, } - log.WithFields(f).Debug("testing if user has access to project SFID") + log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID: %s...", authUser.UserName, authUser.Email, projectSFID) if utils.IsUserAuthorizedForProject(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { + log.WithFields(f).Debugf("user %s/%s has access to project SFID: %s...", authUser.UserName, authUser.Email, projectSFID) return true } + log.WithFields(f).Debugf("user %s/%s doesn't have direct access to the project SFID: %s - loading CLA Group from project id...", authUser.UserName, authUser.Email, projectSFID) - log.WithFields(f).Debug("user doesn't have direct access to the projectSFID - loading CLA Group from project id...") + log.WithFields(f).Debug("loading CLA Group from project id...") projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") @@ -1495,10 +1497,12 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj } projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) - f["projectIDs"] = strings.Join(projectSFIDs, ",") - log.WithFields(f).Debug("testing if user has access to any projects") + projectSFIDsCSV := strings.Join(projectSFIDs, ",") // Create a project SFID CSV for printout + f["projectIDs"] = projectSFIDsCSV + + log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group projects: %s", authUser.UserName, authUser.Email, projectSFIDsCSV) if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to at least of of the projects...") + log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects: %s...", authUser.UserName, authUser.Email, projectSFIDsCSV) return true } @@ -1601,7 +1605,7 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut f["projectIDs"] = projectSFIDsCSV log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group projects: %s", authUser.UserName, authUser.Email, projectSFIDsCSV) - if utils.IsUserAuthorizedForAnyProjectOrganization(ctx, authUser, projectSFIDs, organizationSFID, utils.ALLOW_ADMIN_SCOPE) { + if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects: %s...", authUser.UserName, authUser.Email, projectSFIDsCSV) return true } From 66d15f0807726eb5a254fd43b6ba70327b4d1892 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 22 Apr 2021 15:03:40 -0700 Subject: [PATCH 0240/1276] Updated Assign Role Scope Event Log Text (#2895) - Added user name/email to struct, set in call, printout in event log Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 45 ++++++++++++++----- cla-backend-go/events/mock.go | 3 +- .../github/branch_protection/mock.go | 3 +- cla-backend-go/github_organizations/mock.go | 3 +- .../repositories/mock/mock_repository.go | 3 +- .../repositories/mock/mock_service.go | 3 +- cla-backend-go/v2/cla_manager/service.go | 8 ++-- cla-backend-go/v2/company/service.go | 9 ++-- cla-backend-go/v2/github_activity/service.go | 5 ++- .../v2/github_activity/service_test.go | 11 ++--- 10 files changed, 62 insertions(+), 31 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index e06b6ffca..d02bc56f0 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -371,12 +371,17 @@ type ContributorAssignCLADesignee struct { } // UserConvertToContactData . . . -type UserConvertToContactData struct{} +type UserConvertToContactData struct { + UserName string + UserEmail string +} // AssignRoleScopeData . . . type AssignRoleScopeData struct { - Role string - Scope string + Role string + Scope string + UserName string + UserEmail string } // ClaManagerRoleCreatedData . . . @@ -1024,9 +1029,23 @@ func (ed *UserConvertToContactData) GetEventDetailsString(args *LogEventArgs) (s // GetEventDetailsString . . . func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s was assigned Scope: %s with Role: %s for Project: %s with ID: %s.", - args.UserName, - ed.Scope, ed.Role, args.ProjectName, args.ProjectSFID) + data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -1995,7 +2014,7 @@ func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs // GetEventSummaryString . . . func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was converted to a contact", args.UserName) + data := fmt.Sprintf("The user '%s' with email '%s' was converted to a contact", ed.UserName, ed.UserEmail) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -2005,13 +2024,16 @@ func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (s if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added to the role %s", args.UserName, ed.Role) + data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -2021,13 +2043,16 @@ func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *ClaManagerRoleCreatedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was added to the role %s", ed.UserName, ed.Role) + data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -2046,7 +2071,7 @@ func (ed *ClaManagerRoleCreatedData) GetEventSummaryString(args *LogEventArgs) ( // GetEventSummaryString . . . func (ed *ClaManagerRoleDeletedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The user %s was removed from the role %s", ed.UserName, ed.Role) + data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } diff --git a/cla-backend-go/events/mock.go b/cla-backend-go/events/mock.go index 93c0baa68..91494c65c 100644 --- a/cla-backend-go/events/mock.go +++ b/cla-backend-go/events/mock.go @@ -10,10 +10,11 @@ package events import ( context "context" + reflect "reflect" + models "github.com/communitybridge/easycla/cla-backend-go/gen/models" events0 "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" gomock "github.com/golang/mock/gomock" - reflect "reflect" ) // MockService is a mock of Service interface diff --git a/cla-backend-go/github/branch_protection/mock.go b/cla-backend-go/github/branch_protection/mock.go index ca127ac4e..456925c0e 100644 --- a/cla-backend-go/github/branch_protection/mock.go +++ b/cla-backend-go/github/branch_protection/mock.go @@ -10,10 +10,11 @@ package branch_protection import ( context "context" + reflect "reflect" + gomock "github.com/golang/mock/gomock" github "github.com/google/go-github/v33/github" githubv4 "github.com/shurcooL/githubv4" - reflect "reflect" ) // MockCombinedRepository is a mock of CombinedRepository interface diff --git a/cla-backend-go/github_organizations/mock.go b/cla-backend-go/github_organizations/mock.go index 8eb3841ba..48f1c20bc 100644 --- a/cla-backend-go/github_organizations/mock.go +++ b/cla-backend-go/github_organizations/mock.go @@ -10,9 +10,10 @@ package github_organizations import ( context "context" + reflect "reflect" + models "github.com/communitybridge/easycla/cla-backend-go/gen/models" gomock "github.com/golang/mock/gomock" - reflect "reflect" ) // MockRepository is a mock of Repository interface diff --git a/cla-backend-go/repositories/mock/mock_repository.go b/cla-backend-go/repositories/mock/mock_repository.go index 568756836..9791f117e 100644 --- a/cla-backend-go/repositories/mock/mock_repository.go +++ b/cla-backend-go/repositories/mock/mock_repository.go @@ -10,9 +10,10 @@ package mock import ( context "context" + reflect "reflect" + models "github.com/communitybridge/easycla/cla-backend-go/gen/models" gomock "github.com/golang/mock/gomock" - reflect "reflect" ) // MockRepository is a mock of Repository interface diff --git a/cla-backend-go/repositories/mock/mock_service.go b/cla-backend-go/repositories/mock/mock_service.go index 339b28dff..e5c1a9ce0 100644 --- a/cla-backend-go/repositories/mock/mock_service.go +++ b/cla-backend-go/repositories/mock/mock_service.go @@ -10,9 +10,10 @@ package mock import ( context "context" + reflect "reflect" + models "github.com/communitybridge/easycla/cla-backend-go/gen/models" gomock "github.com/golang/mock/gomock" - reflect "reflect" ) // MockService is a mock of Service interface diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index e9765a1ef..c154d1811 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -402,14 +402,14 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companyID string s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.AssignUserRoleScopeType, - LfUsername: lfxUser.Username, ProjectSFID: projectSFID, CompanyModel: v1CompanyModel, CompanyID: v1CompanyModel.CompanyID, - UserModel: &v1Models.User{LfUsername: lfxUser.Username, UserID: lfxUser.ID}, EventData: &events.AssignRoleScopeData{ - Role: "cla-manager-designee", - Scope: fmt.Sprintf("%s|%s", projectSFID, v1CompanyModel.CompanyExternalID), + Role: "cla-manager-designee", + Scope: fmt.Sprintf("%s|%s", projectSFID, v1CompanyModel.CompanyExternalID), + UserName: lfxUser.Username, + UserEmail: utils.StringValue(lfxUser.Email), }, }) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 6872628fa..b889276ad 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -744,17 +744,16 @@ func (s *service) CreateContributor(ctx context.Context, companyID string, proje s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.AssignUserRoleScopeType, - LfUsername: user.Username, - UserID: user.ID, ProjectSFID: projectID, CompanyModel: v1CompanyModel, ClaGroupModel: projectModel, CLAGroupID: projectModel.ProjectID, CLAGroupName: projectModel.ProjectName, - UserModel: &v1Models.User{LfUsername: user.Username, UserID: user.ID}, EventData: &events.AssignRoleScopeData{ - Role: "contributor", - Scope: fmt.Sprintf("%s|%s", projectID, companyID), + Role: "contributor", + Scope: fmt.Sprintf("%s|%s", projectID, companyID), + UserName: user.Username, + UserEmail: utils.StringValue(user.Email), }, }) diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index af29b3f98..b5b9b5afe 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -7,10 +7,11 @@ import ( "context" "errors" "fmt" + "strconv" + "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/emails" v1GithubOrg "github.com/communitybridge/easycla/cla-backend-go/github_organizations" - "strconv" "github.com/sirupsen/logrus" @@ -344,7 +345,7 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont }, }) - if s.sendEmail{ + if s.sendEmail { if err := s.notifyForGithubRepositoryTransferred(ctx, repoModel, oldGithubOrg, newGithubOrg, false); err != nil { log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) } diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go index 16f09806e..12514dd01 100644 --- a/cla-backend-go/v2/github_activity/service_test.go +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -5,6 +5,8 @@ package github_activity import ( "fmt" + "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/models" @@ -13,7 +15,6 @@ import ( "github.com/golang/mock/gomock" "github.com/google/go-github/v33/github" "github.com/stretchr/testify/assert" - "testing" ) func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryRenamedAction(t *testing.T) { @@ -146,7 +147,7 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredA NewGithubOrgName: newOrgName, }, }).Return() - }else{ + } else { githubRepo.EXPECT(). DisableRepository(gomock.Any(), repoID).Return(nil) eventsService.EXPECT(). @@ -155,7 +156,7 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredA UserID: "githubLoginValue", ProjectID: "", EventData: &events.RepositoryDisabledEventData{ - RepositoryName: repoName, + RepositoryName: repoName, }, }).Return() } @@ -177,9 +178,9 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredA Installation: nil, }) - if tc.newGithubOrg.AutoEnabled{ + if tc.newGithubOrg.AutoEnabled { assert.NoError(tt, err) - }else{ + } else { assert.Error(tt, err) } }) From 57b4c55916b2a9f61037eaec2c8115f4c9b41b23 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 23 Apr 2021 01:36:46 +0300 Subject: [PATCH 0241/1276] [#2882]Bug/Approval List Update (#2896) - Resolved issue raised in ONAP use case for empty lists Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/repository.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index ba294813b..67c54ad5a 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2042,7 +2042,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model var gerritICLAECLAs []string // Only load the gerrit user information, which is costly, if we have updates to remove email or email domains - if params.RemoveEmailApprovalList != nil || params.RemoveDomainApprovalList != nil { + if (params.RemoveEmailApprovalList != nil && len(params.RemoveEmailApprovalList) > 0) || (params.RemoveDomainApprovalList != nil && len(params.RemoveDomainApprovalList) > 0) { goRoutines := 2 gerritResultChannel := make(chan *GerritUserResponse, goRoutines) @@ -2067,7 +2067,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } // If we have an add or remove email list...we need to run an update for this column - if params.AddEmailApprovalList != nil || params.RemoveEmailApprovalList != nil { + if (params.AddEmailApprovalList != nil && len(params.AddEmailApprovalList) > 0) || (params.RemoveEmailApprovalList != nil && len(params.RemoveEmailApprovalList) > 0) { columnName := "email_whitelist" attrList := buildApprovalAttributeList(ctx, cclaSignature.EmailApprovalList, params.AddEmailApprovalList, params.RemoveEmailApprovalList) // If no entries after consolidating all the updates, we need to remove the column @@ -2190,7 +2190,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } } - if params.AddDomainApprovalList != nil || params.RemoveDomainApprovalList != nil { + if (params.AddDomainApprovalList != nil && len(params.AddDomainApprovalList) > 0) || (params.RemoveDomainApprovalList != nil && len(params.RemoveDomainApprovalList) > 0) { columnName := "domain_whitelist" attrList := buildApprovalAttributeList(ctx, cclaSignature.DomainApprovalList, params.AddDomainApprovalList, params.RemoveDomainApprovalList) @@ -2249,7 +2249,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } } - if params.AddGithubUsernameApprovalList != nil || params.RemoveGithubUsernameApprovalList != nil { + if (params.AddGithubUsernameApprovalList != nil && len(params.AddGithubUsernameApprovalList) > 0) || (params.RemoveGithubUsernameApprovalList != nil && len(params.RemoveGithubUsernameApprovalList) > 0) { columnName := "github_whitelist" attrList := buildApprovalAttributeList(ctx, cclaSignature.GithubUsernameApprovalList, params.AddGithubUsernameApprovalList, params.RemoveGithubUsernameApprovalList) // If no entries after consolidating all the updates, we need to remove the column @@ -2332,7 +2332,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } } - if params.AddGithubOrgApprovalList != nil || params.RemoveGithubOrgApprovalList != nil { + if (params.AddGithubOrgApprovalList != nil && len(params.AddGithubOrgApprovalList) > 0) || (params.RemoveGithubOrgApprovalList != nil && len(params.RemoveGithubOrgApprovalList) > 0) { columnName := "github_org_whitelist" attrList := buildApprovalAttributeList(ctx, cclaSignature.GithubOrgApprovalList, params.AddGithubOrgApprovalList, params.RemoveGithubOrgApprovalList) // If no entries after consolidating all the updates, we need to remove the column From 5f2e27055b2185daa663b5167e7f815955c58d61 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 27 Apr 2021 02:21:51 +0300 Subject: [PATCH 0242/1276] [#2897] Feature/API Invalidation of ICLA (#2898) - Added API that handles invalidation of ICLA and email notification Signed-off-by: Harold Wanyama --- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/signatures/email.go | 1 + cla-backend-go/swagger/cla.v2.yaml | 29 +++++++++ cla-backend-go/utils/cla_user.go | 17 ++++++ cla-backend-go/v2/signatures/handlers.go | 28 +++++++++ cla-backend-go/v2/signatures/service.go | 75 +++++++++++++++++++++++- 6 files changed, 150 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 97ae9b630..cbd9d8f58 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -281,7 +281,7 @@ func server(localMode bool) http.Handler { v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, v1CLAGroupRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, projectClaGroupRepo, v1CompanyService) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) - v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo) + v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo, signaturesRepo, usersService) v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) diff --git a/cla-backend-go/signatures/email.go b/cla-backend-go/signatures/email.go index 9a5b62eeb..b7a4c54fb 100644 --- a/cla-backend-go/signatures/email.go +++ b/cla-backend-go/signatures/email.go @@ -16,6 +16,7 @@ type InvalidateSignatureTemplateParams struct { ClaManager string RemovalCriteria string ProjectName string + ProjectManager string CLAManagers []ClaManagerInfoParams CLaManager string CLAGroupName string diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 8b8e30fe4..30b40dc8f 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3103,6 +3103,35 @@ paths: $ref: '#/responses/internal-server-error' tags: - gerrits + /cla-group/{claGroupID}/user/{userID}/icla: + put: + summary: Invalidate ICLA record + description: Invalidates a given ICLA record for a user + operationId: invalidateICLA + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-email" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/path-claGroupID" + - $ref: "#/parameters/path-userID" + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - signatures /company/{companyID}: get: diff --git a/cla-backend-go/utils/cla_user.go b/cla-backend-go/utils/cla_user.go index 8fc062b19..5470d1a90 100644 --- a/cla-backend-go/utils/cla_user.go +++ b/cla-backend-go/utils/cla_user.go @@ -4,6 +4,8 @@ package utils import ( + "strings" + "github.com/communitybridge/easycla/cla-backend-go/gen/models" ) @@ -23,3 +25,18 @@ func GetBestUsername(user *models.User) string { return "User Name Unknown" } + +// GetBestEmail is a helper function to return the best email address for the user model +func GetBestEmail(userModel *models.User) string { + if userModel.LfEmail != "" { + return userModel.LfEmail + } + + for _, email := range userModel.Emails { + if email != "" && !strings.Contains(email, "noreply.github.com") { + return email + } + } + + return "" +} diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index e07f27f39..e9acbac6a 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -1255,6 +1255,34 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj } }) }) + api.SignaturesInvalidateICLAHandler = signatures.InvalidateICLAHandlerFunc(func(params signatures.InvalidateICLAParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.signatures.handlers.SignaturesInvalidateICLAHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": params.ClaGroupID, + "userID": params.UserID, + } + log.WithFields(f).Debug("Invalidating ICLA record...") + eventArgs := &events.LogEventArgs{ + EventType: events.InvalidatedSignature, + EventData: &events.SignatureProjectInvalidatedEventData{ + InvalidatedCount: 1, + }, + } + err := v2service.InvalidateICLA(ctx, params.ClaGroupID, params.UserID, authUser, eventsService, eventArgs) + if err != nil { + msg := "unable to invalidate icla" + log.WithFields(f).Warn(msg) + // return signatures.NewInvalidateSignatureBadRequest().WithXRequestID(reqID).WithPayload( + // utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + return signatures.NewInvalidateICLABadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + return signatures.NewInvalidateICLAOK().WithXRequestID(reqID) + }) } // getProjectIDsFromModels is a helper function to extract the project SFIDs from the project CLA Group models diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index a60dd4039..252e9abd6 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -9,12 +9,14 @@ import ( "errors" "fmt" + "github.com/LF-Engineering/lfx-kit/auth" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/aws" + "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/jinzhu/copier" @@ -25,6 +27,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/project" "github.com/communitybridge/easycla/cla-backend-go/signatures" + "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" ) @@ -46,6 +49,8 @@ type service struct { v1ProjectService project.Service v1CompanyService company.IService v1SignatureService signatures.SignatureService + v1SignatureRepo signatures.SignatureRepository + usersService users.Service projectsClaGroupsRepo projects_cla_groups.Repository s3 *s3.S3 signaturesBucket string @@ -62,17 +67,20 @@ type Service interface { GetSignedDocument(ctx context.Context, signatureID string) (*models.SignedDocument, error) GetSignedIclaZipPdf(claGroupID string) (*models.URLObject, error) GetSignedCclaZipPdf(claGroupID string) (*models.URLObject, error) + InvalidateICLA(ctx context.Context, claGroupID string, userID string, authUser *auth.User, eventsService events.Service, eventArgs *events.LogEventArgs) error } // NewService creates instance of v2 signature service func NewService(awsSession *session.Session, signaturesBucketName string, v1ProjectService project.Service, v1CompanyService company.IService, v1SignatureService signatures.SignatureService, - pcgRepo projects_cla_groups.Repository) *service { + pcgRepo projects_cla_groups.Repository, v1SignatureRepo signatures.SignatureRepository, usersService users.Service) *service { return &service{ v1ProjectService: v1ProjectService, v1CompanyService: v1CompanyService, v1SignatureService: v1SignatureService, + v1SignatureRepo: v1SignatureRepo, + usersService: usersService, projectsClaGroupsRepo: pcgRepo, s3: s3.New(awsSession), signaturesBucket: signaturesBucketName, @@ -292,3 +300,68 @@ func (s service) GetClaGroupCorporateContributors(ctx context.Context, claGroupI return &resp, nil } + +func (s service) InvalidateICLA(ctx context.Context, claGroupID string, userID string, authUser *auth.User, eventsService events.Service, eventArgs *events.LogEventArgs) error { + f := logrus.Fields{ + "functionName": "v2.signatures.service.InvalidateICLA", + "claGroupID": claGroupID, + "userID": userID, + } + // Get signature record + log.WithFields(f).Debug("getting signature record ...") + icla, iclaErr := s.v1SignatureService.GetIndividualSignature(ctx, claGroupID, userID) + if iclaErr != nil { + log.WithFields(f).Debug("unable to get individual signature") + return iclaErr + } + + // Get cla Group + log.WithFields(f).Debug("getting clGroup...") + claGroup, claGrpErr := s.v1ProjectService.GetCLAGroupByID(ctx, claGroupID) + if claGrpErr != nil { + log.WithFields(f).Debug("unable to fetch cla Group record") + return claGrpErr + } + + //Get user record + user, userErr := s.usersService.GetUser(userID) + if userErr != nil { + log.WithFields(f).Debug("unable to get user record") + return userErr + } + + log.WithFields(f).Debug("invalidating signature record ...") + note := fmt.Sprintf("Signature invalidated (approved set to false) by %s for %s ", authUser.UserName, utils.GetBestUsername(user)) + err := s.v1SignatureRepo.InvalidateProjectRecord(ctx, icla.SignatureID, note) + if err != nil { + log.WithFields(f).Debug("unable to invalidate icla record") + return err + } + // send email + email := utils.GetBestEmail(user) + log.WithFields(f).Debugf("sending invalidation email to : %s ", email) + subject := fmt.Sprintf("EasyCLA: ICLA invalidated for %s ", claGroup.ProjectName) + params := signatures.InvalidateSignatureTemplateParams{ + RecipientName: utils.GetBestUsername(user), + ProjectManager: authUser.UserName, + CLAGroupName: claGroup.ProjectName, + } + body, renderErr := utils.RenderTemplate(claGroup.Version, signatures.InvalidateICLASignatureTemplateName, signatures.InvalidateICLASignatureTemplate, params) + if renderErr != nil { + log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) + } else { + err := utils.SendEmail(subject, body, []string{email}) + if err != nil { + log.WithFields(f).Debugf("unable to send approval list update email to : %s ", email) + } + } + + eventArgs.UserName = utils.GetBestUsername(user) + eventArgs.UserModel = user + eventArgs.ProjectName = claGroup.ProjectName + + // Log event + eventsService.LogEventWithContext(ctx, eventArgs) + + return nil +} From 417f789e0c38090f611a24d286b085051f9e865e Mon Sep 17 00:00:00 2001 From: makkalot Date: Wed, 28 Apr 2021 13:53:06 +0300 Subject: [PATCH 0243/1276] github action rename fix, resolve the note conflict in repository update Signed-off-by: makkalot --- cla-backend-go/repositories/repository.go | 6 +- cla-backend-go/v2/github_activity/service.go | 63 +++++++++++++------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index beaeeb1b6..a769d1c1c 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -234,9 +234,9 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, noteValue = fmt.Sprintf("%s. %s", repoModel.Note, noteValue) } } - expressionAttributeNames["#N"] = aws.String("note") - expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(noteValue)} - updateExpression = updateExpression + " #N = :n, " + expressionAttributeNames["#NO"] = aws.String("note") + expressionAttributeValues[":no"] = &dynamodb.AttributeValue{S: aws.String(noteValue)} + updateExpression = updateExpression + " #NO = :no, " } if input.Enabled != nil && repoModel.Enabled != *input.Enabled { diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index b5b9b5afe..3841f005b 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -84,7 +84,7 @@ func (s *eventHandlerService) ProcessRepositoryEvent(event *github.RepositoryEve return fmt.Errorf("missing repository object in event payload") } - log.Debugf("ProcessRepositoryEvent called for action : %s", *event.Action) + log.Debugf("ProcessRepositoryEvent called for action : %s for repository : %s", *event.Action, *event.Repo.Name) switch *event.Action { case "created": return s.handleRepositoryAddedAction(ctx, event.Sender, event.Repo) @@ -176,6 +176,8 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) } + log.WithFields(f).Infof("disabling repo : %s", repoModel.RepositoryID) + if err := s.githubRepo.DisableRepository(context.Background(), repoModel.RepositoryID); err != nil { log.WithFields(f).Warnf("disabling repo : %s failed : %v", *repo.FullName, err) return err @@ -237,6 +239,8 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) } + log.WithFields(f).Infof("renaming Github Repository from : %s to : %s", repoModel.RepositoryName, *repo.Name) + if _, err := s.githubRepo.UpdateGithubRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ RepositoryName: repo.Name, Note: "repository was renamed externally", @@ -298,6 +302,7 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont if repo.ID == nil || *repo.ID == 0 { return fmt.Errorf("missing repo id") } + repositoryExternalID := strconv.FormatInt(*repo.ID, 10) repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { @@ -310,6 +315,9 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont newOrganizationName := *org.Name oldOrganizationName := repoModel.RepositoryOrganizationName + + log.WithFields(f).Infof("running transfer for repository : %s from Github Org : %s to Github Org : %s", *repo.Name, oldOrganizationName, newOrganizationName) + // first check if it's a different organization name (could be a duplicate event) if oldOrganizationName == *org.Name { msg := fmt.Sprintf("nothing to change for github repo : %s, probably duplicate event was sent", repoModel.RepositoryName) @@ -325,30 +333,19 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont newGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, *org.Name) if err != nil { + disabledErr := s.disableFailedTransferRepo(ctx, sender, f, repoModel, oldGithubOrg, newGithubOrg) + if disabledErr != nil { + return disabledErr + } + return fmt.Errorf("fetching the new organization name : %s failed : %v", newOrganizationName, err) } // we need to check if the new org name has autoenabled and have a cla group set otherwise we can't proceed if !newGithubOrg.AutoEnabled || newGithubOrg.AutoEnabledClaGroupID == "" { - log.WithFields(f).Warnf("can't proceed with repo transfer operation because the new org doesn't have autoenabled=true, disabling the repo : %s", repoModel.RepositoryName) - if err := s.githubRepo.DisableRepository(ctx, repoModel.RepositoryID); err != nil { - return fmt.Errorf("disabling the repo : %s failed : %v", repoModel.RepositoryID, err) - } - - // send event for the disabled repository. - s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryDisabled, - ProjectID: repoModel.RepositoryProjectID, - UserID: *sender.Login, - EventData: &events.RepositoryDisabledEventData{ - RepositoryName: repoModel.RepositoryName, - }, - }) - - if s.sendEmail { - if err := s.notifyForGithubRepositoryTransferred(ctx, repoModel, oldGithubOrg, newGithubOrg, false); err != nil { - log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) - } + disabledErr := s.disableFailedTransferRepo(ctx, sender, f, repoModel, oldGithubOrg, newGithubOrg) + if disabledErr != nil { + return disabledErr } return fmt.Errorf("aborting the repository : %s transfer, new githubOrg : %s doesn't have claGroupID set", repoModel.RepositoryName, newGithubOrg.OrganizationName) @@ -385,6 +382,30 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont return nil } +func (s *eventHandlerService) disableFailedTransferRepo(ctx context.Context, sender *github.User, f logrus.Fields, repoModel *models.GithubRepository, oldGithubOrg *models.GithubOrganization, newGithubOrg *models.GithubOrganization) error { + log.WithFields(f).Warnf("can't proceed with repo transfer operation because the new org doesn't have autoenabled=true, disabling the repo : %s", repoModel.RepositoryName) + if err := s.githubRepo.DisableRepository(ctx, repoModel.RepositoryID); err != nil { + return fmt.Errorf("disabling the repo : %s failed : %v", repoModel.RepositoryID, err) + } + + // send event for the disabled repository. + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryDisabled, + ProjectID: repoModel.RepositoryProjectID, + UserID: *sender.Login, + EventData: &events.RepositoryDisabledEventData{ + RepositoryName: repoModel.RepositoryName, + }, + }) + + if s.sendEmail { + if err := s.notifyForGithubRepositoryTransferred(ctx, repoModel, oldGithubOrg, newGithubOrg, false); err != nil { + log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) + } + } + return nil +} + func (s *eventHandlerService) notifyForGithubRepositoryTransferred(ctx context.Context, repoModel *models.GithubRepository, oldGithubOrg *models.GithubOrganization, newGithubOrg *models.GithubOrganization, success bool) error { subject := fmt.Sprintf("EasyCLA: Github Repository Was Transferred") body, err := emails.RenderGithubRepositoryTransferredTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryTransferredTemplateParams{ @@ -425,6 +446,8 @@ func (s *eventHandlerService) handleRepositoryArchivedAction(ctx context.Context return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) } + log.WithFields(f).Infof("archiving repository : %s", repoModel.RepositoryName) + if s.sendEmail { subject := fmt.Sprintf("EasyCLA: Github Repository Was Archived") body, err := emails.RenderGithubRepositoryArchivedTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryArchivedTemplateParams{ From 560ac38dca90b0ba50bd0d18d16e1db36df6738c Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 28 Apr 2021 19:11:56 +0300 Subject: [PATCH 0244/1276] some defensive code against the data coming from github about transferred action (#2901) transferred action Signed-off-by: makkalot --- cla-backend-go/v2/github_activity/service.go | 28 +++++++++++++------ .../v2/github_activity/service_test.go | 4 +-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index 3841f005b..19b12d989 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -292,10 +292,20 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, } func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Context, sender *github.User, repo *github.Repository, org *github.Organization) error { + + if repo.Name == nil { + return fmt.Errorf("missing repo name can't proceed with transfer") + } + repoName := *repo.Name + + if org.Login == nil { + return fmt.Errorf("missing organization login information can't proceed with transferring the rpo : %s", *org.Name) + } + f := logrus.Fields{ "functionName": "v2.github_activity.service.handleRepositoryTransferredAction", - "repositoryName": *repo.Name, - "newGithubOrganization": *org.Name, + "repositoryName": repoName, + "newGithubOrganization": *org.Login, utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } @@ -307,19 +317,19 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { - log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) + log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", repoName) return nil } - return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", *repo.FullName, repositoryExternalID, err) + return fmt.Errorf("fetching the repo : %s by external id : %s failed : %v", repoName, repositoryExternalID, err) } - newOrganizationName := *org.Name + newOrganizationName := *org.Login oldOrganizationName := repoModel.RepositoryOrganizationName - log.WithFields(f).Infof("running transfer for repository : %s from Github Org : %s to Github Org : %s", *repo.Name, oldOrganizationName, newOrganizationName) + log.WithFields(f).Infof("running transfer for repository : %s from Github Org : %s to Github Org : %s", repoName, oldOrganizationName, newOrganizationName) // first check if it's a different organization name (could be a duplicate event) - if oldOrganizationName == *org.Name { + if oldOrganizationName == newOrganizationName { msg := fmt.Sprintf("nothing to change for github repo : %s, probably duplicate event was sent", repoModel.RepositoryName) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) @@ -331,7 +341,7 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont return fmt.Errorf("fetching the old organization name : %s failed : %v", oldOrganizationName, err) } - newGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, *org.Name) + newGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, newOrganizationName) if err != nil { disabledErr := s.disableFailedTransferRepo(ctx, sender, f, repoModel, oldGithubOrg, newGithubOrg) if disabledErr != nil { @@ -354,7 +364,7 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont _, err = s.githubRepo.UpdateGithubRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ Note: fmt.Sprintf("repository was transferred from org : %s to : %s", oldGithubOrg.OrganizationName, newGithubOrg.OrganizationName), RepositoryOrganizationName: aws.String(newGithubOrg.OrganizationName), - RepositoryURL: repo.URL, + RepositoryURL: repo.HTMLURL, }) if err != nil { diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go index 12514dd01..8f7e5767c 100644 --- a/cla-backend-go/v2/github_activity/service_test.go +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -167,10 +167,10 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredA Repo: &github.Repository{ ID: aws.Int64(1), Name: &repoName, - URL: &newRepoUrl, + HTMLURL: &newRepoUrl, }, Org: &github.Organization{ - Name: &newOrgName, + Login: &newOrgName, }, Sender: &github.User{ Login: aws.String("githubLoginValue"), From 83f2720a16b6c814117c6c807dc70e6b07250c65 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 28 Apr 2021 14:38:48 -0700 Subject: [PATCH 0245/1276] [#2886] Updated Event Log Details for Approval List (#2902) - updated event log, data structures, etc. Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 200 +++++++++++++----- cla-backend-go/signatures/service.go | 24 --- .../v2/github_activity/service_test.go | 6 +- 3 files changed, 147 insertions(+), 83 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index d02bc56f0..fced9444f 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -255,65 +255,41 @@ type CLAManagerRequestDeletedEventData struct { // CLAApprovalListAddEmailData . . . type CLAApprovalListAddEmailData struct { - UserName string - UserEmail string - UserLFID string ApprovalListEmail string } // CLAApprovalListRemoveEmailData . . . type CLAApprovalListRemoveEmailData struct { - UserName string - UserEmail string - UserLFID string ApprovalListEmail string } // CLAApprovalListAddDomainData . . . type CLAApprovalListAddDomainData struct { - UserName string - UserEmail string - UserLFID string ApprovalListDomain string } // CLAApprovalListRemoveDomainData . . . type CLAApprovalListRemoveDomainData struct { - UserName string - UserEmail string - UserLFID string ApprovalListDomain string } // CLAApprovalListAddGitHubUsernameData . . . type CLAApprovalListAddGitHubUsernameData struct { - UserName string - UserEmail string - UserLFID string ApprovalListGitHubUsername string } // CLAApprovalListRemoveGitHubUsernameData . . . type CLAApprovalListRemoveGitHubUsernameData struct { - UserName string - UserEmail string - UserLFID string ApprovalListGitHubUsername string } // CLAApprovalListAddGitHubOrgData . . . type CLAApprovalListAddGitHubOrgData struct { - UserName string - UserEmail string - UserLFID string ApprovalListGitHubOrg string } // CLAApprovalListRemoveGitHubOrgData . . . type CLAApprovalListRemoveGitHubOrgData struct { - UserName string - UserEmail string - UserLFID string ApprovalListGitHubOrg string } @@ -724,10 +700,21 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventDetailsString(args *LogEven // GetEventDetailsString . . . func (ed *CLAApprovalListAddEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Email: %s to the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The email address %s was added to the approval list", ed.ApprovalListEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -735,10 +722,21 @@ func (ed *CLAApprovalListAddEmailData) GetEventDetailsString(args *LogEventArgs) // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Email: %s from the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListEmail, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The email address %s was removed from the approval list", ed.ApprovalListEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -746,10 +744,21 @@ func (ed *CLAApprovalListRemoveEmailData) GetEventDetailsString(args *LogEventAr // GetEventDetailsString . . . func (ed *CLAApprovalListAddDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added Domain: %s to the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The email address domain %s was added to the approval list", ed.ApprovalListDomain) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -757,10 +766,21 @@ func (ed *CLAApprovalListAddDomainData) GetEventDetailsString(args *LogEventArgs // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed Domain %s from the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListDomain, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The email address domain %s was removed from the approval list", ed.ApprovalListDomain) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -768,10 +788,21 @@ func (ed *CLAApprovalListRemoveDomainData) GetEventDetailsString(args *LogEventA // GetEventDetailsString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Username: %s to the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The GitHub username %s was added to the approval list", ed.ApprovalListGitHubUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -779,10 +810,21 @@ func (ed *CLAApprovalListAddGitHubUsernameData) GetEventDetailsString(args *LogE // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Username: %s from the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubUsername, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The GitHub username %s was removed from the approval list", ed.ApprovalListGitHubUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -790,10 +832,21 @@ func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventDetailsString(args *L // GetEventDetailsString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s added GitHub Organization: %s to the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The GitHub organization %s was added to the approval list", ed.ApprovalListGitHubOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -801,10 +854,21 @@ func (ed *CLAApprovalListAddGitHubOrgData) GetEventDetailsString(args *LogEventA // GetEventDetailsString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Manager: %s, Email: %s, LFID: %s removed GitHub Organization: %s from the approval list for Company: %s, Project: %s", - ed.UserName, ed.UserEmail, ed.UserLFID, ed.ApprovalListGitHubOrg, args.CompanyName, args.ProjectName) + data := fmt.Sprintf("The GitHub organization %s was removed from the approval list", ed.ApprovalListGitHubOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) } data = data + "." return data, true @@ -1568,7 +1632,7 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventSummaryString(args *LogEven // GetEventSummaryString . . . func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the email %s to the approval list", args.UserName, ed.ApprovalListEmail) + data := fmt.Sprintf("The email address %s was added to the approval list", ed.ApprovalListEmail) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1578,13 +1642,16 @@ func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the email %s from the approval list", args.UserName, ed.ApprovalListEmail) + data := fmt.Sprintf("The email address %s was removed from the approval list", ed.ApprovalListEmail) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1594,13 +1661,16 @@ func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventAr if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the domain %s to the approval list", args.UserName, ed.ApprovalListDomain) + data := fmt.Sprintf("The email address domain %s was added to the approval list", ed.ApprovalListDomain) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1610,13 +1680,16 @@ func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the domain %s from the approval list", args.UserName, ed.ApprovalListDomain) + data := fmt.Sprintf("The email address domain %s was removed from the approval list", ed.ApprovalListDomain) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1626,13 +1699,16 @@ func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventA if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the GitHub username %s to the approval list", args.UserName, ed.ApprovalListGitHubUsername) + data := fmt.Sprintf("The GitHub username %s was added to the approval list", ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1642,13 +1718,16 @@ func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogE if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the GitHub username %s from the approval list", args.UserName, ed.ApprovalListGitHubUsername) + data := fmt.Sprintf("The GitHub username %s was removed from the approval list", ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1658,13 +1737,16 @@ func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *L if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s added the GitHub organization %s to the approval list", args.UserName, ed.ApprovalListGitHubOrg) + data := fmt.Sprintf("The GitHub organization %s was added to the approval list", ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1674,13 +1756,16 @@ func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventA if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } // GetEventSummaryString . . . func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Manager %s removed the GitHub organization %s from the approval list", args.UserName, ed.ApprovalListGitHubOrg) + data := fmt.Sprintf("The GitHub organization %s was removed from the approval list", ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1690,6 +1775,9 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEve if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } data = data + "." return data, true } diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 340d6d3da..3df3b867b 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -664,9 +664,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddEmailData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListEmail: value, }, }) @@ -684,9 +681,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveEmailData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListEmail: value, }, }) @@ -704,9 +698,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddDomainData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListDomain: value, }, }) @@ -724,9 +715,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveDomainData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListDomain: value, }, }) @@ -744,9 +732,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddGitHubUsernameData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListGitHubUsername: value, }, }) @@ -764,9 +749,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveGitHubUsernameData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListGitHubUsername: value, }, }) @@ -784,9 +766,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListAddGitHubOrgData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListGitHubOrg: value, }, }) @@ -805,9 +784,6 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAApprovalListRemoveGitHubOrgData{ - UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, - UserLFID: userModel.UserID, ApprovalListGitHubOrg: value, }, }) diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go index 8f7e5767c..1c0a623cb 100644 --- a/cla-backend-go/v2/github_activity/service_test.go +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -165,9 +165,9 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredA err := activityService.ProcessRepositoryEvent(&github.RepositoryEvent{ Action: aws.String("transferred"), Repo: &github.Repository{ - ID: aws.Int64(1), - Name: &repoName, - HTMLURL: &newRepoUrl, + ID: aws.Int64(1), + Name: &repoName, + HTMLURL: &newRepoUrl, }, Org: &github.Organization{ Login: &newOrgName, From 785b259606d845a08d8597e19d71b459990788d4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 28 Apr 2021 15:39:31 -0700 Subject: [PATCH 0246/1276] Added Defensive Logic to Prevent Setting Enabled Services on TLF Projects (#2903) Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/helpers.go | 33 ++++++++++++--------- cla-backend-go/v2/project-service/client.go | 13 ++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 57edc2ded..2506e7897 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -520,21 +520,26 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, cla if parentLookupErr != nil || parentProjectSFID == "" { log.WithFields(f).WithError(parentLookupErr).Warnf("unable to lookup parent project SFID for project: %s", projectSFID) } else { - log.WithFields(f).Debugf("enabling parent project CLA service for project SFID: %s...", parentProjectSFID) - enableProjectErr := psClient.EnableCLA(parentProjectSFID) - if enableProjectErr != nil { - log.WithFields(f).WithError(enableProjectErr).Warnf("unable to enable CLA service for project: %s, error: %+v", parentProjectSFID, enableProjectErr) - errorList = append(errorList, enableProjectErr) + isTheLF, lookupErr := psClient.IsTheLinuxFoundation(parentProjectSFID) + if lookupErr != nil || isTheLF { + log.WithFields(f).Debugf("skipping setting the enabled services on The Linux Foundation parent project(s) for parent project SFID: %s", parentProjectSFID) } else { - log.WithFields(f).Debugf("enabled CLA service for parent project: %s", parentProjectSFID) - // add event log entry - s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.ProjectServiceCLAEnabled, - ProjectID: parentProjectSFID, - CLAGroupID: claGroupID, - LfUsername: authUser.UserName, - EventData: &events.ProjectServiceCLAEnabledData{}, - }) + log.WithFields(f).Debugf("enabling parent project CLA service for project SFID: %s...", parentProjectSFID) + enableProjectErr := psClient.EnableCLA(parentProjectSFID) + if enableProjectErr != nil { + log.WithFields(f).WithError(enableProjectErr).Warnf("unable to enable CLA service for project: %s, error: %+v", parentProjectSFID, enableProjectErr) + errorList = append(errorList, enableProjectErr) + } else { + log.WithFields(f).Debugf("enabled CLA service for parent project: %s", parentProjectSFID) + // add event log entry + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ProjectServiceCLAEnabled, + ProjectID: parentProjectSFID, + CLAGroupID: claGroupID, + LfUsername: authUser.UserName, + EventData: &events.ProjectServiceCLAEnabledData{}, + }) + } } } } diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index a8e438beb..a9072c2d9 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -4,6 +4,8 @@ package project_service import ( + "errors" + "fmt" "strings" "github.com/sirupsen/logrus" @@ -232,6 +234,17 @@ func (pmm *Client) EnableCLA(projectSFID string) error { "apiGWHost": apiGWHost, } + theLF, lookupErr := pmm.IsTheLinuxFoundation(projectSFID) + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Warnf("unable to test if project is The Linux Foundation using projectSFID: %s", projectSFID) + return lookupErr + } + if theLF { + msg := fmt.Sprintf("unable to set the enabled CLA services for The Linux Foundation with projectSFID: %s - not allowed", projectSFID) + log.WithFields(f).Debug(msg) + return errors.New(msg) + } + tok, err := token.GetToken() if err != nil { log.WithFields(f).WithError(err).Warning("problem retrieving token") From c5eda16fa2a3a359499d27bbd9dbe85edff3bff1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 29 Apr 2021 15:46:54 -0700 Subject: [PATCH 0247/1276] Updated the CLA Manager Add/Remove Event Log Text (#2904) - Added authUser object as parameters to service calls - Added additional debug for templates as well Signed-off-by: David Deal --- cla-backend-go/cla_manager/handlers.go | 15 ++++++++++-- cla-backend-go/cla_manager/service.go | 27 ++++++++++---------- cla-backend-go/events/event_data.go | 30 ++++++++++++++++++++--- cla-backend-go/template/service.go | 19 ++++++++++---- cla-backend-go/v2/cla_manager/handlers.go | 4 +-- cla-backend-go/v2/cla_manager/service.go | 16 ++++++------ 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 99e15ce8d..4d16cd222 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/LF-Engineering/lfx-kit/auth" + user_service "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" "github.com/sirupsen/logrus" @@ -713,7 +715,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Audit Event sent from service upon success - signature, addErr := service.AddClaManager(ctx, params.CompanyID, params.ProjectID, params.Body.UserLFID, "") + signature, addErr := service.AddClaManager(ctx, ToAuthUser(claUser), params.CompanyID, params.ProjectID, params.Body.UserLFID, "") if addErr != nil { msg := buildErrorMessageAddManager("Add CLA Manager - Service Error", params, addErr) log.Warn(msg) @@ -825,7 +827,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. } // Audit Event sent from service upon success - signature, deleteErr := service.RemoveClaManager(ctx, params.CompanyID, params.ProjectID, params.UserLFID) + signature, deleteErr := service.RemoveClaManager(ctx, ToAuthUser(claUser), params.CompanyID, params.ProjectID, params.UserLFID) if deleteErr != nil { msg := buildErrorMessageDeleteManager("EasyCLA - 400 Bad Request - Delete CLA Manager - Service Error", params, deleteErr) @@ -1014,3 +1016,12 @@ func sendRequestDeniedEmailToRequester(emailSvc emails.EmailTemplateService, ema log.Debugf("sent email with subject: %s to recipients: %+v", subject, recipients) } } + +// ToAuthUser converts a legacy v1 CLA user to a v2 platform auth user +func ToAuthUser(claUser *user.CLAUser) *auth.User { + return &auth.User{ + UserName: claUser.LFUsername, + Email: claUser.LFEmail, + ACL: auth.ACL{}, + } +} diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index f8e177443..39bad8916 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -35,8 +37,8 @@ type IService interface { PendingRequest(companyID, claGroupID, requestID string) (*models.ClaManagerRequest, error) DeleteRequest(requestID string) error - AddClaManager(ctx context.Context, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) - RemoveClaManager(ctx context.Context, companyID string, claGroupID string, LFID string) (*models.Signature, error) + AddClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) + RemoveClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string) (*models.Signature, error) } type service struct { @@ -189,7 +191,7 @@ func (s service) DeleteRequest(requestID string) error { } // AddClaManager Adds LFID to Signature Access Control List list -func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) { +func (s service) AddClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) { userModel, userErr := s.usersService.GetUserByLFUserName(LFID) if userErr != nil || userModel == nil { @@ -254,16 +256,14 @@ func (s service) AddClaManager(ctx context.Context, companyID string, claGroupID // Send an event s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerCreated, - ProjectID: claGroupModel.ProjectExternalID, + UserName: authUser.UserName, CLAGroupID: claGroupID, CLAGroupName: claGroupModel.ProjectName, ClaGroupModel: claGroupModel, + ProjectID: claGroupModel.ProjectExternalID, + ProjectSFID: claGroupModel.ProjectExternalID, CompanyID: companyID, CompanyModel: companyModel, - LfUsername: LFID, - UserID: LFID, - UserModel: userModel, - ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAManagerCreatedEventData{ CompanyName: companyModel.CompanyName, ProjectName: claGroupModel.ProjectName, @@ -300,7 +300,7 @@ func (s service) getCompanySignature(ctx context.Context, companyID string, claG } // RemoveClaManager removes lfid from signature acl with given company and project -func (s service) RemoveClaManager(ctx context.Context, companyID string, claGroupID string, LFID string) (*models.Signature, error) { +func (s service) RemoveClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string) (*models.Signature, error) { userModel, userErr := s.usersService.GetUserByLFUserName(LFID) if userErr != nil || userModel == nil { @@ -367,16 +367,15 @@ func (s service) RemoveClaManager(ctx context.Context, companyID string, claGrou // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ EventType: events.ClaManagerDeleted, - ProjectID: claGroupModel.ProjectExternalID, + LfUsername: userModel.LfUsername, + UserName: authUser.UserName, CLAGroupID: claGroupID, CLAGroupName: claGroupModel.ProjectName, ClaGroupModel: claGroupModel, + ProjectID: claGroupModel.ProjectExternalID, + ProjectSFID: claGroupModel.ProjectExternalID, CompanyID: companyID, CompanyModel: companyModel, - LfUsername: userModel.LfUsername, - UserID: LFID, - UserModel: userModel, - ProjectSFID: claGroupModel.ProjectExternalID, EventData: &events.CLAManagerDeletedEventData{ CompanyName: companyModel.CompanyName, ProjectName: claGroupModel.ProjectName, diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index fced9444f..ec1be67d1 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -645,8 +645,19 @@ func (ed *CLAManagerRequestCreatedEventData) GetEventDetailsString(args *LogEven // GetEventDetailsString . . . func (ed *CLAManagerCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s, LFID: %s, Email: %s was added as CLA Manager for Company: %s, Project: %s.", - ed.UserName, ed.UserLFID, ed.UserEmail, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The user: %s LFID: %s, email: %s was added as CLA Manager", ed.UserName, ed.UserLFID, ed.UserEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -656,8 +667,19 @@ func (ed *CLAManagerCreatedEventData) GetEventDetailsString(args *LogEventArgs) // GetEventDetailsString . . . func (ed *CLAManagerDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("User: %s LFID: %s, Email: %s was removed as CLA Manager for Company: %s, Project: %s ", - ed.UserName, ed.UserLFID, ed.UserEmail, ed.CompanyName, ed.ProjectName) + data := fmt.Sprintf("The user: %s LFID: %s, email: %s was removed as CLA Manager", ed.UserName, ed.UserLFID, ed.UserEmail) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index 50e25cbaf..d6ab26a50 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -59,7 +59,7 @@ func NewService(stage string, templateRepo Repository, docraptorClient docraptor // GetTemplates API call func (s service) GetTemplates(ctx context.Context) ([]models.Template, error) { f := logrus.Fields{ - "functionName": "GetTemplates", + "functionName": "v1.template.service.GetTemplates", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } log.WithFields(f).Debug("Loading templates...") @@ -81,7 +81,7 @@ func (s service) GetTemplates(ctx context.Context) ([]models.Template, error) { func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) { f := logrus.Fields{ - "functionName": "CreateTemplatePreview", + "functionName": "v1.template.service.CreateTemplatePreview", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "templateID": claGroupFields.TemplateID, "templateFor": templateFor, @@ -93,6 +93,7 @@ func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode if claGroupFields.TemplateID != "" { templateID = claGroupFields.TemplateID } + log.WithFields(f).Debugf("using template ID: %s", templateID) // Get Template template, err = s.templateRepo.GetTemplate(templateID) @@ -101,6 +102,7 @@ func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode claGroupFields.TemplateID) return nil, err } + log.WithFields(f).Debugf("loaded template ID: %s with ID: %s", template.Name, template.ID) // Apply template fields iclaTemplateHTML, cclaTemplateHTML, err := s.InjectProjectInformationIntoTemplate(template, claGroupFields.MetaFields) @@ -134,7 +136,7 @@ func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode // CreateCLAGroupTemplate func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) { f := logrus.Fields{ - "functionName": "CreateCLAGroupTemplate", + "functionName": "v1.template.service.CreateCLAGroupTemplate", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "claGroupFields": claGroupFields, @@ -265,7 +267,7 @@ func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, func (s service) GetCLATemplatePreview(ctx context.Context, claGroupID, claType string, watermark bool) ([]byte, error) { f := logrus.Fields{ - "functionName": "GetCLATemplatePreview", + "functionName": "v1.template.service.GetCLATemplatePreview", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "claType": claType, @@ -357,6 +359,11 @@ func (s service) GetCLATemplatePreview(ctx context.Context, claGroupID, claType // InjectProjectInformationIntoTemplate func (s service) InjectProjectInformationIntoTemplate(template models.Template, metaFields []*models.MetaField) (string, string, error) { + f := logrus.Fields{ + "functionName": "v1.template.service.InjectProjectInformationIntoTemplate", + "templateName": template.Name, + "templateID": template.ID, + } lookupMap := map[string]models.MetaField{} for _, field := range template.MetaFields { lookupMap[field.Name] = *field @@ -381,11 +388,13 @@ func (s service) InjectProjectInformationIntoTemplate(template models.Template, return "", "", errors.New("bad request: required fields for template were not found") } + log.WithFields(f).Debugf("Rendering ICLA body for template: %s with id: %s", template.Name, template.ID) iclaTemplateHTML, err := raymond.Render(template.IclaHTMLBody, metaFieldsMap) if err != nil { return "", "", err } + log.WithFields(f).Debugf("Rendering CCLA body for template: %s with id: %s", template.Name, template.ID) cclaTemplateHTML, err := raymond.Render(template.CclaHTMLBody, metaFieldsMap) if err != nil { return "", "", err @@ -414,7 +423,7 @@ func (s service) generateTemplateS3FilePath(claGroupID, claType string) string { // SaveTemplateToS3 func (s service) SaveTemplateToS3(bucket, filepath string, template io.ReadCloser) (string, error) { f := logrus.Fields{ - "functionName": "SaveTemplateToS3", + "functionName": "v1.template.service.SaveTemplateToS3", "bucket": bucket, "filepath": filepath, } diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 4f134468b..a86b48f58 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -78,7 +78,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C return cla_manager.NewCreateCLAManagerInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, err.Error(), err)) } - compCLAManager, errorResponse := service.CreateCLAManager(ctx, cginfo.ClaGroupID, params, authUser.UserName) + compCLAManager, errorResponse := service.CreateCLAManager(ctx, authUser, cginfo.ClaGroupID, params, authUser.UserName) if errorResponse != nil { if errorResponse.Code == BadRequest { return cla_manager.NewCreateCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(errorResponse) @@ -126,7 +126,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - errResponse := service.DeleteCLAManager(ctx, cginfo.ClaGroupID, params) + errResponse := service.DeleteCLAManager(ctx, authUser, cginfo.ClaGroupID, params) if errResponse != nil { return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(errResponse) } diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index c154d1811..aa755b1d2 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -82,8 +82,8 @@ type service struct { // Service interface type Service interface { - CreateCLAManager(ctx context.Context, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) - DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse + CreateCLAManager(ctx context.Context, authUser *auth.User, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) + DeleteCLAManager(ctx context.Context, authUser *auth.User, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse InviteCompanyAdmin(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, name string, contributor *v1User.User) ([]*models.ClaManagerDesignee, error) CreateCLAManagerDesignee(ctx context.Context, companyID string, projectID string, userEmail string) (*models.ClaManagerDesignee, error) CreateCLAManagerRequest(ctx context.Context, contactAdmin bool, companyID string, projectID string, userEmail string, fullName string, authUser *auth.User) (*models.ClaManagerDesignee, error) @@ -120,7 +120,7 @@ func NewService(emailTemplateService emails.EmailTemplateService, compService co } // CreateCLAManager creates Cla Manager -func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) { +func (s *service) CreateCLAManager(ctx context.Context, authUser *auth.User, claGroupID string, params cla_manager.CreateCLAManagerParams, authUsername string) (*models.CompanyClaManager, *models.ErrorResponse) { f := logrus.Fields{ "functionName": "cla_manager.service.CreateCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -237,7 +237,7 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param } // Add CLA Manager to Database - signature, addErr := s.managerService.AddClaManager(ctx, v1CompanyModel.CompanyID, claGroupID, user.Username, projectSF.Name) + signature, addErr := s.managerService.AddClaManager(ctx, authUser, v1CompanyModel.CompanyID, claGroupID, user.Username, projectSF.Name) if addErr != nil { msg := buildErrorMessageCreate(params, addErr) log.WithFields(f).Warn(msg) @@ -273,17 +273,17 @@ func (s *service) CreateCLAManager(ctx context.Context, claGroupID string, param return claCompanyManager, nil } -func (s *service) DeleteCLAManager(ctx context.Context, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse { +func (s *service) DeleteCLAManager(ctx context.Context, authUser *auth.User, claGroupID string, params cla_manager.DeleteCLAManagerParams) *models.ErrorResponse { f := logrus.Fields{ "functionName": "cla_manager.service.DeleteCLAManager", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, - "xUserName": params.XUSERNAME, - "xEmail": params.XEMAIL, + "authUserName": authUser.UserName, + "authUserEmail": authUser.Email, } - signature, deleteErr := s.managerService.RemoveClaManager(ctx, params.CompanyID, claGroupID, params.UserLFID) + signature, deleteErr := s.managerService.RemoveClaManager(ctx, authUser, params.CompanyID, claGroupID, params.UserLFID) if deleteErr != nil { msg := buildErrorMessageDelete(params, deleteErr) From c43fc6fc8927327f817f4e4a55c6561b07075c8b Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 30 Apr 2021 02:40:26 +0300 Subject: [PATCH 0248/1276] Bug/Invalidate Gerrit (#2905) - Resolved email and email domain approval list removal invalidation for gerrit use cases Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/repository.go | 89 +++++++++---------------- 1 file changed, 31 insertions(+), 58 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 67c54ad5a..432b71ece 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2138,10 +2138,24 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model Error: fmt.Errorf("unable to get icla for user: %s ", user.UserID), } } else { + + // Update gerrit user + if utils.StringInSlice(user.LfUsername, gerritICLAECLAs) { + gerritIclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeICLA) + if gerritIclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).WithError(gerritIclaErr).Warn(msg) + } + eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeECLA) + if eclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).WithError(eclaErr).Warn(msg) + } + } results <- &ICLAUserResponse{ ICLASignature: &models.IclaSignature{ GithubUsername: icla.UserGHUsername, - LfUsername: icla.UserLFID, + LfUsername: user.LfUsername, SignatureID: icla.SignatureID, }, } @@ -2165,25 +2179,6 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Send email repo.sendEmail(ctx, email, &approvalList, iclas, eclas) - //update gerrit permissions - gerritUser, getGerritUserErr := repo.getGerritUserByEmail(ctx, email, gerritICLAECLAs) - if getGerritUserErr != nil || gerritUser == nil { - msg := fmt.Sprintf("unable to get gerrit user by email : %s ", email) - log.WithFields(f).WithError(getGerritUserErr).Warn(msg) - return - } - iclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, gerritUser.LfUsername, utils.ClaTypeICLA) - if iclaErr != nil { - msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", gerritUser.LfUsername, approvalList.ClaGroupID) - log.WithFields(f).Warn(msg) - return - } - eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, gerritUser.LfUsername, utils.ClaTypeECLA) - if eclaErr != nil { - msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", gerritUser.LfUsername, approvalList.ClaGroupID) - log.WithFields(f).Warn(msg) - return - } }(email) } wg.Wait() @@ -2613,30 +2608,6 @@ func (repo repository) invalidateSignatures(ctx context.Context, approvalList *A } } -// getGerritUsersByEmail searches gerrit instances for users with given email -func (repo repository) getGerritUserByEmail(ctx context.Context, email string, gerritICLAECLAs []string) (*models.User, error) { - f := logrus.Fields{ - "functionName": "v1.signatures.repository.getGerritUserByEmail", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "email": email, - } - - log.WithFields(f).Debugf("checking gerrit user for email: %s ", email) - if email != "" { - claUser, err := repo.usersRepo.GetUserByEmail(email) - if err != nil { - msg := fmt.Sprintf("unable to get easyclauser by email: %s ", email) - log.WithFields(f).Warn(msg) - return nil, err - } - if utils.StringInSlice(claUser.LfUsername, gerritICLAECLAs) { - return claUser, nil - } - } - - return nil, nil -} - // verify UserApprovals checks user func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatureID string, claManager *models.User, approvalList *ApprovalList) (*models.User, error) { f := logrus.Fields{ @@ -2659,9 +2630,10 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur if approvalList.Criteria == utils.EmailDomainCriteria { // Handle Domains + log.WithFields(f).Debugf("Handling domain for user email: %s with approval list: %+v ", email, approvalList.ApprovalList) domain := strings.Split(email, "@")[1] - if utils.StringInSlice(domain, approvalList.DomainApprovals) { - if !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) && !utils.StringInSlice(email, approvalList.EmailApprovals) { + if utils.StringInSlice(domain, approvalList.ApprovalList) { + if (!utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) || utils.StringInSlice(user.LfUsername, approvalList.GerritICLAECLAs)) && !utils.StringInSlice(email, approvalList.EmailApprovals) { //Invalidate record note := fmt.Sprintf("Signature invalidated (approved set to false) by %s due to %s removal", utils.GetBestUsername(claManager), utils.EmailDomainCriteria) err := repo.InvalidateProjectRecord(ctx, signatureID, note) @@ -2670,18 +2642,19 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur return user, err } - log.WithFields(f).Debugf("removing gerrit user:%s from claGroup: %s ...", user.LfUsername, approvalList.ClaGroupID) - iclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeICLA) - if iclaErr != nil { - msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) - log.WithFields(f).Warn(msg) - return user, iclaErr - } - eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeECLA) - if eclaErr != nil { - msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) - log.WithFields(f).Warn(msg) - return user, eclaErr + // Update Gerrit group users + if utils.StringInSlice(user.LfUsername, approvalList.GerritICLAECLAs) { + log.WithFields(f).Debugf("removing gerrit user:%s from claGroup: %s ...", user.LfUsername, approvalList.ClaGroupID) + iclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeICLA) + if iclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).Warn(msg) + } + eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeECLA) + if eclaErr != nil { + msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + log.WithFields(f).Warn(msg) + } } } } From d70722ed1497b2603a1eeee867a3ebe2a3707b09 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 30 Apr 2021 09:19:40 +0300 Subject: [PATCH 0249/1276] [#2891] Bug/Search ICLA (#2899) - Resolved failed search for iclas based on signature_reference_name or email Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/repository.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 432b71ece..8de69888f 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2877,6 +2877,13 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + if searchTerm != nil { + log.WithFields(f).Debugf("adding search term filter for : %s ", *searchTerm) + filterAdded := true + searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*searchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*searchTerm))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + } + // Use the builder to create the expression expr, err := expression.NewBuilder(). WithKeyCondition(condition). From ef49a25434615141e6c5b29944412c517b80132a Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 30 Apr 2021 16:53:46 +0300 Subject: [PATCH 0250/1276] [#2842] Feature/Email Subject (#2906) - Updated email subject for signature invalidation Signed-off-by: Harold Wanyama --- cla-backend-go/signatures/repository.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 8de69888f..6d6c8fb17 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2478,8 +2478,8 @@ func (repo repository) sendEmail(ctx context.Context, email string, approvalList // Send CCLA Email if removalType == CCLA { - subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) - log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + subject := fmt.Sprintf("EasyCLA: CCLA invalidated for :%s ", approvalList.ClaGroupName) + log.WithFields(f).Debugf("sending ccla invalidation email to :%s ", email) body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateCCLASignatureTemplateName, InvalidateCCLASignatureTemplate, params) if renderErr != nil { log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) @@ -2490,8 +2490,8 @@ func (repo repository) sendEmail(ctx context.Context, email string, approvalList } } } else if removalType == ICLA { - subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) - log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + subject := fmt.Sprintf("EasyCLA: ICLA invalidated for :%s ", approvalList.ClaGroupName) + log.WithFields(f).Debugf("sending icla invalidation email to :%s ", email) body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateICLASignatureTemplateName, InvalidateICLASignatureTemplate, params) if renderErr != nil { log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) @@ -2502,8 +2502,8 @@ func (repo repository) sendEmail(ctx context.Context, email string, approvalList } } } else if removalType == CCLAICLA { - subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) - log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + subject := fmt.Sprintf("EasyCLA: ICLA invalidated for :%s ", approvalList.ClaGroupName) + log.WithFields(f).Debugf("sending icla invalidation email to :%s ", email) body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateCCLAICLASignatureTemplateName, InvalidateCCLASignatureTemplate, params) if renderErr != nil { log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) @@ -2514,8 +2514,8 @@ func (repo repository) sendEmail(ctx context.Context, email string, approvalList } } } else if removalType == CCLAICLAECLA { - subject := fmt.Sprintf("EasyCLA: Approval List Update for :%s ", email) - log.WithFields(f).Debugf("sending approval list update removal for :%s ", email) + subject := fmt.Sprintf("EasyCLA: Employee Acknowledgement invalidated for :%s ", approvalList.ClaGroupName) + log.WithFields(f).Debugf("sending employee acknowledgement invalidation email to :%s ", email) body, renderErr := utils.RenderTemplate(approvalList.Version, InvalidateCCLAICLAECLASignatureTemplateName, InvalidateCCLAICLAECLASignatureTemplate, params) if renderErr != nil { log.WithFields(f).Debugf("unable to render email approval template for user: %s ", email) From 25fdfb5055e37625e426078a8796ca9adb88b714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 May 2021 10:31:30 -0700 Subject: [PATCH 0251/1276] Bump urllib3 from 1.25.7 to 1.25.8 in /cla-backend (#2908) Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.7 to 1.25.8. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.7...1.25.8) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 82e845d8c..0d872f646 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -53,7 +53,7 @@ six==1.13.0 soupsieve==1.9.5 termcolor==1.1.0 typed-ast==1.4.1 -urllib3==1.25.7 +urllib3==1.25.8 vintage==0.4.1 wcwidth==0.1.7 Werkzeug==0.15.5 From 1ea0ead7eefdd436af5792657b591e7473086ab5 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 4 May 2021 02:00:23 +0300 Subject: [PATCH 0252/1276] [#2886] Bug/Event Logging (#2909) - Resolved foundation event logging with TLF as parent - Added logging for cla manager add/removal - Added lf username to eventargs params Signed-off-by: Harold Wanyama --- cla-backend-go/cla_manager/service.go | 27 +++++++++++++++++++++++---- cla-backend-go/events/service.go | 2 +- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 39bad8916..58ddbb212 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/LF-Engineering/lfx-kit/auth" + "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -193,6 +194,15 @@ func (s service) DeleteRequest(requestID string) error { // AddClaManager Adds LFID to Signature Access Control List list func (s service) AddClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) { + f := logrus.Fields{ + "functionName": "v1.cla_manager.AddClaManager", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "companyID": companyID, + "claGroupID": claGroupID, + "LFID": LFID, + "projectName": projectSFName, + } + userModel, userErr := s.usersService.GetUserByLFUserName(LFID) if userErr != nil || userModel == nil { return nil, userErr @@ -218,7 +228,7 @@ func (s service) AddClaManager(ctx context.Context, authUser *auth.User, company claManagers := sigModel.SignatureACL - log.Debugf("Got Company signatures - Company: %s , Project: %s , signatureID: %s ", + log.WithFields(f).Debugf("Got Company signatures - Company: %s , Project: %s , signatureID: %s ", companyID, claGroupID, sigModel.SignatureID) // Update the signature ACL @@ -230,7 +240,7 @@ func (s service) AddClaManager(ctx context.Context, authUser *auth.User, company // Update the company ACL record in EasyCLA companyACLError := s.companyService.AddUserToCompanyAccessList(ctx, companyID, LFID) if companyACLError != nil { - log.Warnf("AddCLAManager- Unable to add user to company ACL, companyID: %s, user: %s, error: %+v", companyID, LFID, companyACLError) + log.WithFields(f).Warnf("AddCLAManager- Unable to add user to company ACL, companyID: %s, user: %s, error: %+v", companyID, LFID, companyACLError) return nil, companyACLError } @@ -257,6 +267,7 @@ func (s service) AddClaManager(ctx context.Context, authUser *auth.User, company s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.ClaManagerCreated, UserName: authUser.UserName, + LfUsername: authUser.UserName, CLAGroupID: claGroupID, CLAGroupName: claGroupModel.ProjectName, ClaGroupModel: claGroupModel, @@ -302,6 +313,14 @@ func (s service) getCompanySignature(ctx context.Context, companyID string, claG // RemoveClaManager removes lfid from signature acl with given company and project func (s service) RemoveClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string) (*models.Signature, error) { + f := logrus.Fields{ + "functionName": "v1.cla_manager.RemoveClaManager", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "LFID": LFID, + "companyID": companyID, + } + userModel, userErr := s.usersService.GetUserByLFUserName(LFID) if userErr != nil || userModel == nil { return nil, userErr @@ -333,7 +352,7 @@ func (s service) RemoveClaManager(ctx context.Context, authUser *auth.User, comp // Update the signature ACL updatedSignature, aclErr := s.sigService.RemoveCLAManager(ctx, sigModel.SignatureID, LFID) if aclErr != nil || updatedSignature == nil { - log.Warnf("remove CLA Manager returned an error or empty signature model using Signature ID: %s, error: %+v", + log.WithFields(f).Warnf("remove CLA Manager returned an error or empty signature model using Signature ID: %s, error: %+v", sigModel.SignatureID, sigErr) return nil, aclErr } @@ -367,7 +386,7 @@ func (s service) RemoveClaManager(ctx context.Context, authUser *auth.User, comp // Send an event s.eventsService.LogEvent(&events.LogEventArgs{ EventType: events.ClaManagerDeleted, - LfUsername: userModel.LfUsername, + LfUsername: authUser.UserName, UserName: authUser.UserName, CLAGroupID: claGroupID, CLAGroupName: claGroupModel.ProjectName, diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index e2670565a..4cbb22885 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -263,7 +263,7 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { return nil } var parentProjectName, parentProjectID string - if utils.IsProjectCategory(project, parentProject) { + if !utils.IsProjectHasRootParent(project) { parentProjectName = parentProject.Name parentProjectID = parentProject.ID } else { From 3c9b4e2128c444e1d304de973efa1d90d3bb687b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 May 2021 07:48:42 -0700 Subject: [PATCH 0253/1276] Bump rsa from 4.1 to 4.7 in /cla-backend (#2907) Bumps [rsa](https://github.com/sybrenstuvel/python-rsa) from 4.1 to 4.7. - [Release notes](https://github.com/sybrenstuvel/python-rsa/releases) - [Changelog](https://github.com/sybrenstuvel/python-rsa/blob/main/CHANGELOG.md) - [Commits](https://github.com/sybrenstuvel/python-rsa/compare/version-4.1...version-4.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 0d872f646..b6037b1df 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -46,7 +46,7 @@ python-dateutil==2.8.0 python-jose==3.0.1 requests==2.22.0 requests-oauthlib==1.2.0 -rsa==4.1 +rsa==4.7 s3transfer==0.2.1 sentinels==1.0.0 six==1.13.0 From 0da8fd06c29c00f454a548518b59536f8ca8191a Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 5 May 2021 11:32:05 -0700 Subject: [PATCH 0254/1276] Added SignatureSigned and SignatureApproved to ICLA Signature Query Response (#2916) Signed-off-by: David Deal --- cla-backend-go/Makefile | 1 + cla-backend-go/signatures/repository.go | 10 +++++--- .../swagger/common/icla-signature.yaml | 25 ++++++++++++++++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index ea7d1476c..2c3577f48 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -151,6 +151,7 @@ swagger-build-acs-service: -t v2/acs-service \ -f swagger/acs-service.yaml +build-swagger: swagger-build swagger-build: clean-swagger swagger-prep swagger-build-v1-services swagger-build-v2-services swagger-build-project-service swagger-build-organization-service swagger-build-user-service swagger-build-acs-service swagger-validate: swagger-v1-validate swagger-v2-validate diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 6d6c8fb17..deb3e1ddb 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -3028,13 +3028,15 @@ func (repo repository) getIntermediateICLAResponse(f logrus.Fields, dbSignatures IclaSignature: &models.IclaSignature{ GithubUsername: sig.UserGithubUsername, LfUsername: sig.UserLFUsername, + SignatureApproved: sig.SignatureApproved, + SignatureSigned: sig.SignatureSigned, + SignatureModified: sig.DateModified, SignatureID: sig.SignatureID, - UserEmail: sig.UserEmail, - UserName: sig.UserName, SignedOn: sigSignedTime, - UserDocusignName: sig.UserDocusignName, UserDocusignDateSigned: sigSignedTime, - SignatureModified: sig.DateModified, + UserDocusignName: sig.UserDocusignName, + UserEmail: sig.UserEmail, + UserName: sig.UserName, }, SignatureReferenceID: sig.SignatureReferenceID, }) diff --git a/cla-backend-go/swagger/common/icla-signature.yaml b/cla-backend-go/swagger/common/icla-signature.yaml index 41e085b4f..00ff988cf 100644 --- a/cla-backend-go/swagger/common/icla-signature.yaml +++ b/cla-backend-go/swagger/common/icla-signature.yaml @@ -5,21 +5,44 @@ type: object title: ICLASignature properties: signature_id: - type: string + description: the signature ID + $ref: './common/properties/internal-id.yaml' github_username: type: string + description: the user's github username + example: 'tomcruise' lf_username: type: string + description: the user's LF username + example: 'tomcruise' user_name: type: string + description: the user's user name/real name + example: 'Tom Cruise' user_email: type: string + description: the user's email address + example: 'tomcruise@hollywood.com' signed_on: type: string + description: the date/time the ICLA was signed + example: '2020-03-16T17:57:58Z' userDocusignName: type: string + description: the name provided on the DocuSign document + example: 'Jack Daniels' userDocusignDateSigned: type: string + description: the signature date signed value from DocuSign + example: '2020-03-16T17:57:58Z' + signatureApproved: + type: boolean + description: the signature approved flag - true or false value + example: true + signatureSigned: + type: boolean + description: the signature signed flag - true or false value + example: true signatureModified: type: string description: the signature modified created time From 22ecfdef77f76d8c8071cc4deaaaf3561a2349af Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 6 May 2021 19:10:16 +0300 Subject: [PATCH 0255/1276] [Snyk] Upgrade rxjs from 6.5.5 to 6.6.7 (#2914) Snyk has created this PR to upgrade rxjs from 6.5.5 to 6.6.7. See this package in npm: See this project in Snyk: https://app.snyk.io/org/pbajpai/project/e0799cf1-c42b-4a32-8810-b716ecd0b2b9?utm_source=github&utm_medium=upgrade-pr --- cla-landing-page/package.json | 2 +- cla-landing-page/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 5d429eee7..6946820be 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -55,7 +55,7 @@ "aws-sdk": "^2.733.0", "bootstrap": "^4.4.0", "query-string": "^6.13.6", - "rxjs": "~6.5.4", + "rxjs": "~6.6.7", "serverless": "^2.15.0", "serverless-aws-alias": "^1.8.0", "serverless-cloudfront-invalidate": "^1.5.0", diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 8e79810cb..377c1658b 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -10138,10 +10138,10 @@ rxjs@^6.4.0, rxjs@^6.5.3, rxjs@^6.6.0, rxjs@^6.6.2: dependencies: tslib "^1.9.0" -rxjs@~6.5.4: - version "6.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" - integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== +rxjs@~6.6.7: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" From 9716a37d17a85bfa059944a9783cf54ddc9ec634 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 6 May 2021 09:23:19 -0700 Subject: [PATCH 0256/1276] Added UserID to ICLA Signature Response (#2917) Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 1 + cla-backend-go/swagger/common/icla-signature.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index deb3e1ddb..17cb750a3 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -3027,6 +3027,7 @@ func (repo repository) getIntermediateICLAResponse(f logrus.Fields, dbSignatures intermediateResponse = append(intermediateResponse, &iclaSignatureWithDetails{ IclaSignature: &models.IclaSignature{ GithubUsername: sig.UserGithubUsername, + UserID: sig.SignatureReferenceID, LfUsername: sig.UserLFUsername, SignatureApproved: sig.SignatureApproved, SignatureSigned: sig.SignatureSigned, diff --git a/cla-backend-go/swagger/common/icla-signature.yaml b/cla-backend-go/swagger/common/icla-signature.yaml index 00ff988cf..4b4c627d9 100644 --- a/cla-backend-go/swagger/common/icla-signature.yaml +++ b/cla-backend-go/swagger/common/icla-signature.yaml @@ -7,6 +7,9 @@ properties: signature_id: description: the signature ID $ref: './common/properties/internal-id.yaml' + user_id: + description: the user ID + $ref: './common/properties/internal-id.yaml' github_username: type: string description: the user's github username From 6860ed3b071be2325e7ed31dbcbe61d2abb8d0e6 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 6 May 2021 15:51:45 -0700 Subject: [PATCH 0257/1276] Updated Signature Queries (#2919) - Signature queries will now return Active, Incomplete, and Disabled signatures in most views - Updated swagger to allow clients to filter based on approved and signed signatures - Added USER_AUTH_TRACING to allow printout of user scopes for authenticated users (when needed) Signed-off-by: David Deal --- cla-backend-go/serverless.yml | 2 + cla-backend-go/signatures/handlers.go | 24 ++-- cla-backend-go/signatures/repository.go | 141 +++++++++++++-------- cla-backend-go/signatures/service.go | 30 +++-- cla-backend-go/swagger/cla.v1.yaml | 16 +++ cla-backend-go/swagger/cla.v2.yaml | 24 +++- cla-backend-go/utils/auth_user.go | 7 + cla-backend-go/v2/company/handlers.go | 2 +- cla-backend-go/v2/signatures/converters.go | 10 +- cla-backend-go/v2/signatures/handlers.go | 8 +- cla-backend-go/v2/signatures/service.go | 17 ++- 11 files changed, 186 insertions(+), 95 deletions(-) diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index aac6235f3..a92ccab80 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -249,6 +249,8 @@ provider: # https://github.com/pypa/setuptools/issues/2350 and # https://github.com/pypa/setuptools/issues/2232 SETUPTOOLS_USE_DISTUTILS: stdlib + # Turn on USER_AUTH_TRACING to see additional debug of user scopes for the authenticated users - output is verbose + USER_AUTH_TRACING: true stackTags: Name: ${self:service} diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index 6cfc1b0d2..c1589bce2 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -38,7 +38,8 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d } log.WithFields(f).Debug("querying for individual signature...") - signatureModel, sigErr := service.GetIndividualSignature(ctx, params.ClaGroupID, params.UserID) + approved, signed := true, true + signatureModel, sigErr := service.GetIndividualSignature(ctx, params.ClaGroupID, params.UserID, &approved, &signed) if sigErr != nil { msg := fmt.Sprintf("error retrieving signature using ClaGroupID: %s, userID: %s, error: %+v", params.ClaGroupID, params.UserID, sigErr) @@ -85,22 +86,29 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d api.SignaturesGetSignedCCLADocumentHandler = signatures.GetSignedCCLADocumentHandlerFunc(func(params signatures.GetSignedCCLADocumentParams) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - signatureModel, sigErr := service.GetCorporateSignature(ctx, params.ClaGroupID, params.CompanyID) + f := logrus.Fields{ + "functionName": "v1.signatures.handler.SignaturesGetSignedCCLADocumentHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": params.ClaGroupID, + "companyID": params.CompanyID, + } + + approved, signed := true, true + signatureModel, sigErr := service.GetCorporateSignature(ctx, params.ClaGroupID, params.CompanyID, &approved, &signed) if sigErr != nil { msg := fmt.Sprintf("EasyCLA - 500 Internal Server Error - error retrieving signature using ClaGroupID: %s, CompanyID: %s, error: %+v", params.ClaGroupID, params.CompanyID, sigErr) - log.Warn(msg) + log.WithFields(f).WithError(sigErr).Warn(msg) return signatures.NewGetSignedCCLADocumentInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "500", Message: msg, }) - } if signatureModel == nil { msg := fmt.Sprintf("EasyCLA - 404 Not Found - - error retrieving signature using ClaGroupID: %s, CompanyID: %s", params.ClaGroupID, params.CompanyID) - log.Warn(msg) + log.WithFields(f).Warn(msg) return signatures.NewGetSignedCCLADocumentNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "404", Message: msg, @@ -114,7 +122,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d if s3Err != nil { msg := fmt.Sprintf("EasyCLA - 500 Internal Server Error - unable to locate PDF from source using ClaGroupID: %s, CompanyID: %s, s3 error: %+v", params.ClaGroupID, params.CompanyID, s3Err) - log.Warn(msg) + log.WithFields(f).WithError(s3Err).Warn(msg) return signatures.NewGetSignedCCLADocumentInternalServerError().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "500", Message: msg, @@ -130,9 +138,9 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d if writeErr != nil { msg := fmt.Sprintf("EasyCLA - 500 Internal Server Error - generating s3 redirect for the client client using source using ClaGroupID: %s, CompanyID: %s, error: %+v", params.ClaGroupID, params.CompanyID, s3Err) - log.Warn(msg) + log.WithFields(f).WithError(writeErr).Warn(msg) } - log.Debugf("SignaturesGetSignedICLADocumentHandler - wrote %d bytes", bytesWritten) + log.WithFields(f).Debugf("wrote %d bytes", bytesWritten) }) }) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 17cb750a3..6b32a7932 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -64,13 +64,13 @@ type SignatureRepository interface { InvalidateProjectRecord(ctx context.Context, signatureID, note string) error GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) - GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) - GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) + GetIndividualSignature(ctx context.Context, claGroupID, userID string, approved, signed *bool) (*models.Signature, error) + GetCorporateSignature(ctx context.Context, claGroupID, companyID string, approved, signed *bool) (*models.Signature, error) GetSignatureACL(ctx context.Context, signatureID string) ([]string, error) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) - GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) - GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) + GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) + GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, pageSize int64) (*models.Signatures, error) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) @@ -87,7 +87,7 @@ type SignatureRepository interface { AddUsersDetails(ctx context.Context, signatureID string, userID string) error AddSignedOn(ctx context.Context, signatureID string) error - GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) + GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) } @@ -437,7 +437,7 @@ func (repo repository) GetSignature(ctx context.Context, signatureID string) (*m } // GetIndividualSignature returns the signature record for the specified CLA Group and User -func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) { +func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, userID string, approved, signed *bool) (*models.Signature, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetIndividualSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -457,10 +457,20 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u And(expression.Key("signature_reference_id").Equal(expression.Value(userID))) filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + // If the caller provided a signature signed value...add the appropriate filter + if signed != nil { + log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) + filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(*signed)))) + } + + // If the caller provided a signature approved value...add the appropriate filter + if approved != nil { + log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) + filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(*approved)))) + } + builder := expression.NewBuilder(). WithKeyCondition(condition). WithFilter(filter). @@ -532,7 +542,7 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u } // GetCorporateSignature returns the signature record for the specified CLA Group and Company ID -func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) { +func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, companyID string, approved, signed *bool) (*models.Signature, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetCorporateSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -541,8 +551,8 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co "companyID": companyID, "signatureType": "ccla", "signatureReferenceType": "company", - "signatureApproved": "true", - "signatureSigned": "true", + "signatureApproved": utils.BoolValue(approved), + "signatureSigned": utils.BoolValue(signed), } // These are the keys we want to match for an CCLA Signature with a given CLA Group and Company ID @@ -550,10 +560,20 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co And(expression.Key("signature_reference_id").Equal(expression.Value(companyID))) filter := expression.Name("signature_type").Equal(expression.Value("ccla")). And(expression.Name("signature_reference_type").Equal(expression.Value("company"))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + // If the caller provided a signature signed value...add the appropriate filter + if signed != nil { + log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) + filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(*signed)))) + } + + // If the caller provided a signature approved value...add the appropriate filter + if approved != nil { + log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) + filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(*approved)))) + } + builder := expression.NewBuilder(). WithKeyCondition(condition). WithFilter(filter). @@ -685,7 +705,7 @@ func addConditionToFilter(filter expression.ConditionBuilder, cond expression.Co } // GetProjectSignatures returns a list of signatures for the specified project -func (repo repository) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) { +func (repo repository) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) { // nolint f := logrus.Fields{ "functionName": "v1.signatures.repository.GetProjectSignatures", "tableName": repo.signatureTableName, @@ -698,6 +718,8 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur "pageSize": aws.Int64Value(params.PageSize), "nextKey": aws.StringValue(params.NextKey), "sortOrder": aws.StringValue(params.SortOrder), + "approved": utils.BoolValue(params.Approved), + "signed": utils.BoolValue(params.Signed), } indexName := SignatureProjectIDIndex @@ -722,21 +744,15 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeExists()) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) } } else { @@ -770,13 +786,18 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } } + } - // Filter condition to cater for approved and signed signatures - signatureApprovedExpression := expression.Name("signature_approved").Equal(expression.Value(true)) - filter = addConditionToFilter(filter, signatureApprovedExpression, &filterAdded) + // If the caller provided a signature approved value...add the appropriate filter + if params.Approved != nil { + log.WithFields(f).Debugf("adding signature_approved: %t filter", *params.Approved) + filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved)))) + } - signatureSignedExpression := expression.Name("signature_signed").Equal(expression.Value(true)) - filter = addConditionToFilter(filter, signatureSignedExpression, &filterAdded) + // If the caller provided a signature signed value...add the appropriate filter + if params.Signed != nil { + log.WithFields(f).Debugf("adding signature_signed: %t filter", *params.Signed) + filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed)))) } if filterAdded { @@ -902,6 +923,8 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si "pageSize": aws.Int64Value(params.PageSize), "nextKey": aws.StringValue(params.NextKey), "sortOrder": aws.StringValue(params.SortOrder), + "approved": utils.BoolValue(params.Approved), + "signed": utils.BoolValue(params.Signed), "companyIDList": params.Body, } @@ -927,21 +950,15 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeExists()) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) } } else { @@ -975,13 +992,18 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } } + } - // Filter condition to cater for approved and signed signatures - signatureApprovedExpression := expression.Name("signature_approved").Equal(expression.Value(true)) - filter = addConditionToFilter(filter, signatureApprovedExpression, &filterAdded) + // If the caller provided a signature approved value...add the appropriate filter + if params.Approved != nil { + log.WithFields(f).Debugf("adding signature_approved: %t filter", *params.Approved) + filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved)))) + } - signatureSignedExpression := expression.Name("signature_signed").Equal(expression.Value(true)) - filter = addConditionToFilter(filter, signatureSignedExpression, &filterAdded) + // If the caller provided a signature signed value...add the appropriate filter + if params.Signed != nil { + log.WithFields(f).Debugf("adding signature_signed: %t filter", *params.Signed) + filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed)))) } if len(params.Body) > 0 { @@ -1107,7 +1129,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si } // GetProjectCompanySignature returns a the signature for the specified project and specified company with the other query flags -func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { +func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetProjectCompanySignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1141,17 +1163,17 @@ func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID } // GetProjectCompanySignatures returns a list of signatures for the specified project and specified company -func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) { +func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetProjectCompanySignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyID": companyID, "projectID": projectID, - "signed": aws.BoolValue(signed), - "approved": aws.BoolValue(approved), "nextKey": aws.StringValue(nextKey), "sortOrder": aws.StringValue(sortOrder), "pageSize": aws.Int64Value(pageSize), + "approved": utils.BoolValue(approved), + "signed": utils.BoolValue(signed), } // These are the keys we want to match @@ -1161,16 +1183,16 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI filter := expression.Name("signature_type").Equal(expression.Value("ccla")). And(expression.Name("signature_reference_type").Equal(expression.Value("company"))) - // If the caller provided a signature signed value...add the appropriate filter - if signed != nil { - log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) - filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(*signed)))) - } - // If the caller provided a signature approved value...add the appropriate filter if approved != nil { log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) - filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(*approved)))) + filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved)))) + } + + // If the caller provided a signature signed value...add the appropriate filter + if signed != nil { + log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) + filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed)))) } limit := int64(10) @@ -1989,11 +2011,11 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } log.WithFields(f).Debug("querying database for approval list details") - signed, approved := true, true + approved, signed := true, true pageSize := int64(10) // Get CCLA signature - For Approval List info - cclaSignature, err := repo.GetCorporateSignature(ctx, projectID, companyID) + cclaSignature, err := repo.GetCorporateSignature(ctx, projectID, companyID, &approved, &signed) if err != nil || cclaSignature == nil { msg := fmt.Sprintf("unable to get corporate signature for CLA Group: %s and company: %s", projectID, companyID) log.WithFields(f).Warn(msg) @@ -2132,7 +2154,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model go func() { defer close(results) for _, user := range userSearch.Users { - icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, user.UserID) + icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, user.UserID, &approved, &signed) if iclaErr != nil || icla == nil { results <- &ICLAUserResponse{ Error: fmt.Errorf("unable to get icla for user: %s ", user.UserID), @@ -2208,7 +2230,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if params.RemoveDomainApprovalList != nil { // Get ICLAs log.WithFields(f).Debug("getting icla records... ") - iclas, iclaErr := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil, 0, "") + iclas, iclaErr := repo.GetClaGroupICLASignatures(ctx, approvalList.ClaGroupID, nil, &approved, &signed, 0, "") if iclaErr != nil { log.WithFields(f).Warn("unable to get iclas") } @@ -2301,7 +2323,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model return } if claUser != nil { - icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID) + icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID, &approved, &signed) if iclaErr != nil || icla == nil { log.WithFields(f).Debugf("unable to get icla signature for user with ghUsername: %s ", ghUsername) } @@ -2428,7 +2450,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } // Query the CCLA signature once again to load the most recent updates which include approval list updates from above - updatedSig, err := repo.GetCorporateSignature(ctx, projectID, companyID) + updatedSig, err := repo.GetCorporateSignature(ctx, projectID, companyID, &approved, &signed) if err != nil || cclaSignature == nil { msg := fmt.Sprintf("unable to get corporate signature for CLA Group: %s and company: %s", projectID, companyID) log.WithFields(f).Warn(msg) @@ -2862,21 +2884,28 @@ func (repo repository) AddSignedOn(ctx context.Context, signatureID string) erro return nil } -func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { +func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetClaGroupICLASignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "searchTerm": utils.StringValue(searchTerm), + "approved": utils.BoolValue(approved), + "signed": utils.BoolValue(signed), } condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)) filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + if approved != nil { + filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved)))) + } + if signed != nil { + filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed)))) + } + if searchTerm != nil { log.WithFields(f).Debugf("adding search term filter for : %s ", *searchTerm) filterAdded := true diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 3df3b867b..bfed7e6b6 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -33,11 +33,11 @@ import ( // SignatureService interface type SignatureService interface { GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) - GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) - GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) + GetIndividualSignature(ctx context.Context, claGroupID, userID string, approved, signed *bool) (*models.Signature, error) + GetCorporateSignature(ctx context.Context, claGroupID, companyID string, approved, signed *bool) (*models.Signature, error) GetProjectSignatures(ctx context.Context, params signatures.GetProjectSignaturesParams) (*models.Signatures, error) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) - GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) + GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, params signatures.GetProjectCompanySignaturesParams) (*models.Signatures, error) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams) (*models.Signatures, error) @@ -53,8 +53,8 @@ type SignatureService interface { AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) RemoveCLAManager(ctx context.Context, ignatureID, claManagerID string) (*models.Signature, error) - GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) - GetClaGroupCCLASignatures(ctx context.Context, claGroupID string) (*models.Signatures, error) + GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) + GetClaGroupCCLASignatures(ctx context.Context, claGroupID string, approved, signed *bool) (*models.Signatures, error) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, searchTerm *string) (*models.CorporateContributorList, error) } @@ -83,13 +83,13 @@ func (s service) GetSignature(ctx context.Context, signatureID string) (*models. } // GetIndividualSignature returns the signature associated with the specified CLA Group and User ID -func (s service) GetIndividualSignature(ctx context.Context, claGroupID, userID string) (*models.Signature, error) { - return s.repo.GetIndividualSignature(ctx, claGroupID, userID) +func (s service) GetIndividualSignature(ctx context.Context, claGroupID, userID string, approved, signed *bool) (*models.Signature, error) { + return s.repo.GetIndividualSignature(ctx, claGroupID, userID, approved, signed) } // GetCorporateSignature returns the signature associated with the specified CLA Group and Company ID -func (s service) GetCorporateSignature(ctx context.Context, claGroupID, companyID string) (*models.Signature, error) { - return s.repo.GetCorporateSignature(ctx, claGroupID, companyID) +func (s service) GetCorporateSignature(ctx context.Context, claGroupID, companyID string, approved, signed *bool) (*models.Signature, error) { + return s.repo.GetCorporateSignature(ctx, claGroupID, companyID, approved, signed) } // GetProjectSignatures returns the list of signatures associated with the specified project @@ -115,8 +115,8 @@ func (s service) CreateProjectSummaryReport(ctx context.Context, params signatur } // GetProjectCompanySignature returns the signature associated with the specified project and company -func (s service) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, signed, approved *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { - return s.repo.GetProjectCompanySignature(ctx, companyID, projectID, signed, approved, nextKey, pageSize) +func (s service) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { + return s.repo.GetProjectCompanySignature(ctx, companyID, projectID, approved, signed, nextKey, pageSize) } // GetProjectCompanySignatures returns the list of signatures associated with the specified project @@ -790,16 +790,18 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models } } -func (s service) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { - return s.repo.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm, pageSize, nextKey) +func (s service) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) { + return s.repo.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm, approved, signed, pageSize, nextKey) } -func (s service) GetClaGroupCCLASignatures(ctx context.Context, claGroupID string) (*models.Signatures, error) { +func (s service) GetClaGroupCCLASignatures(ctx context.Context, claGroupID string, approved, signed *bool) (*models.Signatures, error) { pageSize := utils.Int64(1000) return s.repo.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ ClaType: aws.String(utils.ClaTypeCCLA), ProjectID: claGroupID, PageSize: pageSize, + Approved: approved, + Signed: signed, }) } diff --git a/cla-backend-go/swagger/cla.v1.yaml b/cla-backend-go/swagger/cla.v1.yaml index b291bb278..e9c8836e1 100644 --- a/cla-backend-go/swagger/cla.v1.yaml +++ b/cla-backend-go/swagger/cla.v1.yaml @@ -354,6 +354,8 @@ paths: - $ref: '#/parameters/signatureType' - $ref: '#/parameters/claType' - $ref: '#/parameters/sortOrder' + - $ref: '#/parameters/approved' + - $ref: '#/parameters/signed' responses: '200': description: 'Success' @@ -392,6 +394,8 @@ paths: - $ref: '#/parameters/signatureType' - $ref: '#/parameters/claType' - $ref: '#/parameters/sortOrder' + - $ref: '#/parameters/approved' + - $ref: '#/parameters/signed' - name: body in: body schema: @@ -2513,6 +2517,18 @@ parameters: required: false # UUID v4 regex # pattern: '[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}' + approved: + name: approved + description: The signature approved query parameter. If set with a value of true, the query would return approved signatures. If set with a value of false, the query would return invalidated/disabled signatures. + in: query + type: boolean + required: false + signed: + name: signed + description: The signature signed query parameter. If set with a value of true, the query would return signed signatures. If set with a value of false, the query would return incomplete/unsigned signatures. + in: query + type: boolean + required: false sortOrder: name: sortOrder description: The sort order - either asc or desc diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 30b40dc8f..002a18015 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1619,6 +1619,8 @@ paths: - $ref: '#/parameters/sortOrder' - $ref: '#/parameters/pageSize' - $ref: '#/parameters/nextKey' + - $ref: '#/parameters/approved' + - $ref: '#/parameters/signed' responses: '200': description: 'Success' @@ -1641,7 +1643,7 @@ paths: /cla-group/{claGroupID}/corporate-contributors: get: - summary: List corporate contributors for cla group + summary: List corporate contributors description: Returns a list of corporate contributor for the CLA Group operationId: listClaGroupCorporateContributors parameters: @@ -1763,6 +1765,8 @@ paths: - $ref: '#/parameters/signatureType' - $ref: '#/parameters/claType' - $ref: '#/parameters/sortOrder' + - $ref: '#/parameters/signed' + - $ref: '#/parameters/approved' responses: '200': description: 'Success' @@ -1894,8 +1898,8 @@ paths: # -------------------------------------------------------- /signatures/project/{claGroupID}/ccla/pdfs: get: - summary: Downloads all corporate CLAs for this project - description: Downloads the corporate CLAs for this project + summary: Download corporate CLAs + description: Downloads all the corporate CLAs for this project operationId: downloadProjectSignatureCCLAs parameters: - $ref: "#/parameters/x-request-id" @@ -3105,7 +3109,7 @@ paths: - gerrits /cla-group/{claGroupID}/user/{userID}/icla: put: - summary: Invalidate ICLA record + summary: Invalidate ICLA record description: Invalidates a given ICLA record for a user operationId: invalidateICLA parameters: @@ -3770,6 +3774,18 @@ parameters: required: false # UUID v4 regex # pattern: '[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}' + approved: + name: approved + description: The signature approved query parameter. If set with a value of true, the query would return approved signatures. If set with a value of false, the query would return invalidated/disabled signatures. + in: query + type: boolean + required: false + signed: + name: signed + description: The signature signed query parameter. If set with a value of true, the query would return signed signatures. If set with a value of false, the query would return incomplete/unsigned signatures. + in: query + type: boolean + required: false sortOrder: name: sortOrder description: The sort order - either asc or desc diff --git a/cla-backend-go/utils/auth_user.go b/cla-backend-go/utils/auth_user.go index 471ffe32b..33af5fcaf 100644 --- a/cla-backend-go/utils/auth_user.go +++ b/cla-backend-go/utils/auth_user.go @@ -4,6 +4,9 @@ package utils import ( + "os" + "strconv" + "github.com/LF-Engineering/lfx-kit/auth" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/sirupsen/logrus" @@ -24,5 +27,9 @@ func SetAuthUserProperties(authUser *auth.User, xUserName *string, xEmail *strin authUser.Email = *xEmail } + tracingEnabled, conversionErr := strconv.ParseBool(os.Getenv("USER_AUTH_TRACING")) + if conversionErr == nil && tracingEnabled { + log.WithFields(f).Debugf("Auth User: %+v", authUser) + } log.WithFields(f).Debugf("set authuser x-username:%s and x-email:%s", authUser.UserName, authUser.Email) } diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 73c0790c5..9a6b1bf8f 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -107,7 +107,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debug("checking permissions") - if !utils.IsUserAuthorizedForOrganization(ctx, authUser, v2CompanyModel.CompanyExternalID, utils.ALLOW_ADMIN_SCOPE) { + if !utils.IsUserAuthorizedForOrganization(ctx, authUser, params.CompanySFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to CompanyGetCompanyByExternalIDHandler with Organization scope of %s", authUser.UserName, v2CompanyModel.CompanyExternalID) log.WithFields(f).Warn(msg) diff --git a/cla-backend-go/v2/signatures/converters.go b/cla-backend-go/v2/signatures/converters.go index fe87fc318..904735a2e 100644 --- a/cla-backend-go/v2/signatures/converters.go +++ b/cla-backend-go/v2/signatures/converters.go @@ -59,11 +59,11 @@ func iclaSigCsvLine(sig *v1Models.IclaSignature) string { } else { dateTime = t.Format("Jan 2,2006") } - return fmt.Sprintf("\n%s,%s,%s,%s,\"%s\"", sig.GithubUsername, sig.LfUsername, sig.UserName, sig.UserEmail, dateTime) + return fmt.Sprintf("\n%s,%s,%s,%s,\"%s\",%t,%t", sig.GithubUsername, sig.LfUsername, sig.UserName, sig.UserEmail, dateTime, sig.SignatureApproved, sig.SignatureSigned) } func cclaSigCsvHeader() string { - return `Company Name,Signed,Approved,DomainApprovalList,EmailApprovalList,GitHubOrgApprovalList,GitHubUsernameApprovalList,Date Signed` + return `Company Name,Signed,Approved,DomainApprovalList,EmailApprovalList,GitHubOrgApprovalList,GitHubUsernameApprovalList,Date Signed,Approved,Signed` } func cclaSigCsvLine(sig *v1Models.Signature) string { @@ -75,7 +75,7 @@ func cclaSigCsvLine(sig *v1Models.Signature) string { } else { dateTime = t.Format("Jan 2,2006") } - return fmt.Sprintf("\n\"%s\",%t,%t,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"", + return fmt.Sprintf("\n\"%s\",%t,%t,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",%t,%t", sig.CompanyName, sig.SignatureSigned, sig.SignatureApproved, @@ -83,5 +83,7 @@ func cclaSigCsvLine(sig *v1Models.Signature) string { strings.Join(sig.EmailApprovalList, ","), strings.Join(sig.GithubOrgApprovalList, ","), strings.Join(sig.GithubUsernameApprovalList, ","), - dateTime) + dateTime, + sig.SignatureApproved, + sig.SignatureApproved) } diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index e9acbac6a..a09d1a252 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -369,6 +369,8 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "signatureType": params.SignatureType, + "approved": utils.BoolValue(params.Approved), + "signed": utils.BoolValue(params.Signed), } log.WithFields(f).Debug("looking up CLA Group by ID...") @@ -420,6 +422,8 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj SignatureType: params.SignatureType, ClaType: params.ClaType, SortOrder: params.SortOrder, + Approved: params.Approved, + Signed: params.Signed, }) if err != nil { msg := fmt.Sprintf("error retrieving project signatures for projectID: %s, error: %+v", @@ -812,6 +816,8 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj "claGroupID": params.ClaGroupID, "searchTerm": utils.StringValue(params.SearchTerm), "sortOrder": utils.StringValue(params.SortOrder), + "approved": utils.BoolValue(params.Approved), + "signed": utils.BoolValue(params.Signed), } log.WithFields(f).Debug("looking up CLA Group by ID...") @@ -859,7 +865,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj nextKey = *params.NextKey } - results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm, pageSize, nextKey) + results, err := v2service.GetProjectIclaSignatures(ctx, params.ClaGroupID, params.SearchTerm, params.Approved, params.Signed, pageSize, nextKey) if err != nil { msg := fmt.Sprintf("problem loading ICLA signatures by CLA Group ID search term: %s", aws.StringValue(params.SearchTerm)) log.WithFields(f).WithError(err).Warn(msg) diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 252e9abd6..2cd4e629a 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -61,7 +61,7 @@ type Service interface { GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) - GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) + GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companyID string) ([]byte, error) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companySFID string, searchTerm *string) (*models.CorporateContributorList, error) GetSignedDocument(ctx context.Context, signatureID string) (*models.SignedDocument, error) @@ -140,11 +140,11 @@ func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGro func (s service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { var b bytes.Buffer - result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, nil, 0, "") + result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, nil, nil, nil, 0, "") if err != nil { return nil, err } - b.WriteString(`GitHub ID,LF_ID,Name,Email,Date Signed`) + b.WriteString(`GitHub ID,LF_ID,Name,Email,Date Signed,Approved,Signed`) for _, sig := range result.List { b.WriteString(iclaSigCsvLine(sig)) } @@ -158,7 +158,7 @@ func (s service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID str "claGroupID": claGroupID, } log.WithFields(f).Debug("querying for CCLA signatures...") - result, err := s.v1SignatureService.GetClaGroupCCLASignatures(ctx, claGroupID) + result, err := s.v1SignatureService.GetClaGroupCCLASignatures(ctx, claGroupID, nil, nil) if err != nil { log.WithFields(f).Warnf("error loading CCLA signatures for CLA group, error: %+v", err) return nil, err @@ -175,16 +175,18 @@ func (s service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID str return b.Bytes(), nil } -func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, pageSize int64, nextKey string) (*models.IclaSignatures, error) { +func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) { f := logrus.Fields{ "functionName": "v2.signatures.service.GetProjectIclaSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "searchTerm": utils.StringValue(searchTerm), + "approved": utils.BoolValue(approved), + "signed": utils.BoolValue(signed), } var out models.IclaSignatures - result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm, pageSize, nextKey) + result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, searchTerm, approved, signed, pageSize, nextKey) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load ICLA signatures using the specified search parameters") return nil, err @@ -309,7 +311,8 @@ func (s service) InvalidateICLA(ctx context.Context, claGroupID string, userID s } // Get signature record log.WithFields(f).Debug("getting signature record ...") - icla, iclaErr := s.v1SignatureService.GetIndividualSignature(ctx, claGroupID, userID) + approved, signed := true, true + icla, iclaErr := s.v1SignatureService.GetIndividualSignature(ctx, claGroupID, userID, &approved, &signed) if iclaErr != nil { log.WithFields(f).Debug("unable to get individual signature") return iclaErr From 46e127aa008a904333676ba1e5b9846162ee2bde Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 7 May 2021 13:10:57 -0700 Subject: [PATCH 0258/1276] Resolved Signature Query Issue (#2920) Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 98 +++++++++++-------- .../swagger/common/icla-signature.yaml | 2 + .../swagger/common/signature-summary.yaml | 2 + cla-backend-go/swagger/common/signature.yaml | 2 + 4 files changed, 62 insertions(+), 42 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 6b32a7932..4305033ea 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -452,6 +452,7 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u log.WithFields(f).Debug("querying signature for icla records ...") + var filterAdded bool // These are the keys we want to match for an ICLA Signature with a given CLA Group and User ID condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(userID))) @@ -459,16 +460,17 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) - // If the caller provided a signature signed value...add the appropriate filter - if signed != nil { - log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) - filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(*signed)))) - } - - // If the caller provided a signature approved value...add the appropriate filter if approved != nil { - log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) - filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(*approved)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + } + if signed != nil { + filterAdded = true + log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } builder := expression.NewBuilder(). @@ -555,6 +557,7 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co "signatureSigned": utils.BoolValue(signed), } + var filterAdded bool // These are the keys we want to match for an CCLA Signature with a given CLA Group and Company ID condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(companyID))) @@ -562,16 +565,17 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co And(expression.Name("signature_reference_type").Equal(expression.Value("company"))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) - // If the caller provided a signature signed value...add the appropriate filter - if signed != nil { - log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) - filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(*signed)))) - } - - // If the caller provided a signature approved value...add the appropriate filter if approved != nil { - log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) - filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(*approved)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + } + if signed != nil { + filterAdded = true + log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } builder := expression.NewBuilder(). @@ -788,16 +792,17 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } } - // If the caller provided a signature approved value...add the appropriate filter if params.Approved != nil { - log.WithFields(f).Debugf("adding signature_approved: %t filter", *params.Approved) - filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(params.Approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } - - // If the caller provided a signature signed value...add the appropriate filter if params.Signed != nil { - log.WithFields(f).Debugf("adding signature_signed: %t filter", *params.Signed) - filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(params.Signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if filterAdded { @@ -994,16 +999,17 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si } } - // If the caller provided a signature approved value...add the appropriate filter if params.Approved != nil { - log.WithFields(f).Debugf("adding signature_approved: %t filter", *params.Approved) - filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(params.Approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } - - // If the caller provided a signature signed value...add the appropriate filter if params.Signed != nil { - log.WithFields(f).Debugf("adding signature_signed: %t filter", *params.Signed) - filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(params.Signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if len(params.Body) > 0 { @@ -1176,6 +1182,7 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI "signed": utils.BoolValue(signed), } + var filterAdded bool // These are the keys we want to match //condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)) condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)). @@ -1183,16 +1190,17 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI filter := expression.Name("signature_type").Equal(expression.Value("ccla")). And(expression.Name("signature_reference_type").Equal(expression.Value("company"))) - // If the caller provided a signature approved value...add the appropriate filter if approved != nil { - log.WithFields(f).Debugf("adding signature_approved: %t filter", *approved) - filter = filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } - - // If the caller provided a signature signed value...add the appropriate filter if signed != nil { - log.WithFields(f).Debugf("adding signature_signed: %t filter", *signed) - filter = filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } limit := int64(10) @@ -2894,21 +2902,27 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID "signed": utils.BoolValue(signed), } + var filterAdded bool condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)) filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) if approved != nil { - filter.And(expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if signed != nil { - filter.And(expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed)))) + filterAdded = true + log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) + filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if searchTerm != nil { log.WithFields(f).Debugf("adding search term filter for : %s ", *searchTerm) - filterAdded := true searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*searchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*searchTerm))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } diff --git a/cla-backend-go/swagger/common/icla-signature.yaml b/cla-backend-go/swagger/common/icla-signature.yaml index 4b4c627d9..58bde5232 100644 --- a/cla-backend-go/swagger/common/icla-signature.yaml +++ b/cla-backend-go/swagger/common/icla-signature.yaml @@ -42,10 +42,12 @@ properties: type: boolean description: the signature approved flag - true or false value example: true + x-omitempty: false signatureSigned: type: boolean description: the signature signed flag - true or false value example: true + x-omitempty: false signatureModified: type: string description: the signature modified created time diff --git a/cla-backend-go/swagger/common/signature-summary.yaml b/cla-backend-go/swagger/common/signature-summary.yaml index 9530bcfcd..69290d7bb 100644 --- a/cla-backend-go/swagger/common/signature-summary.yaml +++ b/cla-backend-go/swagger/common/signature-summary.yaml @@ -23,10 +23,12 @@ properties: type: boolean description: the signature signed flag - true or false value example: true + x-omitempty: false signatureApproved: type: boolean description: the signature approved flag - true or false value example: true + x-omitempty: false signatureReferenceType: type: string description: the signature reference type - either user or company diff --git a/cla-backend-go/swagger/common/signature.yaml b/cla-backend-go/swagger/common/signature.yaml index 5413f8a8b..0e61a9012 100644 --- a/cla-backend-go/swagger/common/signature.yaml +++ b/cla-backend-go/swagger/common/signature.yaml @@ -32,10 +32,12 @@ properties: type: boolean description: the signature signed flag - true or false value example: true + x-omitempty: false signatureApproved: type: boolean description: the signature approved flag - true or false value example: true + x-omitempty: false signatureReferenceType: type: string description: the signature reference type - either user or company From 258c757925be5e9f68c80c5d8a1efba88a9477a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 15:31:38 -0700 Subject: [PATCH 0259/1276] Bump url-parse from 1.4.7 to 1.5.0 in /cla-frontend-contributor-console/src (#2928) Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.0. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/package.json | 2 +- cla-frontend-contributor-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-contributor-console/src/package.json b/cla-frontend-contributor-console/src/package.json index 505a2f79a..daf558756 100644 --- a/cla-frontend-contributor-console/src/package.json +++ b/cla-frontend-contributor-console/src/package.json @@ -51,7 +51,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.4.7", + "url-parse": "^1.5.0", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 377a3f2dc..4643accbf 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -4119,10 +4119,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.4.7: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== +url-parse@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" + integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From ab0bf93ae5984a336986878a80f274ab209ded36 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 12 May 2021 15:43:43 -0700 Subject: [PATCH 0260/1276] Added cla-signature-query-default Feature Flag (#2944) - Added cla-signature-query-default parameter - A flag to indicate how a default signature query should return data - show only 'active' signatures or 'all' signatures when no other query signed/approved params are provided Signed-off-by: David Deal --- cla-backend-go/config/config.go | 5 +++ cla-backend-go/config/ssm.go | 8 +++++ cla-backend-go/signatures/repository.go | 44 +++++++++++++++++++++++++ cla-backend-go/utils/constants.go | 10 ++++++ 4 files changed, 67 insertions(+) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 704982d4e..2d3629674 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -29,6 +29,11 @@ type Config struct { // EnableCLAServiceForParent is a configuration flag to indicate if we should set the enable_services=[CLA] attribute on the parent project object in the project service when a child project is associated with a CLA group. This determines the v2 project console experience/behavior." EnableCLAServiceForParent bool `json:"enable_cla_service_for_parent"` + // SignatureQueryDefault is a flag to indicate how a default signature query should return data - show only 'active' signatures or 'all' signatures when no other query signed/approved params are provided + SignatureQueryDefault string `json:"signature_query_default"` + // SignatureQueryDefaultValue the default value for the SignatureQueryDefault configuration value + SignatureQueryDefaultValue string `json:"signature_query_default_value"` + // SFDC // GitHub diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 19e5c358a..06d83a970 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -45,6 +45,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint "stage": stage, } config := Config{} + config.SignatureQueryDefaultValue = "all" ssmClient := ssm.New(awsSession) @@ -94,6 +95,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-lfx-metrics-report-sqs-url-%s", stage), fmt.Sprintf("cla-lfx-metrics-report-enabled-%s", stage), fmt.Sprintf("cla-enable-services-for-parent-%s", stage), + fmt.Sprintf("cla-signature-query-default-%s", stage), fmt.Sprintf("cla-platform-api-gw-%s", stage), } @@ -221,6 +223,12 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint } else { config.EnableCLAServiceForParent = boolVal } + case fmt.Sprintf("cla-signature-query-default-%s", stage): + if resp.value == "" { + config.SignatureQueryDefault = config.SignatureQueryDefaultValue + } else { + config.SignatureQueryDefault = resp.value + } } } diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 4305033ea..dbfea2614 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -12,6 +12,8 @@ import ( "sync" "time" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/LF-Engineering/lfx-kit/auth" "github.com/sirupsen/logrus" @@ -473,6 +475,13 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } + // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters + if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { + filterAdded = true + filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + } + builder := expression.NewBuilder(). WithKeyCondition(condition). WithFilter(filter). @@ -578,6 +587,13 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } + // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters + if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { + filterAdded = true + filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + } + builder := expression.NewBuilder(). WithKeyCondition(condition). WithFilter(filter). @@ -805,6 +821,13 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } + // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters + if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { + filterAdded = true + filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + } + if filterAdded { builder = builder.WithFilter(filter) } @@ -1012,6 +1035,13 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } + // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters + if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { + filterAdded = true + filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + } + if len(params.Body) > 0 { // expression.Name("Color").In(expression.Value("red"), expression.Value("green"), expression.Value("blue")) var referenceIDExpressions []expression.OperandBuilder @@ -1203,6 +1233,13 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } + // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters + if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { + filterAdded = true + filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + } + limit := int64(10) if pageSize != nil { limit = *pageSize @@ -2921,6 +2958,13 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } + // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters + if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { + filterAdded = true + filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + } + if searchTerm != nil { log.WithFields(f).Debugf("adding search term filter for : %s ", *searchTerm) searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*searchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*searchTerm))) diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index c91af9f7a..03c9b233f 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -167,3 +167,13 @@ const RemoveApprovals = "RemoveApprovals" //GitHubUsernameCriteria represents criteria based on GH username const GitHubUsernameCriteria = "GitHubUsername" + +// SignatureQueryDefaultAll the signature query default active value - A flag to indicate how a default signature +// query should return data - show only 'active' signatures or 'all' signatures when no other query signed/approved +// params are provided +const SignatureQueryDefaultAll = "all" + +// SignatureQueryDefaultActive the signature query default active value - A flag to indicate how a default signature +// query should return data - show only 'active' signatures or 'all' signatures when no other query signed/approved +// params are provided +const SignatureQueryDefaultActive = "active" From f5ca3464c631a001b4e5ccef9367ce8bfbd4bc68 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 12 May 2021 21:28:07 -0700 Subject: [PATCH 0261/1276] Added Sort to Project Signature Query (#2945) - Added default sort order, updated next key logic Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 173 ++++++++++++++++++------ 1 file changed, 133 insertions(+), 40 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index dbfea2614..6f04abc07 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -5,6 +5,8 @@ package signatures import ( "context" + "encoding/base64" + "encoding/json" "errors" "fmt" "sort" @@ -464,13 +466,13 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u if approved != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } @@ -478,6 +480,7 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true + log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } @@ -576,13 +579,13 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co if approved != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } @@ -590,6 +593,7 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true + log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } @@ -742,10 +746,8 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur "signed": utils.BoolValue(params.Signed), } - indexName := SignatureProjectIDIndex - if params.SortOrder != nil && *params.SortOrder != "" { - indexName = SignatureProjectDateIDIndex - } + // Always sort by date + indexName := SignatureProjectDateIDIndex realPageSize := int64(100) if params.PageSize != nil && *params.PageSize > 0 { @@ -782,7 +784,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } if params.SignatureType != nil { - if params.SearchTerm != nil && (params.FullMatch != nil && !*params.FullMatch) { + if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" && (params.FullMatch != nil && !*params.FullMatch) { indexName = SignatureProjectIDTypeIndex condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(*params.SignatureType)))) } else { @@ -797,12 +799,15 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } } - if params.SearchTerm != nil { + if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { if *params.FullMatch { indexName = SignatureReferenceSearchIndex - condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(*params.SearchTerm)))) + log.WithFields(f).Debugf("adding filter signature_reference_name_lower: %s", strings.ToLower(utils.StringValue(params.SearchTerm))) + condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(utils.StringValue(params.SearchTerm))))) } else { - searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*params.SearchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*params.SearchTerm))) + log.WithFields(f).Debugf("adding filters signature_reference_name_lower: %s or user_email: %s", strings.ToLower(utils.StringValue(params.SearchTerm)), strings.ToLower(utils.StringValue(params.SearchTerm))) + searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(utils.StringValue(params.SearchTerm))). + Or(expression.Name("user_email").Contains(strings.ToLower(utils.StringValue(params.SearchTerm)))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } } @@ -810,13 +815,13 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur if params.Approved != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(params.Approved)) + log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(params.Approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if params.Signed != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(params.Signed)) + log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(params.Signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } @@ -824,6 +829,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true + log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } @@ -854,25 +860,35 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } f["indexName"] = indexName - // If we have the next key, set the exclusive start key value if params.NextKey != nil { - log.WithFields(f).Debugf("received a nextKey, value: %s", *params.NextKey) - // The primary key of the first item that this operation will evaluate. - // and the query key (if not the same) - queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ - "signature_id": { - S: params.NextKey, - }, - "signature_project_id": { - S: ¶ms.ProjectID, - }, + queryInput.ExclusiveStartKey, err = decodeNextKey(*params.NextKey) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem decoding next key value") + return nil, err } - if params.FullMatch != nil && *params.FullMatch && params.SearchTerm != nil { - queryInput.ExclusiveStartKey["signature_reference_name_lower"] = &dynamodb.AttributeValue{ - S: params.SearchTerm, + log.WithFields(f).Debugf("received a nextKey, value: %s - decoded: %+v", *params.NextKey, queryInput.ExclusiveStartKey) + } + /* + // If we have the next key, set the exclusive start key value + if params.NextKey != nil { + log.WithFields(f).Debugf("received a nextKey, value: %s", *params.NextKey) + // The primary key of the first item that this operation will evaluate. + // and the query key (if not the same) + queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "signature_id": { + S: params.NextKey, + }, + "signature_project_id": { + S: ¶ms.ProjectID, + }, + } + if params.FullMatch != nil && utils.BoolValue(params.FullMatch) && params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { + queryInput.ExclusiveStartKey["signature_reference_name_lower"] = &dynamodb.AttributeValue{ + S: params.SearchTerm, + } } } - } + */ sigs := make([]*models.Signature, 0) var lastEvaluatedKey string @@ -928,6 +944,16 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur lastEvaluatedKey = sigs[realPageSize-1].SignatureID } + if len(lastEvaluatedKey) > 0 { + log.WithFields(f).Debug("building next key...") + encodedString, err := buildNextKey(indexName, sigs[len(sigs)-1]) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to build nextKey") + } + lastEvaluatedKey = encodedString + log.WithFields(f).Debugf("lastEvaluatedKey encoded is: %s", encodedString) + } + return &models.Signatures{ ProjectID: params.ProjectID, ResultCount: int64(len(sigs)), @@ -1011,12 +1037,13 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si } } - if params.SearchTerm != nil { - if *params.FullMatch { + if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { + if utils.BoolValue(params.FullMatch) { indexName = SignatureReferenceSearchIndex - condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(*params.SearchTerm)))) + condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(utils.StringValue(params.SearchTerm))))) } else { - searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*params.SearchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*params.SearchTerm))) + searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(utils.StringValue(params.SearchTerm))). + Or(expression.Name("user_email").Contains(strings.ToLower(utils.StringValue(params.SearchTerm)))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } } @@ -1024,13 +1051,13 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si if params.Approved != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(params.Approved)) + log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(params.Approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if params.Signed != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(params.Signed)) + log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(params.Signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } @@ -1038,6 +1065,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true + log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } @@ -1094,7 +1122,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si S: ¶ms.ProjectID, }, } - if params.FullMatch != nil && *params.FullMatch && params.SearchTerm != nil { + if params.FullMatch != nil && *params.FullMatch && params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { queryInput.ExclusiveStartKey["signature_reference_name_lower"] = &dynamodb.AttributeValue{ S: params.SearchTerm, } @@ -1222,13 +1250,13 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI if approved != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } @@ -1236,6 +1264,7 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true + log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } @@ -2947,13 +2976,13 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID if approved != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_approved filter: %t", aws.BoolValue(approved)) + log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true - log.WithFields(f).Debugf("adding signature_signed filter: %t", aws.BoolValue(signed)) + log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) } @@ -2961,6 +2990,7 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true + log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } @@ -3336,3 +3366,66 @@ func (repo repository) getGerritUsers(ctx context.Context, authUser *auth.User, Error: nil, } } + +func buildNextKey(indexName string, signature *models.Signature) (string, error) { + nextKey := make(map[string]*dynamodb.AttributeValue) + nextKey["signature_id"] = &dynamodb.AttributeValue{S: aws.String(signature.SignatureID)} + switch indexName { + // TODO: review all these use-cases + case SignatureProjectIDIndex: + nextKey["signature_project_id"] = &dynamodb.AttributeValue{S: aws.String(signature.ProjectID)} + case SignatureProjectDateIDIndex: + nextKey["signature_project_id"] = &dynamodb.AttributeValue{S: aws.String(signature.ProjectID)} + nextKey["date_modified"] = &dynamodb.AttributeValue{S: aws.String(signature.SignatureModified)} + case SignatureProjectReferenceIndex: + nextKey["signature_project_id"] = &dynamodb.AttributeValue{S: aws.String(signature.ProjectID)} + nextKey["signature_reference_id"] = &dynamodb.AttributeValue{S: aws.String(signature.SignatureReferenceID)} + case SignatureProjectIDSigTypeSignedApprovedIDIndex: + nextKey["signature_project_id"] = &dynamodb.AttributeValue{S: aws.String(signature.ProjectID)} + nextKey["signature_signed_approved_id"] = &dynamodb.AttributeValue{S: aws.String(fmt.Sprintf("%t#%t", signature.SignatureSigned, signature.SignatureApproved))} + case SignatureProjectIDTypeIndex: + nextKey["signature_project_id"] = &dynamodb.AttributeValue{S: aws.String(signature.ProjectID)} + nextKey["signature_type"] = &dynamodb.AttributeValue{S: aws.String(signature.SignatureType)} + case SignatureReferenceIndex: + nextKey["signature_reference_id"] = &dynamodb.AttributeValue{S: aws.String(signature.SignatureReferenceID)} + case SignatureReferenceSearchIndex: + nextKey["signature_project_id"] = &dynamodb.AttributeValue{S: aws.String(signature.ProjectID)} + nextKey["signature_reference_name_lower"] = &dynamodb.AttributeValue{S: aws.String(signature.SignatureReferenceNameLower)} + } + + return encodeNextKey(nextKey) +} + +// encodeNextKey encodes the map as a string +func encodeNextKey(in map[string]*dynamodb.AttributeValue) (string, error) { + if len(in) == 0 { + return "", nil + } + b, err := json.Marshal(in) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(b), nil +} + +// decodeNextKey decodes the next key value into a dynamodb attribute value +func decodeNextKey(str string) (map[string]*dynamodb.AttributeValue, error) { + f := logrus.Fields{ + "functionName": "v1.events.repository.decodeNextKey", + } + + sDec, err := base64.StdEncoding.DecodeString(str) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error decoding string %s", str) + return nil, err + } + + var m map[string]*dynamodb.AttributeValue + err = json.Unmarshal(sDec, &m) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error unmarshalling string after decoding: %s", sDec) + return nil, err + } + + return m, nil +} From c6ad1a70a4f54ec28c69bcf5150b1231ae34c773 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 13 May 2021 08:35:57 -0700 Subject: [PATCH 0262/1276] Resolved #2938 Signatory Email Not Being Sent (#2946) - Removed logic that returns an error with CLA Signatory does not have an LFID - it's not required. If they do then fine - we can assign the cla-signatory role, but if it's missing then it's not a show-stopper. Signed-off-by: David Deal --- cla-backend-go/v2/sign/service.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 733166aa9..01d181491 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -415,7 +415,6 @@ func prepareUserForSigning(ctx context.Context, userEmail string, companySFID, p "signedEntityName": signedEntityName, } - var ErrNotInOrg error role := utils.CLASignatoryRole log.WithFields(f).Debug("called") usc := userService.GetClient() @@ -438,20 +437,20 @@ func prepareUserForSigning(ctx context.Context, userEmail string, companySFID, p // assign user role of cla signatory for this project osc := organizationService.GetClient() - // make user cla-signatory + // Attempt to assign the cla-signatory role log.WithFields(f).Debugf("assigning user role of %s...", role) err = osc.CreateOrgUserRoleOrgScopeProjectOrg(ctx, userEmail, projectSFID, companySFID, roleID) if err != nil { + // Log the error - but assigning the cla-signatory role is not a requirement as most users do not have a LF Login - do not throw an error if strings.Contains(err.Error(), "associated with some organization") { msg := fmt.Sprintf("user: %s already associated with some organization", user.Username) - ErrNotInOrg = errors.New(msg) log.WithFields(f).WithError(err).Warn(msg) - return ErrNotInOrg - } - // Ignore conflict - role has already been assigned, otherwise, return the error - if _, ok := err.(*organizations.CreateOrgUsrRoleScopesConflict); !ok { + // return errors.New(msg) + } else if _, ok := err.(*organizations.CreateOrgUsrRoleScopesConflict); !ok { + log.WithFields(f).WithError(err).Warnf("assigning user role of %s failed - user already assigned the role: %v", role, err) + // return err + } else { log.WithFields(f).WithError(err).Warnf("assigning user role of %s failed: %v", role, err) - return err } } From 3a5da0a0d518cff61d9d9c4c0a5840985a79ae42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 May 2021 08:50:45 -0700 Subject: [PATCH 0263/1276] Bump url-parse from 1.4.7 to 1.5.0 in /cla-frontend-project-console/src (#2927) Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.0. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/package.json | 2 +- cla-frontend-project-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-project-console/src/package.json b/cla-frontend-project-console/src/package.json index 7fa182314..e5e27ee59 100644 --- a/cla-frontend-project-console/src/package.json +++ b/cla-frontend-project-console/src/package.json @@ -54,7 +54,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.4.7", + "url-parse": "^1.5.0", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 658895412..19f23f84b 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -4200,10 +4200,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.4.7: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== +url-parse@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" + integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From a06f5761ffab9f1f8b08c5370b0a158987fdd991 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 May 2021 08:51:08 -0700 Subject: [PATCH 0264/1276] Bump hosted-git-info from 2.8.8 to 2.8.9 (#2935) Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 246d24da5..0840af6c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2451,9 +2451,9 @@ has@^1.0.3: function-bind "^1.1.1" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-cache-semantics@3.8.1: version "3.8.1" From 5450a1d9883f68ff7ddfdd8dad62d9770a2bc497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 May 2021 08:51:44 -0700 Subject: [PATCH 0265/1276] Bump lodash from 4.17.20 to 4.17.21 in /cla-backend-go (#2937) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index e8b46aa70..1f3a8d7d4 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -3170,9 +3170,9 @@ lodash.union@^4.6.0: integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-ok@^0.1.1: version "0.1.1" From 1a75e4b534864492df362115e3bcc57a56817d54 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Tue, 18 May 2021 00:42:30 +0300 Subject: [PATCH 0266/1276] [Snyk] Security upgrade urllib3 from 1.25.8 to 1.25.9 (#2947) The following vulnerabilities are fixed by pinning transitive dependencies: - https://snyk.io/vuln/SNYK-PYTHON-URLLIB3-1014645 --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index b6037b1df..308486186 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -53,7 +53,7 @@ six==1.13.0 soupsieve==1.9.5 termcolor==1.1.0 typed-ast==1.4.1 -urllib3==1.25.8 +urllib3==1.25.9 vintage==0.4.1 wcwidth==0.1.7 Werkzeug==0.15.5 From 1103c4a070c6289d71a1a14d9b71c4b30a17cf44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 May 2021 16:07:40 -0700 Subject: [PATCH 0267/1276] Bump hosted-git-info from 2.8.8 to 2.8.9 in /cla-frontend-contributor-console/edge (#2933) Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/edge/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock index 8c6b32458..7bf38acdc 100644 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ b/cla-frontend-contributor-console/edge/yarn.lock @@ -1448,9 +1448,9 @@ home-or-tmp@^2.0.0: os-tmpdir "^1.0.1" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-signature@~1.2.0: version "1.2.0" From 4f49ef714a0be7e2fb587585cb6ccb4d1a8e6b59 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 18 May 2021 14:27:08 -0700 Subject: [PATCH 0268/1276] Resolved [#2948] Signature Query for Project and Company (#2950) - Resolved issue with project and company query - issue occured when more than 1 CCLA signature was on file, user specified limit 1, and the next key was invalid. Ultimately, the issue was with the next key logic - it was missing the signature_reference_id in the hash. Signed-off-by: David Deal --- cla-backend-go/signatures/repository.go | 61 ++++++++++--------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 6f04abc07..330b19186 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -868,27 +868,6 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } log.WithFields(f).Debugf("received a nextKey, value: %s - decoded: %+v", *params.NextKey, queryInput.ExclusiveStartKey) } - /* - // If we have the next key, set the exclusive start key value - if params.NextKey != nil { - log.WithFields(f).Debugf("received a nextKey, value: %s", *params.NextKey) - // The primary key of the first item that this operation will evaluate. - // and the query key (if not the same) - queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ - "signature_id": { - S: params.NextKey, - }, - "signature_project_id": { - S: ¶ms.ProjectID, - }, - } - if params.FullMatch != nil && utils.BoolValue(params.FullMatch) && params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { - queryInput.ExclusiveStartKey["signature_reference_name_lower"] = &dynamodb.AttributeValue{ - S: params.SearchTerm, - } - } - } - */ sigs := make([]*models.Signature, 0) var lastEvaluatedKey string @@ -1283,6 +1262,7 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI return nil, err } + indexName := SignatureProjectReferenceIndex // Assemble the query input parameters queryInput := &dynamodb.QueryInput{ ExpressionAttributeNames: expr.Names(), @@ -1291,27 +1271,21 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI FilterExpression: expr.Filter(), ProjectionExpression: expr.Projection(), TableName: aws.String(repo.signatureTableName), - IndexName: aws.String(SignatureProjectReferenceIndex), // Name of a secondary index to scan + IndexName: aws.String(indexName), // Name of a secondary index to scan Limit: aws.Int64(limit), - //IndexName: aws.String("project-signature-index"), // Name of a secondary index to scan } // If we have the next key, set the exclusive start key value if nextKey != nil { - log.WithFields(f).Debugf("Received a nextKey, value: %s", *nextKey) - // The primary key of the first item that this operation will evaluate. - // and the query key (if not the same) - queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ - "signature_id": { - S: nextKey, - }, - "signature_project_id": { - S: &projectID, - }, + queryInput.ExclusiveStartKey, err = decodeNextKey(*nextKey) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem decoding next key value") + return nil, err } + log.WithFields(f).Debugf("received a nextKey, value: %s - decoded: %+v", *nextKey, queryInput.ExclusiveStartKey) } - var sigs []*models.Signature + sigs := make([]*models.Signature, 0) var lastEvaluatedKey string // Loop until we have all the records @@ -1338,7 +1312,6 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI // Add to the signatures response model to the list sigs = append(sigs, signatureList...) - // log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey["signature_id"]) if results.LastEvaluatedKey["signature_id"] != nil { lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ @@ -1346,7 +1319,10 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI S: aws.String(lastEvaluatedKey), }, "signature_project_id": { - S: &projectID, + S: aws.String(projectID), + }, + "signature_reference_id": { + S: aws.String(companyID), }, } } else { @@ -1368,6 +1344,17 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI return nil, err } + // Calculate the next key - this uses a compound key - need to encode it before sharing with the caller + if len(lastEvaluatedKey) > 0 { + log.WithFields(f).Debug("building next key...") + encodedString, err := buildNextKey(indexName, sigs[len(sigs)-1]) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to build nextKey") + } + lastEvaluatedKey = encodedString + log.WithFields(f).Debugf("lastEvaluatedKey encoded is: %s", encodedString) + } + // Meta-data for the response totalCount := *describeTableResult.Table.ItemCount @@ -1384,7 +1371,7 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI }, nil } -// Get project signatures with no pagination +// ProjectSignatures - get project signatures with no pagination func (repo repository) ProjectSignatures(ctx context.Context, projectID string) (*models.Signatures, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.ProjectSignatures", From ca2b19872cdadb8926a1ecf8f9e6c888c889c783 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 19 May 2021 11:04:43 -0700 Subject: [PATCH 0269/1276] Resolved PCC-968 - Return 409 Conflict when Duplicate CLA Name Occurs (#2951) - Updated swagger spec to include 409 conflict response error - Added logic to trap for conflict specific error and return 409 Signed-off-by: David Deal --- cla-backend-go/swagger/cla.v2.yaml | 2 ++ cla-backend-go/v2/cla_groups/handlers.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 002a18015..7f2a2a7f3 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -411,6 +411,8 @@ paths: $ref: '#/responses/forbidden' '404': $ref: '#/responses/not-found' + '409': + $ref: '#/responses/conflict' '500': $ref: '#/responses/internal-server-error' tags: diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 9541a4a66..d07051c75 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -164,6 +164,11 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P claGroup, err := service.UpdateCLAGroup(ctx, authUser, claGroupModel, params.Body, utils.StringValue(params.XUSERNAME)) if err != nil { + // Return a 409 conflict if we have a duplicate name + if _, ok := err.(*utils.CLAGroupNameConflict); ok { + return cla_group.NewUpdateClaGroupConflict().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseConflictWithError(reqID, err.Error(), err)) + } log.WithFields(f).WithError(err).Warn("unable to update the CLA Group Name and/or Description - update failed") return cla_group.NewUpdateClaGroupBadRequest().WithXRequestID(reqID).WithPayload( utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to update CLA Group by ID: %s", params.ClaGroupID), err)) From af7f1b7e01ceaa9d62157f3476bc74817a5970da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 May 2021 11:36:49 -0700 Subject: [PATCH 0270/1276] Bump hosted-git-info from 2.7.1 to 2.8.9 in /cla-frontend-corporate-console/src (#2930) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index 00c9980c1..06bdb1339 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -1790,8 +1790,9 @@ hoek@6.x.x, hoek@^4.2.1: integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-errors@1.7.2: version "1.7.2" From e360a862dfe990fe713172dcdc17b779ca3b6a58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 May 2021 11:37:15 -0700 Subject: [PATCH 0271/1276] Bump hosted-git-info from 2.7.1 to 2.8.9 in /cla-frontend-contributor-console/src (#2932) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 4643accbf..84c3ff46a 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -1820,8 +1820,9 @@ hoek@6.x.x, hoek@^4.2.1: integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-errors@1.7.2: version "1.7.2" From 1da43c85c96f1c20a7d7f5932be85dacde7c5089 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 May 2021 11:37:35 -0700 Subject: [PATCH 0272/1276] Bump hosted-git-info from 2.7.1 to 2.8.9 in /cla-frontend-project-console/src (#2931) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 19f23f84b..8ad50aaad 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -1843,8 +1843,9 @@ hoek@6.x.x, hoek@^4.2.1: integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-errors@1.7.2: version "1.7.2" From 1ebfb2769a3a50b28f2f681fca159370ab4f0db5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 May 2021 11:55:42 -0700 Subject: [PATCH 0273/1276] Bump url-parse from 1.4.7 to 1.5.0 in /cla-frontend-corporate-console/src (#2926) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/package.json | 2 +- cla-frontend-corporate-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-corporate-console/src/package.json b/cla-frontend-corporate-console/src/package.json index ad099499c..184a0456b 100644 --- a/cla-frontend-corporate-console/src/package.json +++ b/cla-frontend-corporate-console/src/package.json @@ -50,7 +50,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.4.7", + "url-parse": "^1.5.0", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index 06bdb1339..d387df25f 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -4051,10 +4051,10 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url-parse@^1.4.7: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== +url-parse@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" + integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From 176111ada3b4cd4dbaa17222db214024181dc380 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 May 2021 12:42:39 -0700 Subject: [PATCH 0274/1276] Bump py from 1.8.0 to 1.10.0 in /cla-backend (#2889) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 308486186..c0f65349f 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -31,7 +31,7 @@ nose2==0.9.1 oauthlib==3.1.0 packaging==19.2 pluggy==0.13.1 -py==1.8.0 +py==1.10.0 pyasn1==0.4.8 pydocusign==2.2 PyGithub==1.43.8 From ef0cce7c18e9b2781d690357cd01899f33520fd4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 19 May 2021 13:00:09 -0700 Subject: [PATCH 0275/1276] Resolved CCLA Query Issue (#2952) --- cla-backend-go/signatures/repository.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 330b19186..d08661a7c 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -1192,7 +1192,7 @@ func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID return nil, getErr } - if sigs == nil || sigs.Signatures == nil { + if sigs == nil || len(sigs.Signatures) == 0 { return nil, nil } @@ -1300,17 +1300,20 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI } log.WithFields(f).Debugf("query response received with %d results", len(results.Items)) - // Convert the list of DB models to a list of response models - log.WithFields(f).Debugf("building response model for %d results", len(results.Items)) - signatureList, modelErr := repo.buildProjectSignatureModels(ctx, results, projectID, LoadACLDetails) - if modelErr != nil { - log.WithFields(f).Warnf("error converting DB model to response model for signatures with project %s with company: %s, error: %v", - projectID, companyID, modelErr) - return nil, modelErr - } + // If we have any results - may not have any after filters are applied, but may have more records to page through... + if len(results.Items) > 0 { + // Convert the list of DB models to a list of response models + log.WithFields(f).Debugf("building response model for %d results", len(results.Items)) + signatureList, modelErr := repo.buildProjectSignatureModels(ctx, results, projectID, LoadACLDetails) + if modelErr != nil { + log.WithFields(f).Warnf("error converting DB model to response model for signatures with project %s with company: %s, error: %v", + projectID, companyID, modelErr) + return nil, modelErr + } - // Add to the signatures response model to the list - sigs = append(sigs, signatureList...) + // Add to the signatures response model to the list + sigs = append(sigs, signatureList...) + } if results.LastEvaluatedKey["signature_id"] != nil { lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S From e84a6941005c3e5e9c7affcb712a1c1fd9671957 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 20 May 2021 19:43:38 +0300 Subject: [PATCH 0276/1276] [Snyk] Upgrade aws-sdk from 2.775.0 to 2.885.0 (#2911) --- cla-landing-page/package.json | 2 +- cla-landing-page/yarn.lock | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 6946820be..6966dabe5 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -52,7 +52,7 @@ "@types/node": "^12.11.1", "@ng-bootstrap/ng-bootstrap": "^6.1.0", "auth0-js": "^9.13.2", - "aws-sdk": "^2.733.0", + "aws-sdk": "^2.885.0", "bootstrap": "^4.4.0", "query-string": "^6.13.6", "rxjs": "~6.6.7", diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 377c1658b..06b3c8a96 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -2419,7 +2419,7 @@ autoprefixer@9.7.4: postcss "^7.0.26" postcss-value-parser "^4.0.2" -aws-sdk@^2.224.1, aws-sdk@^2.733.0: +aws-sdk@^2.224.1: version "2.775.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.775.0.tgz#707c369e84b79cf3b136819c01f849b93fb44eab" integrity sha512-rlej1sgHmfhl+PJqpQ2qOOsbHEEnLBIKBmanMTUNGiEAfuS0MpFjXECXTpJIOrbUzl3OZuAYrGuBkg2qrBwRbQ== @@ -2449,6 +2449,21 @@ aws-sdk@^2.803.0: uuid "3.3.2" xml2js "0.4.19" +aws-sdk@^2.885.0: + version "2.899.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.899.0.tgz#fac90b8926707b3e9dcd1e7d5937cfa71c0be5ca" + integrity sha512-k8jSANDQGvTyyj1f/7Hj4SWaV61/gjj/BopRmavAr6n1ayjXtUeVrV8G29+ABD3V82pHXDqLq47bqNmZ9m86xQ== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" From 572d8678545faff9307eb18cfd2ccb1db9a0601b Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 20 May 2021 14:09:41 -0700 Subject: [PATCH 0277/1276] Resolved 2955 - Fixed Contributor Console Typo (#2956) --- cla-backend-go/v2/company/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index b889276ad..1e44f218d 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1798,7 +1798,7 @@ func (s *service) RequestCompanyAdmin(ctx context.Context, userID string, claMan return orgErr } if len(organizations.Data) > 0 { - msg := fmt.Sprintf("Comapny already exist with the name: %s ", companyName) + msg := fmt.Sprintf("Company already exists with the name: %s ", companyName) log.Warn(msg) return errors.New(msg) } From 963090ca7f348ca92c25a6526fb189e9099698f3 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Fri, 21 May 2021 01:27:04 +0300 Subject: [PATCH 0278/1276] [Snyk] Upgrade graceful-fs from 4.2.4 to 4.2.6 (#2953) --- cla-frontend-corporate-console/package.json | 2 +- cla-frontend-corporate-console/yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cla-frontend-corporate-console/package.json b/cla-frontend-corporate-console/package.json index 3316ac840..ff305e0af 100644 --- a/cla-frontend-corporate-console/package.json +++ b/cla-frontend-corporate-console/package.json @@ -13,7 +13,7 @@ "install-frontend": "../scripts/install-frontend.sh" }, "dependencies": { - "graceful-fs": "^4.2.2", + "graceful-fs": "^4.2.6", "ionic": "^3.20.0", "serverless": "^2.15.0", "serverless-cloudfront-invalidate": "^1.2.1", diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 75f9ac8ad..208e1e46c 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -3055,11 +3055,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" From 73b1e47c798836ffa3a35480b5256e65b5e4a75b Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 21 May 2021 17:58:07 +0300 Subject: [PATCH 0279/1276] [#2954] Bug/Create CLA Group (#2958) --- .../projects_cla_groups/repository.go | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/cla-backend-go/projects_cla_groups/repository.go b/cla-backend-go/projects_cla_groups/repository.go index be33acecf..26467a4c6 100644 --- a/cla-backend-go/projects_cla_groups/repository.go +++ b/cla-backend-go/projects_cla_groups/repository.go @@ -248,22 +248,37 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st } _, nowStr := utils.CurrentTime() - input := &ProjectClaGroup{ - ProjectSFID: projectSFID, - ProjectName: projectName, - ClaGroupID: claGroupID, - ClaGroupName: claGroupName, - FoundationSFID: foundationSFID, - FoundationName: foundationName, - Note: fmt.Sprintf("Associate CLA Group with project API request on: %s", nowStr), - Version: "v1", - DateCreated: nowStr, - DateModified: nowStr, - } - - av, err := dynamodbattribute.MarshalMap(input) - if err != nil { - return err + item := map[string]*dynamodb.AttributeValue{ + "project_sfid": { + S: aws.String(projectSFID), + }, + "project_name": { + S: aws.String(projectName), + }, + "cla_group_id": { + S: aws.String(claGroupID), + }, + "cla_group_name": { + S: aws.String(claGroupName), + }, + "foundation_sfid": { + S: aws.String(foundationSFID), + }, + "foundation_name": { + S: aws.String(foundationName), + }, + "note": { + S: aws.String(fmt.Sprintf("Associate CLA Group with project API request on: %s", nowStr)), + }, + "version": { + S: aws.String("v1"), + }, + "date_created": { + S: aws.String(nowStr), + }, + "date_modified": { + S: aws.String(nowStr), + }, } log.WithFields(f).Debug("Locating records with matching projectSFID...") @@ -277,9 +292,9 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st log.WithFields(f).Debugf("record found with matching projectSFID: %+v", existingRecord) } - log.WithFields(f).Debugf("adding entry into the %s table with: %+v", repo.tableName, input) - _, err = repo.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ - Item: av, + log.WithFields(f).Debugf("adding entry into the %s table with: %+v", repo.tableName, item) + _, err := repo.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ + Item: item, TableName: aws.String(repo.tableName), ConditionExpression: aws.String("attribute_not_exists(project_sfid)"), }) From 99602c53126c993d2620b9de03368c923260934d Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Fri, 21 May 2021 18:21:40 +0300 Subject: [PATCH 0280/1276] [Snyk] Upgrade graceful-fs from 4.2.4 to 4.2.6 (#2957) --- cla-frontend-contributor-console/package.json | 2 +- cla-frontend-contributor-console/yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cla-frontend-contributor-console/package.json b/cla-frontend-contributor-console/package.json index 2a31baa5a..e55171d2f 100644 --- a/cla-frontend-contributor-console/package.json +++ b/cla-frontend-contributor-console/package.json @@ -13,7 +13,7 @@ "install-frontend": "../scripts/install-frontend.sh" }, "dependencies": { - "graceful-fs": "^4.2.2", + "graceful-fs": "^4.2.6", "ionic": "^3.20.0", "serverless": "^2.15.0", "serverless-cloudfront-invalidate": "^1.2.1", diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index de6b5e402..014f2fb79 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -3075,11 +3075,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" From a1a2146372f514a8a745d22afedf10b95fd9e0ed Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Tue, 25 May 2021 20:02:18 +0300 Subject: [PATCH 0281/1276] [Snyk] Upgrade aws-sdk from 2.488.0 to 2.897.0 (#2959) --- .../src/package.json | 2 +- cla-frontend-corporate-console/src/yarn.lock | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/cla-frontend-corporate-console/src/package.json b/cla-frontend-corporate-console/src/package.json index 184a0456b..f0996d309 100644 --- a/cla-frontend-corporate-console/src/package.json +++ b/cla-frontend-corporate-console/src/package.json @@ -38,7 +38,7 @@ "@swimlane/ngx-datatable": "^11.3.2", "@types/lodash": "4.14.112", "@types/node": "^8.0.17", - "aws-sdk": "^2.304.0", + "aws-sdk": "^2.897.0", "chart.js": "^2.5.0", "google-libphonenumber": "^2.0.18", "graceful-fs": "^4.2.2", diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index d387df25f..849d57044 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -394,13 +394,14 @@ autoprefixer@^7.2.6: postcss "^6.0.17" postcss-value-parser "^3.2.3" -aws-sdk@^2.304.0: - version "2.488.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.488.0.tgz#1664e14743d93793d2ffc4b2e70b7ce19c008d84" +aws-sdk@^2.897.0: + version "2.912.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.912.0.tgz#3f096b057ccc7b22ca234b44167be5c02a1daab3" + integrity sha512-ImeMEC014AB9DMlS3X25G+y1Q/jGDwh7kOY+5AE0PBOMjDrQOYjfiqVYbFU2nZx57Uk7UVFclOb3Lty3hEz2gg== dependencies: - buffer "4.9.1" + buffer "4.9.2" events "1.1.1" - ieee754 "1.1.8" + ieee754 "1.1.13" jmespath "0.15.0" querystring "0.2.0" sax "1.2.1" @@ -617,7 +618,16 @@ buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" -buffer@4.9.1, buffer@^4.3.0: +buffer@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +buffer@^4.3.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: @@ -1836,11 +1846,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - -ieee754@^1.1.4: +ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" From b64b351c358ab6fb1d2ec63f51280c7b8a868e5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:04:40 -0700 Subject: [PATCH 0282/1276] Bump lodash from 4.17.20 to 4.17.21 in /cla-backend (#2929) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index eb2ab3c37..d672ee8df 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -3939,9 +3939,9 @@ lodash.values@^4.3.0: integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-ok@^0.1.1: version "0.1.1" From 4cbf45ae579081dd5d1d3a13f40de4285373be64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:05:22 -0700 Subject: [PATCH 0283/1276] Bump lodash from 4.17.20 to 4.17.21 in /cla-frontend-project-console (#2939) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index 1f47c6f18..c334447b7 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -3979,9 +3979,9 @@ lodash.union@^4.6.0: integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.8.0: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-ok@^0.1.1: version "0.1.1" From 9691433b4bdaf6ea48f1992fa7179d9d568f62bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:05:46 -0700 Subject: [PATCH 0284/1276] Bump lodash from 4.17.19 to 4.17.21 in /cla-frontend-project-console/src (#2921) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 8ad50aaad..dc655ea79 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -2381,15 +2381,10 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@>=4.17.19, lodash@^4.17.15: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.0.0, lodash@^4.17.11, lodash@~4.17.10: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@>=4.17.19, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.15, lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== longest@^1.0.1: version "1.0.1" From 9aa694f99c4b49d28bc3ff1534814e899d9b4486 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:06:10 -0700 Subject: [PATCH 0285/1276] Bump lodash from 4.17.19 to 4.17.21 in /cla-frontend-contributor-console/edge (#2925) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/edge/yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock index 7bf38acdc..d30e8eecf 100644 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ b/cla-frontend-contributor-console/edge/yarn.lock @@ -1866,15 +1866,10 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@^4.0.0, lodash@^4.17.15, lodash@~4.17.10: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.17.14, lodash@^4.17.4: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== longest@^1.0.1: version "1.0.1" From 54e997bd69e799847b6af1caba04a34cf62d8afd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:06:34 -0700 Subject: [PATCH 0286/1276] Bump lodash from 4.17.20 to 4.17.21 in /cla-frontend-contributor-console (#2924) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 014f2fb79..71ba0f0e7 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -4069,9 +4069,9 @@ lodash.union@^4.6.0: integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.8.0: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-ok@^0.1.1: version "0.1.1" From 8d8b1f7deee2fdc68d7c6d74066ea42cedf91eb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:06:58 -0700 Subject: [PATCH 0287/1276] Bump lodash from 4.17.20 to 4.17.21 (#2934) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0840af6c3..8e21dbb68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3026,9 +3026,9 @@ lodash.union@^4.6.0: integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log@^6.0.0: version "6.0.0" From e8793d7e5dda9d4f58041b3282105f6f0cd0588d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:07:24 -0700 Subject: [PATCH 0288/1276] Bump lodash from 4.17.20 to 4.17.21 in /cla-frontend-corporate-console (#2936) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 208e1e46c..1fa89e32d 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -4030,9 +4030,9 @@ lodash.union@^4.6.0: integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.8.0: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-ok@^0.1.1: version "0.1.1" From b900980a506d815537b5e7a221265808b5670905 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:07:52 -0700 Subject: [PATCH 0289/1276] Bump lodash from 4.17.19 to 4.17.21 in /cla-frontend-corporate-console/src (#2923) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index 849d57044..22ee0fefa 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -2281,15 +2281,10 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@>=4.17.19, lodash@^4.17.15: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.0.0, lodash@^4.17.11, lodash@~4.17.10: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@>=4.17.19, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.15, lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== longest@^1.0.1: version "1.0.1" From c1c8cc55a7ad442748e9aa4035998f1cc6dc5dc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 10:08:17 -0700 Subject: [PATCH 0290/1276] Bump lodash from 4.17.19 to 4.17.21 in /cla-frontend-contributor-console/src (#2922) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 84c3ff46a..fb0203b33 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -2326,15 +2326,10 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@>=4.17.19, lodash@^4.17.15: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.0.0, lodash@^4.17.11, lodash@~4.17.10: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@>=4.17.19, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.15, lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== longest@^1.0.1: version "1.0.1" From a13c428f79712fcbe928d2ecf81b479227d4444b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 May 2021 12:29:24 -0700 Subject: [PATCH 0291/1276] Bump chart.js from 2.8.0 to 2.9.4 in /cla-frontend-contributor-console/src (#2941) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/package.json | 2 +- cla-frontend-contributor-console/src/yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cla-frontend-contributor-console/src/package.json b/cla-frontend-contributor-console/src/package.json index daf558756..a51c8c7d1 100644 --- a/cla-frontend-contributor-console/src/package.json +++ b/cla-frontend-contributor-console/src/package.json @@ -40,7 +40,7 @@ "@types/node": "^8.0.17", "auth0-js": "^9.13.2", "aws-sdk": "^2.304.0", - "chart.js": "^2.5.0", + "chart.js": "^2.9.4", "google-libphonenumber": "^2.0.18", "graceful-fs": "^4.2.2", "ionic-angular": "3.9.2", diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index fb0203b33..1d91d9e19 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -720,9 +720,10 @@ chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chart.js@^2.5.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.8.0.tgz#b703b10d0f4ec5079eaefdcd6ca32dc8f826e0e9" +chart.js@^2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== dependencies: chartjs-color "^2.1.0" moment "^2.10.2" From 77594ae51e3183a28eeb5be04a0c0ec509b64e50 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 26 May 2021 00:25:01 +0300 Subject: [PATCH 0292/1276] [Snyk] Upgrade graceful-fs from 4.2.4 to 4.2.6 (#2961) --- cla-frontend-project-console/edge/package.json | 2 +- cla-frontend-project-console/edge/yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cla-frontend-project-console/edge/package.json b/cla-frontend-project-console/edge/package.json index fcf194d49..18de7a2b7 100644 --- a/cla-frontend-project-console/edge/package.json +++ b/cla-frontend-project-console/edge/package.json @@ -10,7 +10,7 @@ "test": "./node_modules/.bin/jasmine --config=jasmine.json" }, "dependencies": { - "graceful-fs": "^4.2.2" + "graceful-fs": "^4.2.6" }, "devDependencies": { "babel-core": "^6.26.0", diff --git a/cla-frontend-project-console/edge/yarn.lock b/cla-frontend-project-console/edge/yarn.lock index 76b68e1bf..3e897acfe 100644 --- a/cla-frontend-project-console/edge/yarn.lock +++ b/cla-frontend-project-console/edge/yarn.lock @@ -1094,11 +1094,16 @@ globals@^9.18.0: resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" From cd9e2c8dff928328b5b02b0c993749f54c0e40f1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 25 May 2021 16:43:20 -0700 Subject: [PATCH 0293/1276] Added Sort/Most Recent to Preview (#2963) --- cla-backend-go/approval_list/service.go | 2 +- cla-backend-go/cmd/server.go | 55 ++++----- cla-backend-go/emails/prefill.go | 6 +- cla-backend-go/events/mockrepo.go | 2 +- cla-backend-go/events/service.go | 4 +- .../github_organizations/service.go | 4 +- cla-backend-go/go.mod | 6 +- cla-backend-go/go.sum | 39 +----- cla-backend-go/project/repository.go | 2 +- cla-backend-go/project/service.go | 28 ++--- .../projects_cla_groups/repository.go | 112 ++++++++++-------- cla-backend-go/projects_cla_groups/service.go | 95 +++++++++++++++ cla-backend-go/template/service.go | 97 +++++++++++++-- cla-backend-go/v2/cla_groups/handlers.go | 6 +- cla-backend-go/v2/cla_groups/helpers.go | 12 +- cla-backend-go/v2/cla_groups/service.go | 16 +-- cla-backend-go/v2/cla_manager/handlers.go | 6 +- cla-backend-go/v2/cla_manager/service.go | 6 +- cla-backend-go/v2/company/handlers.go | 4 +- cla-backend-go/v2/company/service.go | 8 +- cla-backend-go/v2/dynamo_events/autoenable.go | 6 +- .../v2/dynamo_events/cla_groups_db_handler.go | 21 ++-- .../v2/dynamo_events/cla_manager.go | 4 +- cla-backend-go/v2/dynamo_events/events.go | 2 +- .../v2/dynamo_events/github_repository.go | 4 +- .../v2/dynamo_events/projects_cla_groups.go | 2 +- cla-backend-go/v2/dynamo_events/signatures.go | 6 +- cla-backend-go/v2/events/handlers.go | 6 +- cla-backend-go/v2/gerrits/handlers.go | 4 +- .../v2/github_organizations/service.go | 2 +- cla-backend-go/v2/metrics/handlers.go | 2 +- cla-backend-go/v2/metrics/repository.go | 5 +- cla-backend-go/v2/metrics/service.go | 9 +- cla-backend-go/v2/project/service.go | 2 +- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 4 +- cla-backend-go/v2/signatures/handlers.go | 18 +-- cla-backend-go/v2/signatures/service.go | 2 +- cla-backend-go/v2/template/handlers.go | 49 ++++++-- 39 files changed, 423 insertions(+), 237 deletions(-) create mode 100644 cla-backend-go/projects_cla_groups/service.go diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 4bf7077c1..ae9ce32df 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -197,7 +197,7 @@ func (s service) ApproveCclaApprovalListRequest(ctx context.Context, claUser *us // Get project cla Group records log.WithFields(f).Debugf("Getting SalesForce Projects for claGroup: %s ", claGroupID) - projectCLAGroups, getErr := s.projectsCLAGroupRepository.GetProjectsIdsForClaGroup(claGroupID) + projectCLAGroups, getErr := s.projectsCLAGroupRepository.GetProjectsIdsForClaGroup(ctx, claGroupID) if getErr != nil { msg := fmt.Sprintf("Error getting SF projects for claGroup: %s ", claGroupID) log.Debug(msg) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index cbd9d8f58..5718fe743 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -237,9 +237,9 @@ func server(localMode bool) http.Handler { approvalListRepo := approval_list.NewRepository(awsSession, stage) v1CompanyRepo := v1Company.NewRepository(awsSession, stage) eventsRepo := events.NewRepository(awsSession, stage) - projectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) - v1CLAGroupRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, projectClaGroupRepo) - metricsRepo := metrics.NewRepository(awsSession, stage, configFile.APIGatewayURL, projectClaGroupRepo) + v1ProjectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) + v1CLAGroupRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, v1ProjectClaGroupRepo) + metricsRepo := metrics.NewRepository(awsSession, stage, configFile.APIGatewayURL, v1ProjectClaGroupRepo) githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) claManagerReqRepo := cla_manager.NewRepository(awsSession, stage) @@ -248,7 +248,7 @@ func server(localMode bool) http.Handler { usersRepo, v1CompanyRepo, v1CLAGroupRepo, - projectClaGroupRepo, + v1ProjectClaGroupRepo, }) gerritService := gerrits.NewService(gerritRepo, &gerrits.LFGroup{ @@ -270,31 +270,32 @@ func server(localMode bool) http.Handler { organization_service.InitClient(configFile.PlatformAPIGatewayURL, eventsService) acs_service.InitClient(configFile.PlatformAPIGatewayURL, configFile.AcsAPIKey) + v1ProjectClaGroupService := projects_cla_groups.NewService(v1ProjectClaGroupRepo) usersService := users.NewService(usersRepo, eventsService) healthService := health.New(Version, Commit, Branch, BuildDate) templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) - v1ProjectService := project.NewService(v1CLAGroupRepo, repositoriesRepo, gerritRepo, projectClaGroupRepo, usersRepo) - emailTemplateService := emails.NewEmailTemplateService(v1CLAGroupRepo, projectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) + v1ProjectService := project.NewService(v1CLAGroupRepo, repositoriesRepo, gerritRepo, v1ProjectClaGroupRepo, usersRepo) + emailTemplateService := emails.NewEmailTemplateService(v1CLAGroupRepo, v1ProjectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) emailService := emails.NewService(emailTemplateService, v1ProjectService) - v2ProjectService := v2Project.NewService(v1ProjectService, v1CLAGroupRepo, projectClaGroupRepo) + v2ProjectService := v2Project.NewService(v1ProjectService, v1CLAGroupRepo, v1ProjectClaGroupRepo) v1CompanyService := v1Company.NewService(v1CompanyRepo, configFile.CorporateConsoleV1URL, userRepo, usersService) - v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, v1CLAGroupRepo, usersRepo, v1CompanyRepo, projectClaGroupRepo, eventsService) - v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, projectClaGroupRepo, v1CompanyService) + v2CompanyService := v2Company.NewService(v1CompanyService, signaturesRepo, v1CLAGroupRepo, usersRepo, v1CompanyRepo, v1ProjectClaGroupRepo, eventsService) + v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, v1ProjectClaGroupRepo, v1CompanyService) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) - v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, projectClaGroupRepo, signaturesRepo, usersService) - v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, projectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) - v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo) - v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, projectClaGroupRepo, githubOrganizationsRepo) - v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, projectClaGroupRepo) - v1ApprovalListService := approval_list.NewService(approvalListRepo, projectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, v1CLAGroupRepo, signaturesRepo, emailTemplateService, configFile.CorporateConsoleV2URL, http.DefaultClient) + v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, v1ProjectClaGroupRepo, signaturesRepo, usersService) + v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, v1ProjectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) + v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo) + v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsRepo) + v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, v1ProjectClaGroupRepo) + v1ApprovalListService := approval_list.NewService(approvalListRepo, v1ProjectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, v1CLAGroupRepo, signaturesRepo, emailTemplateService, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) - v2MetricsService := metrics.NewService(metricsRepo, projectClaGroupRepo) - githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo) - v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, projectClaGroupRepo, githubOrganizationsService) - autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, projectClaGroupRepo, v1ProjectService) + v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo) + githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo) + v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsService) + autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, githubOrganizationsRepo, eventsService, autoEnableService, emailService) - v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, projectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) + v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, v1ProjectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) sessionStore, err := dynastore.New(dynastore.Path("/"), dynastore.HTTPOnly(), dynastore.TableName(configFile.SessionStoreTableName), dynastore.DynamoDB(dynamodb.New(awsSession))) if err != nil { @@ -314,10 +315,10 @@ func server(localMode bool) http.Handler { health.Configure(api, healthService) v2Health.Configure(v2API, healthService) template.Configure(api, templateService, eventsService) - v2Template.Configure(v2API, templateService, eventsService) + v2Template.Configure(v2API, templateService, v1ProjectClaGroupService, eventsService) github.Configure(api, configFile.GitHub.ClientID, configFile.GitHub.ClientSecret, configFile.GitHub.AccessToken, sessionStore) signatures.Configure(api, v1SignaturesService, sessionStore, eventsService) - v2Signatures.Configure(v2API, v1ProjectService, v1CLAGroupRepo, v1CompanyService, v1SignaturesService, sessionStore, eventsService, v2SignatureService, projectClaGroupRepo) + v2Signatures.Configure(v2API, v1ProjectService, v1CLAGroupRepo, v1CompanyService, v1SignaturesService, sessionStore, eventsService, v2SignatureService, v1ProjectClaGroupRepo) approval_list.Configure(api, v1ApprovalListService, sessionStore, v1SignaturesService, eventsService) v1Company.Configure(api, v1CompanyService, usersService, companyUserValidation, eventsService) docs.Configure(api) @@ -325,19 +326,19 @@ func server(localMode bool) http.Handler { version.Configure(api, Version, Commit, Branch, BuildDate) v2Version.Configure(v2API, Version, Commit, Branch, BuildDate) events.Configure(api, eventsService) - v2Events.Configure(v2API, eventsService, v1CompanyRepo, projectClaGroupRepo) + v2Events.Configure(v2API, eventsService, v1CompanyRepo, v1ProjectClaGroupRepo) v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) - v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, projectClaGroupRepo) - v2Company.Configure(v2API, v2CompanyService, projectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleV1URL) + v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, v1ProjectClaGroupRepo) + v2Company.Configure(v2API, v2CompanyService, v1ProjectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleV1URL) cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService) - v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, configFile.CorporateConsoleV2URL, projectClaGroupRepo, userRepo) + v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, configFile.CorporateConsoleV2URL, v1ProjectClaGroupRepo, userRepo) sign.Configure(v2API, v2SignService) - cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, projectClaGroupRepo, eventsService) + cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, v1ProjectClaGroupRepo, eventsService) v2GithubActivity.Configure(v2API, v2GithubActivityService) userCreaterMiddleware := func(next http.Handler) http.Handler { diff --git a/cla-backend-go/emails/prefill.go b/cla-backend-go/emails/prefill.go index 507330f15..55c3dc494 100644 --- a/cla-backend-go/emails/prefill.go +++ b/cla-backend-go/emails/prefill.go @@ -53,7 +53,7 @@ func (s *emailTemplateServiceProvider) PrefillV2CLAProjectParams(projectSFIDs [] // keeping a cache so we can safe some of the remote svc calls signedAtFoundationLevelCache := map[string]bool{} for _, pSFID := range projectSFIDs { - projectCLAGroup, err := s.repository.GetClaGroupIDForProject(pSFID) + projectCLAGroup, err := s.repository.GetClaGroupIDForProject(context.Background(), pSFID) if err != nil { return nil, fmt.Errorf("fetching project : %s failed: %v", pSFID, err) } @@ -104,7 +104,7 @@ func (s *emailTemplateServiceProvider) GetCLAGroupTemplateParamsFromCLAGroup(cla } func (s *emailTemplateServiceProvider) getV2CLAGroupTemplateParamsFromProjectSFID(projectSFID string) (CLAGroupTemplateParams, error) { - projectCLAGroup, err := s.repository.GetClaGroupIDForProject(projectSFID) + projectCLAGroup, err := s.repository.GetClaGroupIDForProject(context.Background(), projectSFID) if err != nil { return CLAGroupTemplateParams{}, err } @@ -114,7 +114,7 @@ func (s *emailTemplateServiceProvider) getV2CLAGroupTemplateParamsFromProjectSFI params.CorporateConsole = s.corporateConsoleV2 params.Version = projectCLAGroup.Version - projects, err := s.repository.GetProjectsIdsForClaGroup(projectCLAGroup.ClaGroupID) + projects, err := s.repository.GetProjectsIdsForClaGroup(context.Background(), projectCLAGroup.ClaGroupID) if err != nil { return CLAGroupTemplateParams{}, fmt.Errorf("getProjectsIdsForClaGroup failed : %w", err) } diff --git a/cla-backend-go/events/mockrepo.go b/cla-backend-go/events/mockrepo.go index e4a5def3f..61c647466 100644 --- a/cla-backend-go/events/mockrepo.go +++ b/cla-backend-go/events/mockrepo.go @@ -44,7 +44,7 @@ func (repo *mockRepository) GetClaGroupEvents(claGroupID string, nextKey *string panic("implement me") } -func (repo *mockRepository) GetClaGroupIDForProject(projectSFID string) (*projects_cla_groups.ProjectClaGroup, error) { +func (repo *mockRepository) GetClaGroupIDForProject(ctx context.Context, projectSFID string) (*projects_cla_groups.ProjectClaGroup, error) { return nil, nil } diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 4cbb22885..2eb9fadf9 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -50,7 +50,7 @@ type CombinedRepo interface { GetCompany(ctx context.Context, companyID string) (*models.Company, error) GetUserByUserName(userName string, fullMatch bool) (*models.User, error) GetUser(userID string) (*models.User, error) - GetClaGroupIDForProject(projectSFID string) (*projects_cla_groups.ProjectClaGroup, error) + GetClaGroupIDForProject(ctx context.Context, projectSFID string) (*projects_cla_groups.ProjectClaGroup, error) } type service struct { @@ -207,7 +207,7 @@ func (s *service) loadCLAGroup(ctx context.Context, args *LogEventArgs) error { args.CLAGroupName = claGroupModel.ProjectName args.CLAGroupID = claGroupID } else if args.ProjectSFID != "" { - projectCLAGroupModel, projectCLAGroupErr := s.combinedRepo.GetClaGroupIDForProject(args.ProjectSFID) + projectCLAGroupModel, projectCLAGroupErr := s.combinedRepo.GetClaGroupIDForProject(ctx, args.ProjectSFID) if projectCLAGroupErr != nil || projectCLAGroupModel == nil { log.WithFields(f).WithError(projectCLAGroupErr).Warnf("failed to load project CLA Group mapping by SFID: %s", args.ProjectSFID) return nil diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 3fbdfb722..f434d64af 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -63,7 +63,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, // check if valid cla group id is passed if input.AutoEnabledClaGroupID != "" { - if _, err := s.claRepository.GetCLAGroupNameByID(input.AutoEnabledClaGroupID); err != nil { + if _, err := s.claRepository.GetCLAGroupNameByID(ctx, input.AutoEnabledClaGroupID); err != nil { return nil, err } } @@ -161,7 +161,7 @@ func (s service) GetGithubOrganizationByName(ctx context.Context, githubOrgName func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { // check if valid cla group id is passed if autoEnabledClaGroupID != "" { - if _, err := s.claRepository.GetCLAGroupNameByID(autoEnabledClaGroupID); err != nil { + if _, err := s.claRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { return err } } diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index a98bc3a35..106261197 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -8,8 +8,8 @@ replace github.com/awslabs/aws-lambda-go-api-proxy => github.com/LF-Engineering/ require ( github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 - github.com/LF-Engineering/lfx-kit v0.1.24 - github.com/LF-Engineering/lfx-models v0.6.42 + github.com/LF-Engineering/lfx-kit v0.1.25 + github.com/LF-Engineering/lfx-models v0.6.44 github.com/aws/aws-lambda-go v1.22.0 github.com/aws/aws-sdk-go v1.36.27 github.com/aymerick/raymond v2.0.2+incompatible @@ -48,7 +48,7 @@ require ( github.com/rs/cors v1.7.0 github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa - github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a + github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect github.com/sirupsen/logrus v1.7.0 github.com/spf13/afero v1.3.0 // indirect github.com/spf13/cast v1.3.1 // indirect diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index d094865eb..7e1875a77 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -26,14 +26,10 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOC github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= -github.com/LF-Engineering/lfx-kit v0.1.22 h1:4tE1xTvu5CRWIokOo1waOfuB6vgaCpov5glhkdVzbAs= -github.com/LF-Engineering/lfx-kit v0.1.22/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= -github.com/LF-Engineering/lfx-kit v0.1.24 h1:2lfmBMWWfOwU2XEOSP7keS/CqR5mj30YmJPv6fUiOvM= -github.com/LF-Engineering/lfx-kit v0.1.24/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= -github.com/LF-Engineering/lfx-models v0.6.34 h1:K8al2aTq8nDm3qNmsTNAhZ1uDzfew/UymwbcW9gbDDs= -github.com/LF-Engineering/lfx-models v0.6.34/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= -github.com/LF-Engineering/lfx-models v0.6.42 h1:PfvP/hmcV+OHftNrzclzvwGUv864xyNcp0Kz2HUA5C0= -github.com/LF-Engineering/lfx-models v0.6.42/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= +github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJpEYa80PY= +github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= +github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= +github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= @@ -58,7 +54,6 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= @@ -91,19 +86,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/communitybridge/easycla v1.0.99 h1:PkmkMV7cLH2Q2YNSFiGGmlyrHBXVYdsWMwbXNuMAyqw= -github.com/communitybridge/easycla v1.0.106 h1:NLYUZUZtp9DQ0dHEQkhz9h9EMzLRmuh9udsPZk8oLoQ= -github.com/communitybridge/easycla v1.0.107 h1:dktHAji1yJ1nMEu54z4paPWOM4Q7A9rryc0OCADfAcY= -github.com/communitybridge/easycla v1.0.117 h1:o+rdmcNgZeMQ/N8HV/d5apNIBrkYH7eyM9UUYnEzewo= -github.com/communitybridge/easycla v1.0.118 h1:8yrsOQ+ENUFi4RFl1krRlIxc51lzZNutidR+yy2HwW0= -github.com/communitybridge/easycla v1.0.123 h1:Lh5i/9aajrTYItxNpVCmi9T1yyIfnQIOk0tC2Wtslvk= -github.com/communitybridge/easycla v1.0.133 h1:aJulQGLLRISCMsZcCP4aIE8xGtHoBNm/EmA00n3NYVA= -github.com/communitybridge/easycla v1.0.135 h1:Dvn8jX+7BAnpmA+jvdK0n5ajWP8SoH5vvopt7whZDEU= -github.com/communitybridge/easycla v1.0.145 h1:ikhBSsOeEL2u3/EoyDsufh/j3HkjfFTiXAk1d61GoS8= -github.com/communitybridge/easycla v2.0.10+incompatible h1:6eRJ5fxrMxRZHBkg8piYo+zHTcSowMrP85nZXzp5mpA= -github.com/communitybridge/easycla v2.0.16+incompatible h1:I0hEApDh4IvlwRPyHV1LOsSYlSPbqBsGszjSTHwkdak= -github.com/communitybridge/easycla v2.0.19+incompatible h1:HLaNt3jGDXPh3Au+rW/HKbJNkQf3daboVIrP9G6WYQ4= -github.com/communitybridge/easycla v2.0.29+incompatible h1:kEply0yEyhsflUFDo7DZ0XBpncNPnsdgy6zn6ujkReY= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= @@ -139,7 +121,6 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -195,7 +176,6 @@ github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2e github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.15 h1:2GIefxs9Rx1vCDNghRtypRq+ig8KSLrjHbAYI/gCLCM= github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= github.com/go-openapi/runtime v0.19.19 h1:PCaQSqG0HiCgpekchPrHO9AEc5ZUaAclOUp9T3RSKoQ= github.com/go-openapi/runtime v0.19.19/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= @@ -268,8 +248,6 @@ github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZC github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -283,7 +261,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -294,7 +271,6 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= @@ -326,7 +302,6 @@ github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmI github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -497,7 +472,6 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -610,7 +584,6 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -729,7 +702,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -775,12 +747,10 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -878,7 +848,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 24c9d31dd..9c69d9705 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -373,7 +373,7 @@ func (repo *repo) GetClaGroupByProjectSFID(ctx context.Context, projectSFID stri "tableName": repo.claGroupTable} log.WithFields(f).Debugf("loading project") - claGroupProject, err := repo.projectClaGroupRepo.GetClaGroupIDForProject(projectSFID) + claGroupProject, err := repo.projectClaGroupRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { log.WithFields(f).Warnf("error fetching CLA Group ID for project, error: %v", err) return nil, err diff --git a/cla-backend-go/project/service.go b/cla-backend-go/project/service.go index 570e72639..3e98bca4d 100644 --- a/cla-backend-go/project/service.go +++ b/cla-backend-go/project/service.go @@ -42,25 +42,25 @@ type Service interface { // service type service struct { - repo ProjectRepository - repositoriesRepo repositories.Repository - gerritRepo gerrits.Repository - projectCGRepo projects_cla_groups.Repository - usersRepo users.UserRepository + repo ProjectRepository + repositoriesRepo repositories.Repository + gerritRepo gerrits.Repository + projectCLAGroupRepo projects_cla_groups.Repository + usersRepo users.UserRepository } // NewService returns an instance of the project service -func NewService(projectRepo ProjectRepository, repositoriesRepo repositories.Repository, gerritRepo gerrits.Repository, pcgRepo projects_cla_groups.Repository, usersRepo users.UserRepository) Service { +func NewService(projectRepo ProjectRepository, repositoriesRepo repositories.Repository, gerritRepo gerrits.Repository, projectCLAGroupRepo projects_cla_groups.Repository, usersRepo users.UserRepository) Service { return service{ - repo: projectRepo, - repositoriesRepo: repositoriesRepo, - gerritRepo: gerritRepo, - projectCGRepo: pcgRepo, - usersRepo: usersRepo, + repo: projectRepo, + repositoriesRepo: repositoriesRepo, + gerritRepo: gerritRepo, + projectCLAGroupRepo: projectCLAGroupRepo, + usersRepo: usersRepo, } } -// CreateProject service method +// CreateCLAGroup service method func (s service) CreateCLAGroup(ctx context.Context, claGroupModel *models.ClaGroup) (*models.ClaGroup, error) { return s.repo.CreateCLAGroup(ctx, claGroupModel) } @@ -70,7 +70,7 @@ func (s service) GetCLAGroups(ctx context.Context, params *project.GetProjectsPa return s.repo.GetCLAGroups(ctx, params) } -// GetProjectByID service method +// GetCLAGroupByID service method func (s service) GetCLAGroupByID(ctx context.Context, claGroupID string) (*models.ClaGroup, error) { f := logrus.Fields{ "functionName": "GetCLAGroupByID", @@ -337,7 +337,7 @@ func (s service) SignedAtFoundationLevel(ctx context.Context, foundationSFID str } log.WithFields(f).Debug("querying foundation CLA Group entries...") - entries, pcgErr := s.projectCGRepo.GetProjectsIdsForFoundation(foundationSFID) + entries, pcgErr := s.projectCLAGroupRepo.GetProjectsIdsForFoundation(ctx, foundationSFID) if pcgErr != nil { return false, pcgErr } diff --git a/cla-backend-go/projects_cla_groups/repository.go b/cla-backend-go/projects_cla_groups/repository.go index 26467a4c6..55efa5b9d 100644 --- a/cla-backend-go/projects_cla_groups/repository.go +++ b/cla-backend-go/projects_cla_groups/repository.go @@ -4,6 +4,7 @@ package projects_cla_groups import ( + "context" "errors" "fmt" "strconv" @@ -42,19 +43,19 @@ var ( // Repository provides interface for interacting with project_cla_groups table type Repository interface { - GetClaGroupIDForProject(projectSFID string) (*ProjectClaGroup, error) - GetProjectsIdsForClaGroup(claGroupID string) ([]*ProjectClaGroup, error) - GetProjectsIdsForFoundation(foundationSFID string) ([]*ProjectClaGroup, error) - GetProjectsIdsForAllFoundation() ([]*ProjectClaGroup, error) - AssociateClaGroupWithProject(claGroupID string, projectSFID string, foundationSFID string) error - RemoveProjectAssociatedWithClaGroup(claGroupID string, projectSFIDList []string, all bool) error - GetCLAGroupNameByID(claGroupID string) (string, error) - GetCLAGroup(claGroupID string) (*ProjectClaGroup, error) - - IsExistingFoundationLevelCLAGroup(foundationSFID string) (bool, error) - IsAssociated(projectSFID string, claGroupID string) (bool, error) - UpdateRepositoriesCount(projectSFID string, diff int64, reset bool) error - UpdateClaGroupName(projectSFID string, claGroupName string) error + GetClaGroupIDForProject(ctx context.Context, projectSFID string) (*ProjectClaGroup, error) + GetProjectsIdsForClaGroup(ctx context.Context, claGroupID string) ([]*ProjectClaGroup, error) + GetProjectsIdsForFoundation(ctx context.Context, foundationSFID string) ([]*ProjectClaGroup, error) + GetProjectsIdsForAllFoundation(ctx context.Context) ([]*ProjectClaGroup, error) + AssociateClaGroupWithProject(ctx context.Context, claGroupID string, projectSFID string, foundationSFID string) error + RemoveProjectAssociatedWithClaGroup(ctx context.Context, claGroupID string, projectSFIDList []string, all bool) error + GetCLAGroupNameByID(ctx context.Context, claGroupID string) (string, error) + GetCLAGroup(ctx context.Context, claGroupID string) (*ProjectClaGroup, error) + + IsExistingFoundationLevelCLAGroup(ctx context.Context, foundationSFID string) (bool, error) + IsAssociated(ctx context.Context, projectSFID string, claGroupID string) (bool, error) + UpdateRepositoriesCount(ctx context.Context, projectSFID string, diff int64, reset bool) error + UpdateClaGroupName(ctx context.Context, projectSFID string, claGroupName string) error } type repo struct { @@ -72,11 +73,12 @@ func NewRepository(awsSession *session.Session, stage string) Repository { } } -func (repo *repo) queryClaGroupsProjects(keyCondition expression.KeyConditionBuilder, indexName *string) ([]*ProjectClaGroup, error) { +func (repo *repo) queryClaGroupsProjects(ctx context.Context, keyCondition expression.KeyConditionBuilder, indexName *string) ([]*ProjectClaGroup, error) { f := logrus.Fields{ - "functionName": "project_cla_groups.repository.queryClaGroupsProjects", - "indexName": aws.StringValue(indexName), - "keyCondition": fmt.Sprintf("%+v", keyCondition), + "functionName": "project_cla_groups.repository.queryClaGroupsProjects", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "indexName": aws.StringValue(indexName), + "keyCondition": fmt.Sprintf("%+v", keyCondition), } expr, err := expression.NewBuilder().WithKeyCondition(keyCondition).Build() @@ -123,11 +125,12 @@ func (repo *repo) queryClaGroupsProjects(keyCondition expression.KeyConditionBui } // GetClaGroupIDForProject retrieves the CLA Group ID for the project -func (repo *repo) GetClaGroupIDForProject(projectSFID string) (*ProjectClaGroup, error) { +func (repo *repo) GetClaGroupIDForProject(ctx context.Context, projectSFID string) (*ProjectClaGroup, error) { f := logrus.Fields{ - "functionName": "project_cla_groups.repository.GetClaGroupIDForProject", - "tableName": repo.tableName, - "projectSFID": projectSFID, + "functionName": "project_cla_groups.repository.GetClaGroupIDForProject", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "tableName": repo.tableName, + "projectSFID": projectSFID, } result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ @@ -147,7 +150,7 @@ func (repo *repo) GetClaGroupIDForProject(projectSFID string) (*ProjectClaGroup, if len(result.Item) == 0 { // Query by foundation sfid index returns multiple results log.WithFields(f).Debug("no results querying by project SFID - checking if this is a foundation SFID") - pcgs, foundationErr := repo.GetProjectsIdsForFoundation(projectSFID) + pcgs, foundationErr := repo.GetProjectsIdsForFoundation(ctx, projectSFID) if foundationErr != nil { log.WithFields(f).Warnf("unable to lookup CLA Group associated with project, error: %+v", foundationErr) return nil, err @@ -171,18 +174,23 @@ func (repo *repo) GetClaGroupIDForProject(projectSFID string) (*ProjectClaGroup, return &out, nil } -func (repo *repo) GetProjectsIdsForClaGroup(claGroupID string) ([]*ProjectClaGroup, error) { +func (repo *repo) GetProjectsIdsForClaGroup(ctx context.Context, claGroupID string) ([]*ProjectClaGroup, error) { keyCondition := expression.Key("cla_group_id").Equal(expression.Value(claGroupID)) - return repo.queryClaGroupsProjects(keyCondition, aws.String(CLAGroupIDIndex)) + return repo.queryClaGroupsProjects(ctx, keyCondition, aws.String(CLAGroupIDIndex)) } -func (repo *repo) GetProjectsIdsForFoundation(foundationSFID string) ([]*ProjectClaGroup, error) { +func (repo *repo) GetProjectsIdsForFoundation(ctx context.Context, foundationSFID string) ([]*ProjectClaGroup, error) { keyCondition := expression.Key("foundation_sfid").Equal(expression.Value(foundationSFID)) - return repo.queryClaGroupsProjects(keyCondition, aws.String(FoundationSFIDIndex)) + return repo.queryClaGroupsProjects(ctx, keyCondition, aws.String(FoundationSFIDIndex)) } -func (repo *repo) GetProjectsIdsForAllFoundation() ([]*ProjectClaGroup, error) { - f := logrus.Fields{"functionName": "project_cla_groups.repository.GetProjectsIdsForAllFoundation", "tableName": repo.tableName} +func (repo *repo) GetProjectsIdsForAllFoundation(ctx context.Context) ([]*ProjectClaGroup, error) { + f := logrus.Fields{ + "functionName": "project_cla_groups.repository.GetProjectsIdsForAllFoundation", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "tableName": repo.tableName, + } + scanInput := &dynamodb.ScanInput{ TableName: aws.String(repo.tableName), } @@ -210,9 +218,10 @@ func (repo *repo) GetProjectsIdsForAllFoundation() ([]*ProjectClaGroup, error) { } // AssociateClaGroupWithProject creates entry in db to track cla_group association with project/foundation -func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID string, foundationSFID string) error { +func (repo *repo) AssociateClaGroupWithProject(ctx context.Context, claGroupID string, projectSFID string, foundationSFID string) error { f := logrus.Fields{ "functionName": "project_cla_groups.repository.AssociateClaGroupWithProject", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "projectSFID": projectSFID, "foundationSFID": foundationSFID, @@ -240,7 +249,7 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st } // Lookup the CLA Group name/Project Name - claGroupName, claGroupLookupErr := repo.GetCLAGroupNameByID(claGroupID) + claGroupName, claGroupLookupErr := repo.GetCLAGroupNameByID(ctx, claGroupID) if claGroupLookupErr != nil { claGroupName = NotDefined log.Warnf("unable to lookup CLA Group/Project by ID, error: %+v - using '%s'", @@ -282,7 +291,7 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st } log.WithFields(f).Debug("Locating records with matching projectSFID...") - existingRecord, lookupErr := repo.GetClaGroupIDForProject(projectSFID) + existingRecord, lookupErr := repo.GetClaGroupIDForProject(ctx, projectSFID) if lookupErr != nil { log.WithFields(f).Warnf("cannot lookup record by projectSFID, error: %+v", lookupErr) } @@ -313,14 +322,15 @@ func (repo *repo) AssociateClaGroupWithProject(claGroupID string, projectSFID st } // RemoveProjectAssociatedWithClaGroup removes all associated project with cla_group -func (repo *repo) RemoveProjectAssociatedWithClaGroup(claGroupID string, projectSFIDList []string, all bool) error { +func (repo *repo) RemoveProjectAssociatedWithClaGroup(ctx context.Context, claGroupID string, projectSFIDList []string, all bool) error { f := logrus.Fields{ "functionName": "project_cla_groups.repository.RemoveProjectAssociatedWithClaGroup", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "projectSFIDList": projectSFIDList, "all": all, } - list, err := repo.GetProjectsIdsForClaGroup(claGroupID) + list, err := repo.GetProjectsIdsForClaGroup(ctx, claGroupID) if err != nil { log.WithFields(f).Warnf("unable to fetch projects IDs for CLA Group, error: %+v", err) return err @@ -355,7 +365,7 @@ func (repo *repo) RemoveProjectAssociatedWithClaGroup(claGroupID string, project } // GetCLAGroupNameByID helper function to fetch the CLA Group name -func (repo *repo) GetCLAGroupNameByID(claGroupID string) (string, error) { +func (repo *repo) GetCLAGroupNameByID(ctx context.Context, claGroupID string) (string, error) { tableName := fmt.Sprintf("cla-%s-projects", repo.stage) result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ TableName: aws.String(tableName), @@ -382,7 +392,7 @@ func (repo *repo) GetCLAGroupNameByID(claGroupID string) (string, error) { } // GetCLAGroup helper function to fetch the CLA Group -func (repo *repo) GetCLAGroup(claGroupID string) (*ProjectClaGroup, error) { +func (repo *repo) GetCLAGroup(ctx context.Context, claGroupID string) (*ProjectClaGroup, error) { tableName := fmt.Sprintf("cla-%s-projects", repo.stage) result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ TableName: aws.String(tableName), @@ -409,16 +419,17 @@ func (repo *repo) GetCLAGroup(claGroupID string) (*ProjectClaGroup, error) { } // UpdateRepositoriesCount updates the repositories count -func (repo *repo) UpdateRepositoriesCount(projectSFID string, diff int64, reset bool) error { +func (repo *repo) UpdateRepositoriesCount(ctx context.Context, projectSFID string, diff int64, reset bool) error { f := logrus.Fields{ - "functionName": "project_cla_groups.repository.UpdateRepositoriesCount", - "projectSFID": projectSFID, - "diff": diff, - "reset": reset, + "functionName": "project_cla_groups.repository.UpdateRepositoriesCount", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "diff": diff, + "reset": reset, } // Check to see if we have an existing record - existingProjectCLAGroupMapping, err := repo.GetClaGroupIDForProject(projectSFID) + existingProjectCLAGroupMapping, err := repo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("unable to lookup existing project cla group mapping") return err @@ -472,15 +483,16 @@ func (repo *repo) UpdateRepositoriesCount(projectSFID string, diff int64, reset } // UpdateClaGroupName updates cla group name for given projectSFID -func (repo *repo) UpdateClaGroupName(projectSFID string, claGroupName string) error { +func (repo *repo) UpdateClaGroupName(ctx context.Context, projectSFID string, claGroupName string) error { f := logrus.Fields{ - "functionName": "project_cla_groups.repository.UpdateClaGroupName", - "projectSFID": projectSFID, - "claGroupName": claGroupName, + "functionName": "project_cla_groups.repository.UpdateClaGroupName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "claGroupName": claGroupName, } // Check to see if we have an existing record - existingProjectCLAGroupMapping, err := repo.GetClaGroupIDForProject(projectSFID) + existingProjectCLAGroupMapping, err := repo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("unable to lookup existing project cla group mapping") return err @@ -528,8 +540,8 @@ func (repo *repo) UpdateClaGroupName(projectSFID string, claGroupName string) er // IsExistingFoundationLevelCLAGroup is a query helper function to determine if the // specified foundation SFID has an entry in the mapping table to signify that // it's a foundation level CLA Group (foundationSFID == projectSFID) -func (repo *repo) IsExistingFoundationLevelCLAGroup(foundationSFID string) (bool, error) { - projectCLAGroupModels, err := repo.GetProjectsIdsForFoundation(foundationSFID) +func (repo *repo) IsExistingFoundationLevelCLAGroup(ctx context.Context, foundationSFID string) (bool, error) { + projectCLAGroupModels, err := repo.GetProjectsIdsForFoundation(ctx, foundationSFID) if err != nil { return false, err } @@ -543,8 +555,8 @@ func (repo *repo) IsExistingFoundationLevelCLAGroup(foundationSFID string) (bool return false, nil } -func (repo *repo) IsAssociated(projectSFID string, claGroupID string) (bool, error) { - pmlist, err := repo.GetProjectsIdsForClaGroup(claGroupID) +func (repo *repo) IsAssociated(ctx context.Context, projectSFID string, claGroupID string) (bool, error) { + pmlist, err := repo.GetProjectsIdsForClaGroup(ctx, claGroupID) if err != nil { return false, err } diff --git a/cla-backend-go/projects_cla_groups/service.go b/cla-backend-go/projects_cla_groups/service.go new file mode 100644 index 000000000..d98f53b7f --- /dev/null +++ b/cla-backend-go/projects_cla_groups/service.go @@ -0,0 +1,95 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package projects_cla_groups + +import "context" + +// ProjectCLAGroupsService interface +type ProjectCLAGroupsService interface { + GetClaGroupIDForProject(ctx context.Context, projectSFID string) (*ProjectClaGroup, error) + GetProjectsIdsForClaGroup(ctx context.Context, claGroupID string) ([]*ProjectClaGroup, error) + GetProjectsIdsForFoundation(ctx context.Context, foundationSFID string) ([]*ProjectClaGroup, error) + GetProjectsIdsForAllFoundation() ([]*ProjectClaGroup, error) + AssociateClaGroupWithProject(ctx context.Context, claGroupID string, projectSFID string, foundationSFID string) error + RemoveProjectAssociatedWithClaGroup(ctx context.Context, claGroupID string, projectSFIDList []string, all bool) error + GetCLAGroupNameByID(ctx context.Context, claGroupID string) (string, error) + GetCLAGroup(ctx context.Context, claGroupID string) (*ProjectClaGroup, error) + + IsExistingFoundationLevelCLAGroup(ctx context.Context, foundationSFID string) (bool, error) + IsAssociated(ctx context.Context, projectSFID string, claGroupID string) (bool, error) + UpdateRepositoriesCount(ctx context.Context, projectSFID string, diff int64, reset bool) error + UpdateClaGroupName(ctx context.Context, projectSFID string, claGroupName string) error +} + +// Service model +type Service struct { + repo Repository +} + +// NewService creates a new whitelist service +func NewService(repo Repository) Service { + return Service{ + repo, + } +} + +// GetClaGroupIDForProject service method +func (s Service) GetClaGroupIDForProject(ctx context.Context, projectSFID string) (*ProjectClaGroup, error) { + return s.repo.GetClaGroupIDForProject(ctx, projectSFID) +} + +// GetProjectsIdsForClaGroup service method +func (s Service) GetProjectsIdsForClaGroup(ctx context.Context, claGroupID string) ([]*ProjectClaGroup, error) { + return s.repo.GetProjectsIdsForClaGroup(ctx, claGroupID) +} + +// GetProjectsIdsForFoundation service method +func (s Service) GetProjectsIdsForFoundation(ctx context.Context, foundationSFID string) ([]*ProjectClaGroup, error) { + return s.repo.GetProjectsIdsForFoundation(ctx, foundationSFID) +} + +// GetProjectsIdsForAllFoundation service method +func (s Service) GetProjectsIdsForAllFoundation(ctx context.Context) ([]*ProjectClaGroup, error) { + return s.repo.GetProjectsIdsForAllFoundation(ctx) +} + +// AssociateClaGroupWithProject service method +func (s Service) AssociateClaGroupWithProject(ctx context.Context, claGroupID string, projectSFID string, foundationSFID string) error { + return s.repo.AssociateClaGroupWithProject(ctx, claGroupID, projectSFID, foundationSFID) +} + +// RemoveProjectAssociatedWithClaGroup service method +func (s Service) RemoveProjectAssociatedWithClaGroup(ctx context.Context, claGroupID string, projectSFIDList []string, all bool) error { + return s.repo.RemoveProjectAssociatedWithClaGroup(ctx, claGroupID, projectSFIDList, all) +} + +// GetCLAGroupNameByID service method +func (s Service) GetCLAGroupNameByID(ctx context.Context, claGroupID string) (string, error) { + return s.repo.GetCLAGroupNameByID(ctx, claGroupID) +} + +// GetCLAGroup service method +func (s Service) GetCLAGroup(ctx context.Context, claGroupID string) (*ProjectClaGroup, error) { + return s.repo.GetCLAGroup(ctx, claGroupID) +} + +// IsExistingFoundationLevelCLAGroup service method +func (s Service) IsExistingFoundationLevelCLAGroup(ctx context.Context, foundationSFID string) (bool, error) { + return s.repo.IsExistingFoundationLevelCLAGroup(ctx, foundationSFID) +} + +// IsAssociated service method +func (s Service) IsAssociated(ctx context.Context, projectSFID string, claGroupID string) (bool, error) { + return s.repo.IsAssociated(ctx, projectSFID, claGroupID) +} + +// UpdateRepositoriesCount service method +func (s Service) UpdateRepositoriesCount(ctx context.Context, projectSFID string, diff int64, reset bool) error { + return s.repo.UpdateRepositoriesCount(ctx, projectSFID, diff, reset) +} + +// UpdateClaGroupName service method +func (s Service) UpdateClaGroupName(ctx context.Context, projectSFID string, claGroupName string) error { + return s.repo.UpdateClaGroupName(ctx, projectSFID, claGroupName) +} diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index d6ab26a50..1f5160b2e 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -9,7 +9,9 @@ import ( "fmt" "io" "io/ioutil" + "strconv" "strings" + "time" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" @@ -42,16 +44,16 @@ type Service interface { type service struct { stage string // The AWS stage (dev, staging, prod) templateRepo Repository - docraptorClient docraptor.Client + docRaptorClient docraptor.Client s3Client *s3manager.Uploader } // NewService API call -func NewService(stage string, templateRepo Repository, docraptorClient docraptor.Client, awsSession *session.Session) service { +func NewService(stage string, templateRepo Repository, docRaptorClient docraptor.Client, awsSession *session.Session) service { return service{ stage: stage, templateRepo: templateRepo, - docraptorClient: docraptorClient, + docRaptorClient: docRaptorClient, s3Client: s3manager.NewUploader(awsSession), } } @@ -120,7 +122,7 @@ func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode return nil, errors.New("invalid value of template_for") } - pdf, err := s.docraptorClient.CreatePDF(templateHTML, templateFor) + pdf, err := s.docRaptorClient.CreatePDF(templateHTML, templateFor) if err != nil { return nil, err } @@ -133,7 +135,7 @@ func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode return ioutil.ReadAll(pdf) } -// CreateCLAGroupTemplate +// CreateCLAGroupTemplate service method func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) { f := logrus.Fields{ "functionName": "v1.template.service.CreateCLAGroupTemplate", @@ -149,8 +151,6 @@ func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, return models.TemplatePdfs{}, err } - // Verify the caller is authorized for the project that owns this CLA Group - // Get Template template, err := s.templateRepo.GetTemplate(claGroupFields.TemplateID) if err != nil { @@ -181,7 +181,7 @@ func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, // Invoke the go routine - any errors will be handled below eg.Go(func() error { log.WithFields(f).Debugf("Creating PDF for %s", claTypeICLA) - iclaPdf, iclaErr := s.docraptorClient.CreatePDF(iclaTemplateHTML, claTypeICLA) + iclaPdf, iclaErr := s.docRaptorClient.CreatePDF(iclaTemplateHTML, claTypeICLA) if iclaErr != nil { log.WithFields(f).WithError(iclaErr).Warn("Problem generating ICLA template via docraptor client - returning empty template PDFs") return err @@ -208,7 +208,7 @@ func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, // Invoke the go routine - any errors will be handled below eg.Go(func() error { log.WithFields(f).Debugf("Creating PDF for %s", claTypeCCLA) - cclaPdf, cclaErr := s.docraptorClient.CreatePDF(cclaTemplateHTML, claTypeCCLA) + cclaPdf, cclaErr := s.docRaptorClient.CreatePDF(cclaTemplateHTML, claTypeCCLA) if cclaErr != nil { log.WithFields(f).WithError(cclaErr).Warn("Problem generating CCLA template via docraptor client - returning empty template PDFs") return err @@ -317,7 +317,7 @@ func (s service) GetCLATemplatePreview(ctx context.Context, claGroupID, claType return nil, err } - doc := claGroupDocuments[0] + doc := getLatestDocument(ctx, claGroupDocuments) pdfS3URL := doc.DocumentS3URL if pdfS3URL == "" { err = fmt.Errorf("s3 url is empty for groupID : %s and document %s", claGroupID, doc.DocumentFileID) @@ -357,7 +357,82 @@ func (s service) GetCLATemplatePreview(ctx context.Context, claGroupID, claType return b, nil } -// InjectProjectInformationIntoTemplate +func getLatestDocument(ctx context.Context, documents []models.ClaGroupDocument) *models.ClaGroupDocument { + f := logrus.Fields{ + "functionName": "v1.template.service.getLatestDocument", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + var latestDocument *models.ClaGroupDocument + var latestMajorVersion = 0 + var latestMinorVersion = 0 + var latestDateTime time.Time + for _, currentDocument := range documents { + if latestDocument == nil { + latestDocument = ¤tDocument // nolint + // Grab and save the major version + major, convertErr := strconv.Atoi(latestDocument.DocumentMajorVersion) + if convertErr != nil { + log.WithFields(f).WithError(convertErr).Warnf("problem converting document major version to int: %s", latestDocument.DocumentMajorVersion) + major = 0 + } + latestMajorVersion = major + + // Grab and save the major version + minor, convertErr := strconv.Atoi(latestDocument.DocumentMinorVersion) + if convertErr != nil { + log.WithFields(f).WithError(convertErr).Warnf("problem converting document minor version to int: %s", latestDocument.DocumentMinorVersion) + minor = 0 + } + latestMinorVersion = minor + + dateTime, dateTimeErr := utils.ParseDateTime(latestDocument.DocumentCreationDate) + if dateTimeErr != nil { + log.WithFields(f).WithError(dateTimeErr).Warnf("problem converting document creation date to time object: %s", latestDocument.DocumentCreationDate) + } + latestDateTime = dateTime + + continue + } + + // Grab and save the major version + major, convertErr := strconv.Atoi(currentDocument.DocumentMajorVersion) + if convertErr != nil { + log.WithFields(f).WithError(convertErr).Warnf("problem converting document major version to int: %s", currentDocument.DocumentMajorVersion) + major = 0 + } + + // Grab and save the major version + minor, convertErr := strconv.Atoi(currentDocument.DocumentMinorVersion) + if convertErr != nil { + log.WithFields(f).WithError(convertErr).Warnf("problem converting document minor version to int: %s", currentDocument.DocumentMinorVersion) + minor = 0 + } + + dateTime, dateTimeErr := utils.ParseDateTime(currentDocument.DocumentCreationDate) + if dateTimeErr != nil { + log.WithFields(f).WithError(dateTimeErr).Warnf("problem converting document creation date to time object: %s", currentDocument.DocumentCreationDate) + } + + if major > latestMajorVersion { + latestDocument = ¤tDocument // nolint + continue + } + + if minor > latestMinorVersion { + latestDocument = ¤tDocument // nolint + continue + } + + if dateTime.After(latestDateTime) { + latestDocument = ¤tDocument // nolint + continue + } + } + + return latestDocument +} + +// InjectProjectInformationIntoTemplate service function func (s service) InjectProjectInformationIntoTemplate(template models.Template, metaFields []*models.MetaField) (string, string, error) { f := logrus.Fields{ "functionName": "v1.template.service.InjectProjectInformationIntoTemplate", diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index d07051c75..288044ae9 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -128,7 +128,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P utils.ErrorResponseBadRequest(reqID, "no new values passed, nothing to change, aborting.")) } - projectCLAGroupModels, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + projectCLAGroupModels, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, params.ClaGroupID) if projectCLAGroupErr != nil { msg := fmt.Sprintf("unable to load the Project to CLA Group mappings for CLA Group: %s - is this CLA Group configured?", params.ClaGroupID) log.WithFields(f).Warn(msg) @@ -531,7 +531,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, pare log.WithFields(f).Debugf("user does not have access any of the provided project SFID: %s", projectSFIDs) log.WithFields(f).Debug("user doesn't have direct access to the parentProjectSFID or the provided projects SFIDs - loading CLA Group from project id...") - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(parentProjectSFID) + projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(ctx, parentProjectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") return false @@ -555,7 +555,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, pare // Lookup the other project IDs for the CLA Group log.WithFields(f).Debug("looking up other projects associated with the CLA Group...") - projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(projectCLAGroupModel.ClaGroupID) + projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, projectCLAGroupModel.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project cla group mappings by CLA Group ID - returning false") return false diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 2506e7897..8e8189363 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -87,7 +87,7 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat // Look up any existing configuration with this foundation SFID in our database... log.WithFields(f).Debug("loading existing project IDs by foundation SFID...") - claGroupProjectModels, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(foundationSFID) + claGroupProjectModels, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, foundationSFID) if lookupErr != nil { log.WithFields(f).Warnf("problem looking up foundation level CLA group using foundation ID: %s, error: %+v", foundationSFID, lookupErr) return false, lookupErr @@ -249,7 +249,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI } // check if projects are not already enabled - enabledProjects, err := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(foundationSFID) + enabledProjects, err := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, foundationSFID) if err != nil { return err } @@ -357,7 +357,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS } // check if projects are already enrolled/enabled - enabledProjects, err := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(foundationSFID) + enabledProjects, err := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, foundationSFID) if err != nil { return err } @@ -407,11 +407,11 @@ func (s *service) AssociateCLAGroupWithProjects(ctx context.Context, request *As go func(projectSFID, parentProjectSFID, claGroupID string) { defer wg.Done() log.WithFields(f).Debugf("associating cla_group with project: %s", projectSFID) - err := s.projectsClaGroupsRepo.AssociateClaGroupWithProject(claGroupID, projectSFID, parentProjectSFID) + err := s.projectsClaGroupsRepo.AssociateClaGroupWithProject(ctx, claGroupID, projectSFID, parentProjectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("associating cla_group with project: %s failed", projectSFID) log.WithFields(f).Debug("deleting stale entries from cla_group project association") - deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(claGroupID, request.ProjectSFIDList, false) + deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(ctx, claGroupID, request.ProjectSFIDList, false) if deleteErr != nil { log.WithFields(f).WithError(deleteErr).Warn("deleting stale entries from cla_group project association failed") } @@ -454,7 +454,7 @@ func (s *service) UnassociateCLAGroupWithProjects(ctx context.Context, request * "projectSFIDList": strings.Join(request.ProjectSFIDList, ","), } - deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(request.CLAGroupID, request.ProjectSFIDList, false) + deleteErr := s.projectsClaGroupsRepo.RemoveProjectAssociatedWithClaGroup(ctx, request.CLAGroupID, request.ProjectSFIDList, false) if deleteErr != nil { log.WithFields(f).Warnf("problem disassociating projects with CLA Group, error: %+v", deleteErr) return deleteErr diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index d5aa44c65..aef0ee9ec 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -193,7 +193,7 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input } // Build the response model - subProjectList, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(claGroup.ProjectID) + subProjectList, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, claGroup.ProjectID) if err != nil { return nil, err } @@ -289,7 +289,7 @@ func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGr } // Load the project IDs for this CLA Group - subProjectList, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(claGroupModel.ProjectID) + subProjectList, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, claGroupModel.ProjectID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem getting project IDs for CLA Group") return nil, err @@ -530,7 +530,7 @@ func (s *service) buildClaGroupSummaryResponseModel(ctx context.Context, f logru } // How many SF projects are associated with this CLA Group? - cgprojects, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(v1ClaGroup.ProjectID) + cgprojects, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, v1ClaGroup.ProjectID) if err != nil { return nil, &utils.ProjectCLAGroupMappingNotFound{CLAGroupID: v1ClaGroup.ProjectID, Err: err} } @@ -566,7 +566,7 @@ func (s *service) buildClaGroupSummaryResponseModel(ctx context.Context, f logru func (s *service) appendCLAGroupsForFoundation(ctx context.Context, f logrus.Fields, projectOrFoundationSFID string, v1ClaGroups *v1Models.ClaGroups) error { log.WithFields(f).Debug("found 'project group' in platform project service. Locating CLA Groups for foundation...") - projectCLAGroupMappings, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectOrFoundationSFID) + projectCLAGroupMappings, lookupErr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, projectOrFoundationSFID) if lookupErr != nil { log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) return &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} @@ -640,7 +640,7 @@ func (s *service) appendCLAGroupsForProject(ctx context.Context, f logrus.Fields } log.WithFields(f).Debug("locating CLA Group mapping...") - projectCLAGroup, lookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(projectOrFoundationSFID) + projectCLAGroup, lookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, projectOrFoundationSFID) if lookupErr != nil { log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) return "", "", &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} @@ -679,9 +679,9 @@ func (s *service) ListAllFoundationClaGroups(ctx context.Context, foundationID * var out []*projects_cla_groups.ProjectClaGroup var err error if foundationID != nil { - out, err = s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(*foundationID) + out, err = s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, utils.StringValue(foundationID)) } else { - out, err = s.projectsClaGroupsRepo.GetProjectsIdsForAllFoundation() + out, err = s.projectsClaGroupsRepo.GetProjectsIdsForAllFoundation(ctx) } if err != nil { return nil, err @@ -708,7 +708,7 @@ func (s *service) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.Cl oscClient := organization_service.GetClient() // Get a list of project CLA Group entries - need to know which SF Projects we're dealing with... - projectCLAGroupEntries, projErr := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(claGroupModel.ProjectID) + projectCLAGroupEntries, projErr := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, claGroupModel.ProjectID) if projErr != nil { log.WithFields(f).Warnf("unable to fetch project IDs for CLA Group, error: %+v", projErr) return projErr diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index a86b48f58..8cfd93bca 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -68,7 +68,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C } log.WithFields(f).Debug("looking up CLA Group for projectSFID...") - cginfo, err := projectClaGroupRepo.GetClaGroupIDForProject(params.ProjectSFID) + cginfo, err := projectClaGroupRepo.GetClaGroupIDForProject(ctx, params.ProjectSFID) if err != nil { if err == projects_cla_groups.ErrProjectNotAssociatedWithClaGroup { msg := fmt.Sprintf("no CLA Group associated with this project: %s", params.ProjectSFID) @@ -119,7 +119,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C return cla_manager.NewDeleteCLAManagerBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - cginfo, err := projectClaGroupRepo.GetClaGroupIDForProject(params.ProjectSFID) + cginfo, err := projectClaGroupRepo.GetClaGroupIDForProject(ctx, params.ProjectSFID) if err != nil { msg := fmt.Sprintf("no CLA Group associated with this project: %s", params.ProjectSFID) log.WithFields(f).WithError(err).Warn(msg) @@ -190,7 +190,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C log.WithFields(f).Debugf("processing CLA Manager Designee by group request") log.WithFields(f).Debugf("getting project IDs for CLA group") - projectCLAGroups, getErr := projectClaGroupRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + projectCLAGroups, getErr := projectClaGroupRepo.GetProjectsIdsForClaGroup(ctx, params.ClaGroupID) if getErr != nil { msg := fmt.Sprintf("error getting SF projects for claGroup: %s ", params.ClaGroupID) log.WithFields(f).WithError(getErr).Warn(msg) diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index aa755b1d2..c8e6e5a25 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -446,7 +446,7 @@ func (s *service) IsCLAManagerDesignee(ctx context.Context, companySFID, claGrou } log.WithFields(f).Debugf("Getting project sf mappings for claGroupID: %s ", claGroupID) - pcgs, pcgErr := s.projectCGRepo.GetProjectsIdsForClaGroup(claGroupID) + pcgs, pcgErr := s.projectCGRepo.GetProjectsIdsForClaGroup(ctx, claGroupID) if pcgErr != nil { log.WithFields(f).Warnf("Problem getting mappings for claGroup: %s , error: %+v ", claGroupID, pcgErr) return nil, pcgErr @@ -890,7 +890,7 @@ func (s *service) InviteCompanyAdmin(ctx context.Context, contactAdmin bool, com // Get project cla Group records log.WithFields(f).Debugf("Getting SalesForce Projects for claGroup: %s ", projectID) - projectCLAGroups, getErr := s.projectCGRepo.GetProjectsIdsForClaGroup(projectID) + projectCLAGroups, getErr := s.projectCGRepo.GetProjectsIdsForClaGroup(ctx, projectID) if getErr != nil { msg := fmt.Sprintf("Error getting SF projects for claGroup: %s ", projectID) log.Debug(msg) @@ -1191,7 +1191,7 @@ func (s *service) NotifyCLAManagers(ctx context.Context, notifyCLAManagers *mode // Get mappings var projectSFIDs []string - pcgs, pcgErr := s.projectCGRepo.GetProjectsIdsForClaGroup(notifyCLAManagers.ClaGroupID) + pcgs, pcgErr := s.projectCGRepo.GetProjectsIdsForClaGroup(ctx, notifyCLAManagers.ClaGroupID) if pcgErr != nil { log.WithFields(f).Warnf("problem getting cla_group_mappings by claGroupID: %s ", notifyCLAManagers.ClaGroupID) return pcgErr diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 9a6b1bf8f..408021e2b 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -761,7 +761,7 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // other projects or the parent project group/foundation log.WithFields(f).Debug("user doesn't have direct access to the project only, project + organization, or organization only - loading CLA Group from project id...") - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") return false @@ -798,7 +798,7 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // Lookup the other project IDs associated with this CLA Group log.WithFields(f).Debug("looking up other projects associated with the CLA Group...") - projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(projectCLAGroupModel.ClaGroupID) + projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, projectCLAGroupModel.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project cla group mappings by CLA Group ID - returning false") return false diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 1e44f218d..63b0547c3 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1194,20 +1194,20 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, proj // Determine query index (foundation or project) if !utils.IsProjectCategory(projectDetails, parentProjectDetails) { // get all projects for all cla group under foundation - allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForFoundation(projectSFID) + allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForFoundation(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to get project IDs for foundation SFID: %s", projectSFID) return nil, err } } else { // get cla group id from project - projectMapping, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + projectMapping, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if perr != nil { log.WithFields(f).WithError(perr).Warnf("unable to get CLA group IDs for project SFID: %s", projectSFID) return nil, err } // get all projects for that cla group - allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForClaGroup(projectMapping.ClaGroupID) + allProjectMapping, err = s.projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, projectMapping.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to get project IDs for CLA Group: %s", projectMapping.ClaGroupID) return nil, err @@ -1609,7 +1609,7 @@ func (s *service) getCompanyAndClaGroup(ctx context.Context, companyID, projectS t := time.Now() var pm *projects_cla_groups.ProjectClaGroup log.WithFields(f).Debugf("loading CLA Group by project SFID: %s", projectSFID) - pm, projectErr := s.projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + pm, projectErr := s.projectClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if projectErr != nil { log.WithFields(f).Debugf("cla group mapping not found for projectSFID %s", projectSFID) // Return the result through the channel diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index c7bc874bd..79a7fabac 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -65,16 +65,16 @@ type autoEnableServiceProvider struct { } func (a *autoEnableServiceProvider) CreateAutoEnabledRepository(repo *github.Repository) (*models.GithubRepository, error) { + ctx := utils.NewContext() repositoryFullName := *repo.FullName repositoryExternalID := strconv.FormatInt(*repo.ID, 10) - f := logrus.Fields{ "functionName": "handleRepositoryAddedAction", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryFullName": repositoryFullName, } organizationName := strings.Split(repositoryFullName, "/")[0] - ctx := context.Background() orgModel, err := a.githubOrgRepo.GetGithubOrganization(ctx, organizationName) if err != nil { log.Warnf("fetching github org failed : %v", err) @@ -106,7 +106,7 @@ func (a *autoEnableServiceProvider) CreateAutoEnabledRepository(repo *github.Rep return nil, listErr } } - claGroupModel, err := a.claRepository.GetCLAGroup(claGroupID) + claGroupModel, err := a.claRepository.GetCLAGroup(ctx, claGroupID) if err != nil { log.Warnf("fetching the cla group for cla group id : %s failed : %v", claGroupID, err) return nil, err diff --git a/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go b/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go index f83ef6d99..9a90182ec 100644 --- a/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go +++ b/cla-backend-go/v2/dynamo_events/cla_groups_db_handler.go @@ -7,18 +7,21 @@ import ( "github.com/aws/aws-lambda-go/events" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" ) func (s *service) ProcessCLAGroupUpdateEvents(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "ProcessCLAGroupUpdateEvents", - "eventID": event.EventID, - "eventName": event.EventName, - "eventSource": event.EventSource, - "event": event, - "newImage": event.Change.NewImage, - "oldImage": event.Change.OldImage, + "functionName": "ProcessCLAGroupUpdateEvents", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, + "event": event, + "newImage": event.Change.NewImage, + "oldImage": event.Change.OldImage, } log.WithFields(f).Debug("processing event") @@ -52,14 +55,14 @@ func (s *service) ProcessCLAGroupUpdateEvents(event events.DynamoDBEventRecord) } if oldProject.ProjectName != updatedProject.ProjectName { - claProjects, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(updatedProject.ProjectID) + claProjects, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, updatedProject.ProjectID) if err != nil { log.WithFields(f).Warnf("unabled to update cla group name : %v", err) return nil } for _, claProject := range claProjects { - if err := s.projectsClaGroupRepo.UpdateClaGroupName(claProject.ProjectSFID, updatedProject.ProjectName); err != nil { + if err := s.projectsClaGroupRepo.UpdateClaGroupName(ctx, claProject.ProjectSFID, updatedProject.ProjectName); err != nil { log.WithFields(f).Warnf("updating cla project : %s with name : %s failed : %v", claProject.ProjectSFID, updatedProject.ProjectName, err) return nil } diff --git a/cla-backend-go/v2/dynamo_events/cla_manager.go b/cla-backend-go/v2/dynamo_events/cla_manager.go index 917e418cb..bc17275c9 100644 --- a/cla-backend-go/v2/dynamo_events/cla_manager.go +++ b/cla-backend-go/v2/dynamo_events/cla_manager.go @@ -19,7 +19,7 @@ import ( "github.com/sirupsen/logrus" ) -// SetInitialCLAManagerACSPermissions +// SetInitialCLAManagerACSPermissions establishes the initial CLA manager permissions func (s *service) SetInitialCLAManagerACSPermissions(ctx context.Context, signatureID string) error { f := logrus.Fields{ "functionName": "SetInitialCLAManagerACSPermissions", @@ -124,7 +124,7 @@ func (s *service) SetInitialCLAManagerACSPermissions(ctx context.Context, signat // fetch list of projects under cla group log.WithFields(f).Debug("locating SF projects associated with the CLA Group...") - projectList, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(sig.ProjectID) + projectList, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, sig.ProjectID) if err != nil { log.WithFields(f).Warnf("unable to fetch list of projects associated with CLA Group: %s, error: %+v", sig.ProjectID, err) diff --git a/cla-backend-go/v2/dynamo_events/events.go b/cla-backend-go/v2/dynamo_events/events.go index 094aa24a3..6fd6b910e 100644 --- a/cla-backend-go/v2/dynamo_events/events.go +++ b/cla-backend-go/v2/dynamo_events/events.go @@ -34,7 +34,7 @@ func (s *service) EventAddedEvent(event events.DynamoDBEventRecord) error { } else { companySFID = companyModel.CompanyExternalID } - pmList, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(newEvent.EventProjectID) + pmList, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, newEvent.EventProjectID) if err != nil || len(pmList) == 0 { log.WithFields(f).Error("unable to get project mapping detail", err) } else { diff --git a/cla-backend-go/v2/dynamo_events/github_repository.go b/cla-backend-go/v2/dynamo_events/github_repository.go index abc27f7d7..068e93284 100644 --- a/cla-backend-go/v2/dynamo_events/github_repository.go +++ b/cla-backend-go/v2/dynamo_events/github_repository.go @@ -16,7 +16,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" ) -// GithubRepoModifyEvent github repository modify event +// GithubRepoModifyAddEvent github repository modify add event func (s *service) GithubRepoModifyAddEvent(event events.DynamoDBEventRecord) error { ctx := utils.NewContext() f := logrus.Fields{ @@ -201,7 +201,7 @@ func (s *service) setRepositoryCount(ctx context.Context, claGroupID string, par // Update projects-cla-group table log.WithFields(f).Debugf("Updating the projects-cla-groups-table for projectSFID: %s ", projectSFID) - pcgErr := s.projectsClaGroupRepo.UpdateRepositoriesCount(projectSFID, int64(repoCount), true) + pcgErr := s.projectsClaGroupRepo.UpdateRepositoriesCount(ctx, projectSFID, int64(repoCount), true) if pcgErr != nil { log.WithFields(f).WithError(updateErr).Debugf("Failed to set repositories_count for project: %s ", projectSFID) return pcgErr diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index a2754e384..a7b25a612 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -378,7 +378,7 @@ func (s *service) addCLAManagerDesigneePermissions(ctx context.Context, claGroup } else { // Signed at Project level Use case - pcgs, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(claGroupID) + pcgs, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, claGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem getting project cla Groups for claGroupID: %s", claGroupID) return err diff --git a/cla-backend-go/v2/dynamo_events/signatures.go b/cla-backend-go/v2/dynamo_events/signatures.go index a83513209..a7faff862 100644 --- a/cla-backend-go/v2/dynamo_events/signatures.go +++ b/cla-backend-go/v2/dynamo_events/signatures.go @@ -186,7 +186,7 @@ func (s *service) SignatureSignedEvent(event events.DynamoDBEventRecord) error { // Load the list of SF projects associated with this CLA Group log.WithFields(f).Debugf("querying SF projects for CLA Group: %s", newSignature.SignatureProjectID) - projectCLAGroups, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(newSignature.SignatureProjectID) + projectCLAGroups, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, newSignature.SignatureProjectID) log.WithFields(f).Debugf("found %d SF projects for CLA Group: %s", len(projectCLAGroups), newSignature.SignatureProjectID) // Only proceed if we have one or more SF projects - otherwise, we can't assign and cleanup/adjust roles @@ -421,7 +421,7 @@ func (s *service) assignContributor(ctx context.Context, newSignature Signature, } // Load the list of SF projects associated with this CLA Group log.WithFields(f).Debugf("querying SF projects for CLA Group: %s", newSignature.SignatureProjectID) - projectCLAGroups, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(newSignature.SignatureProjectID) + projectCLAGroups, err := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, newSignature.SignatureProjectID) log.WithFields(f).Debugf("found %d SF projects for CLA Group: %s", len(projectCLAGroups), newSignature.SignatureProjectID) if err != nil { @@ -483,7 +483,7 @@ func (s *service) updateCLAManagerPermissions(signature Signature, managers []st return orgErr } - projectCLAGroups, pcgErr := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(signature.SignatureProjectID) + projectCLAGroups, pcgErr := s.projectsClaGroupRepo.GetProjectsIdsForClaGroup(ctx, signature.SignatureProjectID) if pcgErr != nil { log.WithFields(f).WithError(pcgErr).Warnf("unable to get project mappings for claGroupID: %s ", signature.SignatureProjectID) return pcgErr diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index 4feb29cf6..a5d88af22 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -176,7 +176,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe }) } - pm, err := projectsClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) + pm, err := projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, params.ProjectSFID) if err != nil { if err == projects_cla_groups.ErrProjectNotAssociatedWithClaGroup { msg := fmt.Sprintf("no cla group associated with this project: %s", params.ProjectSFID) @@ -226,7 +226,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe // Lookup the CLA Group associated with this Project SFID... log.WithFields(f).Debugf("loading CLA Group for projectSFID: %s", params.ProjectSFID) - pm, err := projectsClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) + pm, err := projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, params.ProjectSFID) if err != nil { msg := fmt.Sprintf("problem loading CLA Group from Project SFID:: %s", params.ProjectSFID) log.WithFields(f).Warn(msg) @@ -313,7 +313,7 @@ func Configure(api *operations.EasyclaAPI, service v1Events.Service, v1CompanyRe result, err = service.GetCompanyFoundationEvents(v1Company.CompanyExternalID, "", params.ProjectSFID, params.NextKey, params.PageSize, aws.BoolValue(params.ReturnAllEvents)) } else { log.WithFields(f).Debugf("loading project level events for projectSFID :%s...", params.ProjectSFID) - pm, perr := projectsClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) + pm, perr := projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, params.ProjectSFID) if perr != nil { if perr == projects_cla_groups.ErrProjectNotAssociatedWithClaGroup { // Although the API should view this as a bad request since the project doesn't seem to belong to a diff --git a/cla-backend-go/v2/gerrits/handlers.go b/cla-backend-go/v2/gerrits/handlers.go index 0f5de712b..7118eb1a1 100644 --- a/cla-backend-go/v2/gerrits/handlers.go +++ b/cla-backend-go/v2/gerrits/handlers.go @@ -110,7 +110,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS XRequestID: reqID, }) } - ok, err := projectsClaGroupsRepo.IsAssociated(params.ProjectSFID, params.ClaGroupID) + ok, err := projectsClaGroupsRepo.IsAssociated(ctx, params.ProjectSFID, params.ClaGroupID) if err != nil { return gerrits.NewAddGerritBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } @@ -183,7 +183,7 @@ func Configure(api *operations.EasyclaAPI, v1Service v1Gerrits.Service, projectS } log.WithFields(f).Debug("checking if project CLA Group mapping...") - ok, err := projectsClaGroupsRepo.IsAssociated(params.ProjectSFID, params.ClaGroupID) + ok, err := projectsClaGroupsRepo.IsAssociated(ctx, params.ProjectSFID, params.ClaGroupID) if err != nil { msg := fmt.Sprintf("unable to determine project CLA group association for project: %s and CLA Group: %s", params.ProjectSFID, params.ClaGroupID) log.WithFields(f).WithError(err).Warn(msg) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 94e4350c2..b0de5faea 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -135,7 +135,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) autoEnabledCLAGroupName := "" if org.AutoEnabledClaGroupID != "" { log.WithFields(f).Debugf("Loading CLA Group by ID: %s to obtain the name for GitHub auth enabled CLA Group response", org.AutoEnabledClaGroupID) - claGroupMode, claGroupLookupErr := s.projectsCLAGroupService.GetCLAGroup(org.AutoEnabledClaGroupID) + claGroupMode, claGroupLookupErr := s.projectsCLAGroupService.GetCLAGroup(ctx, org.AutoEnabledClaGroupID) if claGroupLookupErr != nil { log.WithFields(f).WithError(claGroupLookupErr).Warnf("Unable to lookup CLA Group by ID: %s", org.AutoEnabledClaGroupID) } diff --git a/cla-backend-go/v2/metrics/handlers.go b/cla-backend-go/v2/metrics/handlers.go index b69c9369a..b17552ca1 100644 --- a/cla-backend-go/v2/metrics/handlers.go +++ b/cla-backend-go/v2/metrics/handlers.go @@ -120,7 +120,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyRepo v1Comp }) } - result, err := service.ListCompanyProjectMetrics(params.CompanyID, params.ProjectSFID) + result, err := service.ListCompanyProjectMetrics(ctx, params.CompanyID, params.ProjectSFID) if err != nil { return metrics.NewListCompanyProjectMetricsBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } diff --git a/cla-backend-go/v2/metrics/repository.go b/cla-backend-go/v2/metrics/repository.go index 7fcc31042..54f39ef43 100644 --- a/cla-backend-go/v2/metrics/repository.go +++ b/cla-backend-go/v2/metrics/repository.go @@ -4,6 +4,7 @@ package metrics import ( + "context" "errors" "fmt" "time" @@ -967,7 +968,7 @@ type claGroup struct { func (repo *repo) getClaGroupProjectsMapping() (map[string]*claGroup, error) { r := make(map[string]*claGroup) - cgpList, err := repo.projectsClaGroupsRepo.GetProjectsIdsForAllFoundation() + cgpList, err := repo.projectsClaGroupsRepo.GetProjectsIdsForAllFoundation(context.Background()) if err != nil { return r, err } @@ -1073,7 +1074,7 @@ func (repo *repo) projectHelperMap(in *CompanyProjectMetrics, claGroupMapping ma log.Printf("length of projectIDArray %d", len(projectIDArray)) for projectSFID := range projectIDArray { - projectData, err := repo.projectsClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + projectData, err := repo.projectsClaGroupsRepo.GetClaGroupIDForProject(context.Background(), projectSFID) if err != nil { log.Warnf("projectHelperMap/GetClaGroupIDForProject error = unable to get project details from easycla. %s", projectSFID) continue diff --git a/cla-backend-go/v2/metrics/service.go b/cla-backend-go/v2/metrics/service.go index 13da21b41..0b9bd6194 100644 --- a/cla-backend-go/v2/metrics/service.go +++ b/cla-backend-go/v2/metrics/service.go @@ -4,6 +4,7 @@ package metrics import ( + "context" "errors" "math" "sort" @@ -35,7 +36,7 @@ type Service interface { GetTopCompanies() (*models.TopCompanies, error) GetTopProjects() (*models.TopProjects, error) ListProjectMetrics(paramPageSize *int64, paramNextKey *string) (*models.ListProjectMetric, error) - ListCompanyProjectMetrics(companyID string, projectSFID string) (*models.CompanyProjectMetrics, error) + ListCompanyProjectMetrics(ctx context.Context, companyID string, projectSFID string) (*models.CompanyProjectMetrics, error) } type service struct { @@ -287,7 +288,7 @@ func (s *service) ListProjectMetrics(paramPageSize *int64, paramNextKey *string) return &out, nil } -func (s *service) ListCompanyProjectMetrics(companyID string, projectSFID string) (*models.CompanyProjectMetrics, error) { +func (s *service) ListCompanyProjectMetrics(ctx context.Context, companyID string, projectSFID string) (*models.CompanyProjectMetrics, error) { psc := project_service.GetClient() claGroupList := utils.NewStringSet() project, err := psc.GetProject(projectSFID) @@ -295,7 +296,7 @@ func (s *service) ListCompanyProjectMetrics(companyID string, projectSFID string return nil, err } if project.ProjectType == FoundationType { - cgmList, cgerr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(projectSFID) + cgmList, cgerr := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, projectSFID) if cgerr != nil { return nil, err } @@ -303,7 +304,7 @@ func (s *service) ListCompanyProjectMetrics(companyID string, projectSFID string claGroupList.Add(cgm.ClaGroupID) } } else { - cgm, cgerr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + cgm, cgerr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if cgerr != nil { return nil, err } diff --git a/cla-backend-go/v2/project/service.go b/cla-backend-go/v2/project/service.go index 09189dcb4..b7b281fc0 100644 --- a/cla-backend-go/v2/project/service.go +++ b/cla-backend-go/v2/project/service.go @@ -50,7 +50,7 @@ func (s *service) GetCLAProjectsByID(ctx context.Context, foundationSFID string) } enabledClas := make([]*models.EnabledCla, 0) - claGroupsMapping, err := s.projectsClaGroups.GetProjectsIdsForFoundation(foundationSFID) + claGroupsMapping, err := s.projectsClaGroups.GetProjectsIdsForFoundation(ctx, foundationSFID) if err != nil { return nil, err } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index ce5e73db2..a47796026 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -97,7 +97,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, parentProjectSFID = project.Parent } - allMappings, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(aws.StringValue(input.ClaGroupID)) + allMappings, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, aws.StringValue(input.ClaGroupID)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to get project IDs for CLA Group") return nil, err diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 01d181491..683299365 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -176,7 +176,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri if project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { // this is root project - cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(utils.StringValue(input.ProjectSfid)) + cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(ctx, utils.StringValue(input.ProjectSfid)) if perr != nil { log.WithFields(f).WithError(err).Warn("unable to lookup other projects associated with this project SFID") return nil, perr @@ -197,7 +197,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri claGroupID = (claGroups.List())[0] } else { - cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(utils.StringValue(input.ProjectSfid)) + cgm, perr := s.projectClaGroupsRepo.GetClaGroupIDForProject(ctx, utils.StringValue(input.ProjectSfid)) if perr != nil { log.WithFields(f).WithError(err).Warn("unable to lookup CLA Group ID for this project SFID") return nil, perr diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index a09d1a252..15195a8e6 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -529,7 +529,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj // Locate the CLA Group for the provided project SFID log.WithFields(f).Debug("loading project signatures...") - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(params.ProjectSFID) + projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(ctx, params.ProjectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping") return signatures.NewGetProjectCompanyEmployeeSignaturesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError( @@ -758,7 +758,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj } // Lookup the Project to CLA Group mapping table entries - this will have the correct details - projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, params.ClaGroupID) // Should have at least one entry if we're setup correctly - it will have the foundation (parent project/project group) and project details set if projectCLAGroupErr != nil || len(projectCLAGroupEntries) == 0 { msg := fmt.Sprintf("unable to load project CLA Group mappings for CLA Group: %s - has this project been migrated to v2?", params.ClaGroupID) @@ -936,7 +936,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj } // Lookup the Project to CLA Group mapping table entries - this will have the correct details - projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(params.ClaGroupID) + projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, params.ClaGroupID) // Should have at least one entry if we're setup correctly - it will have the foundation (parent project/project group) and project details set if projectCLAGroupErr != nil || len(projectCLAGroupEntries) == 0 { msg := fmt.Sprintf("unable to load project CLA Group mappings for CLA Group: %s - has this project been migrated to v2?", params.ClaGroupID) @@ -1318,7 +1318,7 @@ func isUserHaveAccessOfSignedSignaturePDF(ctx context.Context, authUser *auth.Us } var projectCLAGroup *v1Models.ClaGroup - projects, err := projectClaGroupRepo.GetProjectsIdsForClaGroup(signature.ProjectID) + projects, err := projectClaGroupRepo.GetProjectsIdsForClaGroup(ctx, signature.ProjectID) if err != nil { log.WithFields(f).WithError(err).Warn("error loading load project IDs for CLA Group") return false, err @@ -1429,7 +1429,7 @@ func isUserHaveAccessToCLAGroupProjects(ctx context.Context, authUser *auth.User // Lookup the project IDs for the CLA Group log.WithFields(f).Debug("looking up projects associated with the CLA Group...") - projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(claGroupID) + projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, claGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project cla group mappings by CLA Group ID - failed permission check") return false @@ -1500,7 +1500,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj log.WithFields(f).Debugf("user %s/%s doesn't have direct access to the project SFID: %s - loading CLA Group from project id...", authUser.UserName, authUser.Email, projectSFID) log.WithFields(f).Debug("loading CLA Group from project id...") - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") return false @@ -1524,7 +1524,7 @@ func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, proj // Lookup the other project IDs for the CLA Group log.WithFields(f).Debug("looking up other projects associated with the CLA Group...") - projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(projectCLAGroupModel.ClaGroupID) + projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, projectCLAGroupModel.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project cla group mappings by CLA Group ID - returning false") return false @@ -1589,7 +1589,7 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // other projects or the parent project group/foundation log.WithFields(f).Debugf("user %s/%s doesn't have direct access to the project only, project + organization, or organization only - loading CLA Group from project id...", authUser.UserName, authUser.Email) - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") return false @@ -1627,7 +1627,7 @@ func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *aut // Lookup the other project IDs associated with this CLA Group log.WithFields(f).Debug("looking up other projects associated with the CLA Group...") - projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(projectCLAGroupModel.ClaGroupID) + projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, projectCLAGroupModel.ClaGroupID) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading project cla group mappings by CLA Group ID - returning false") return false diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 2cd4e629a..1984e5ed7 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -88,7 +88,7 @@ func NewService(awsSession *session.Session, signaturesBucketName string, v1Proj } func (s *service) GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) { - pm, err := s.projectsClaGroupsRepo.GetClaGroupIDForProject(projectSFID) + pm, err := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { return nil, err } diff --git a/cla-backend-go/v2/template/handlers.go b/cla-backend-go/v2/template/handlers.go index 7a88e18ae..9f305052b 100644 --- a/cla-backend-go/v2/template/handlers.go +++ b/cla-backend-go/v2/template/handlers.go @@ -5,7 +5,11 @@ package template import ( "context" + "fmt" "net/http" + "strings" + + v1ProjectsCLAGroups "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/sirupsen/logrus" @@ -25,14 +29,14 @@ import ( ) // Configure API call -func Configure(api *operations.EasyclaAPI, service v1Template.Service, eventsService v1Events.Service) { +func Configure(api *operations.EasyclaAPI, service v1Template.Service, v1ProjectClaGroupService v1ProjectsCLAGroups.Service, eventsService v1Events.Service) { // Retrieve a list of available templates api.TemplateGetTemplatesHandler = template.GetTemplatesHandlerFunc(func(params template.GetTemplatesParams, user *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "TemplateGetTemplatesHandler", + "functionName": "v2.template.handlers.TemplateGetTemplatesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } @@ -50,23 +54,39 @@ func Configure(api *operations.EasyclaAPI, service v1Template.Service, eventsSer return template.NewGetTemplatesOK().WithPayload(response) }) - api.TemplateCreateCLAGroupTemplateHandler = template.CreateCLAGroupTemplateHandlerFunc(func(params template.CreateCLAGroupTemplateParams, user *auth.User) middleware.Responder { + api.TemplateCreateCLAGroupTemplateHandler = template.CreateCLAGroupTemplateHandlerFunc(func(params template.CreateCLAGroupTemplateParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint - utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "TemplateCreateCLAGroupTemplateHandler", + "functionName": "v2.template.handlers.TemplateCreateCLAGroupTemplateHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, } + projectCLAGroups, lookupErr := v1ProjectClaGroupService.GetProjectsIdsForClaGroup(ctx, params.ClaGroupID) + if lookupErr != nil || len(projectCLAGroups) == 0 { + msg := fmt.Sprintf("unable to lookup CLA Group mapping using CLA Group ID: %s", params.ClaGroupID) + return template.NewGetTemplatesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, lookupErr)) + } + projectSFIDs := getProjectSFIDList(projectCLAGroups) + + // Check authorization + if !utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("authUser '%s' does not have access to create CLA Group template with Project scope of any %s", + authUser.UserName, strings.Join(projectSFIDs, ",")) + log.WithFields(f).Debug(msg) + return template.NewGetTemplatesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + input := &v1Models.CreateClaGroupTemplate{} err := copier.Copy(input, ¶ms.Body) if err != nil { log.WithFields(f).WithError(err).Warn("problem converting templates") return template.NewGetTemplatesInternalServerError().WithPayload(errorResponse(reqID, err)) } - pdfUrls, err := service.CreateCLAGroupTemplate(params.HTTPRequest.Context(), params.ClaGroupID, input) + + pdfUrls, err := service.CreateCLAGroupTemplate(ctx, params.ClaGroupID, input) if err != nil { log.WithFields(f).WithError(err).Warnf("Error generating PDFs from provided templates, error: %v", err) return template.NewGetTemplatesBadRequest().WithPayload(errorResponse(reqID, err)) @@ -75,7 +95,7 @@ func Configure(api *operations.EasyclaAPI, service v1Template.Service, eventsSer eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLATemplateCreated, ProjectID: params.ClaGroupID, - LfUsername: user.UserName, + LfUsername: authUser.UserName, EventData: &events.CLATemplateCreatedEventData{}, }) @@ -94,7 +114,7 @@ func Configure(api *operations.EasyclaAPI, service v1Template.Service, eventsSer ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "TemplateTemplatePreviewHandler", + "functionName": "v2.template.handlers.TemplateTemplatePreviewHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "templateFor": params.TemplateFor, } @@ -123,10 +143,10 @@ func Configure(api *operations.EasyclaAPI, service v1Template.Service, eventsSer reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "TemplateGetCLATemplatePreviewHandler", + "functionName": "v2.template.handlers.TemplateGetCLATemplatePreviewHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - pdf, err := service.GetCLATemplatePreview(params.HTTPRequest.Context(), params.ClaGroupID, params.ClaType, *params.Watermark) + pdf, err := service.GetCLATemplatePreview(ctx, params.ClaGroupID, params.ClaType, *params.Watermark) if err != nil { log.WithFields(f).WithError(err).Warnf("Error getting PDFs for provided cla group ID : %s, error: %v", params.ClaGroupID, err) return writeResponse(http.StatusBadRequest, runtime.JSONMime, runtime.JSONProducer(), reqID, errorResponse(reqID, err)) @@ -142,6 +162,15 @@ func Configure(api *operations.EasyclaAPI, service v1Template.Service, eventsSer }) } +// getProjectSFIDList is a helper function to extract the project SFID values from the list of project to CLA group mapping records +func getProjectSFIDList(groups []*v1ProjectsCLAGroups.ProjectClaGroup) []string { + var response []string + for _, projectCLAGroup := range groups { + response = append(response, projectCLAGroup.ProjectSFID) + } + return response +} + type codedResponse interface { Code() string } From 81d0f3adf180acbd86cf0cdc762703223e785690 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 26 May 2021 21:40:43 +0300 Subject: [PATCH 0294/1276] [Snyk] Upgrade graceful-fs from 4.2.4 to 4.2.6 (#2962) --- cla-frontend-contributor-console/edge/package.json | 2 +- cla-frontend-contributor-console/edge/yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cla-frontend-contributor-console/edge/package.json b/cla-frontend-contributor-console/edge/package.json index 2ad0aae7c..c9b992a58 100644 --- a/cla-frontend-contributor-console/edge/package.json +++ b/cla-frontend-contributor-console/edge/package.json @@ -10,7 +10,7 @@ "test": "./node_modules/.bin/jasmine --config=jasmine.json" }, "dependencies": { - "graceful-fs": "^4.2.2" + "graceful-fs": "^4.2.6" }, "resolutions": { "node-sass": "4.13.1", diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock index d30e8eecf..77b6a1c18 100644 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ b/cla-frontend-contributor-console/edge/yarn.lock @@ -1347,11 +1347,16 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" From 52fc993e24ec55c48a6e0907210d8fd1bfb57a72 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 26 May 2021 22:10:57 +0300 Subject: [PATCH 0295/1276] [Snyk] Upgrade graceful-fs from 4.2.4 to 4.2.6 (#2965) --- cla-frontend-corporate-console/edge/package.json | 2 +- cla-frontend-corporate-console/edge/yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cla-frontend-corporate-console/edge/package.json b/cla-frontend-corporate-console/edge/package.json index 85c1a2443..e1af3dea4 100644 --- a/cla-frontend-corporate-console/edge/package.json +++ b/cla-frontend-corporate-console/edge/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "acorn": "^5.7.4", - "graceful-fs": "^4.2.2" + "graceful-fs": "^4.2.6" }, "devDependencies": { "babel-core": "^6.26.0", diff --git a/cla-frontend-corporate-console/edge/yarn.lock b/cla-frontend-corporate-console/edge/yarn.lock index c0667c228..1514185fe 100644 --- a/cla-frontend-corporate-console/edge/yarn.lock +++ b/cla-frontend-corporate-console/edge/yarn.lock @@ -1094,11 +1094,16 @@ globals@^9.18.0: resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" From 46006a06e159d42daa59334f881ccde5796fbacb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 May 2021 12:30:35 -0700 Subject: [PATCH 0296/1276] Bump chart.js from 2.8.0 to 2.9.4 in /cla-frontend-corporate-console/src (#2942) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/package.json | 2 +- cla-frontend-corporate-console/src/yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cla-frontend-corporate-console/src/package.json b/cla-frontend-corporate-console/src/package.json index f0996d309..d6c33e8a6 100644 --- a/cla-frontend-corporate-console/src/package.json +++ b/cla-frontend-corporate-console/src/package.json @@ -39,7 +39,7 @@ "@types/lodash": "4.14.112", "@types/node": "^8.0.17", "aws-sdk": "^2.897.0", - "chart.js": "^2.5.0", + "chart.js": "^2.9.4", "google-libphonenumber": "^2.0.18", "graceful-fs": "^4.2.2", "ionic-angular": "3.9.2", diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index 22ee0fefa..9e2359380 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -726,9 +726,10 @@ chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chart.js@^2.5.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.8.0.tgz#b703b10d0f4ec5079eaefdcd6ca32dc8f826e0e9" +chart.js@^2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== dependencies: chartjs-color "^2.1.0" moment "^2.10.2" From ff7f2a5bee25fb72f773e3dd9d9df89d3b3e8681 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 May 2021 12:44:34 -0700 Subject: [PATCH 0297/1276] Bump chart.js from 2.8.0 to 2.9.4 in /cla-frontend-project-console/src (#2940) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/package.json | 2 +- cla-frontend-project-console/src/yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cla-frontend-project-console/src/package.json b/cla-frontend-project-console/src/package.json index e5e27ee59..5fecf5de1 100644 --- a/cla-frontend-project-console/src/package.json +++ b/cla-frontend-project-console/src/package.json @@ -40,7 +40,7 @@ "@types/node": "^8.0.17", "auth0-js": "^9.13.2", "aws-sdk": "^2.304.0", - "chart.js": "^2.5.0", + "chart.js": "^2.9.4", "google-libphonenumber": "^2.0.18", "graceful-fs": "^4.2.2", "ionic-angular": "3.9.2", diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index dc655ea79..ad1b1bdba 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -738,9 +738,10 @@ chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chart.js@^2.5.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.8.0.tgz#b703b10d0f4ec5079eaefdcd6ca32dc8f826e0e9" +chart.js@^2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== dependencies: chartjs-color "^2.1.0" moment "^2.10.2" From 8867bacc7557f7aacab1b454508de3e07c26ad12 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 28 May 2021 14:06:57 -0700 Subject: [PATCH 0298/1276] Added Template ID to CLA Group Model (#2969) - Added template ID to swagger models - Updated internal DB models - Updated repository save logic to add template ID - Updated CLA Group update logic to ensure templates are now changed during an update operation - Updated list cla-groups for foundation/project to include template info Signed-off-by: David Deal --- cla-backend-go/project/models.go | 1 + cla-backend-go/project/projections.go | 1 + cla-backend-go/project/repository.go | 15 +++- cla-backend-go/swagger/cla.v2.yaml | 78 +---------------- .../swagger/common/cla-group-summary.yaml | 86 +++++++++++++++++++ cla-backend-go/swagger/common/cla-group.yaml | 6 +- cla-backend-go/template/repository.go | 7 ++ cla-backend-go/template/service.go | 10 ++- cla-backend-go/v2/cla_groups/helpers.go | 27 +++++- cla-backend-go/v2/cla_groups/service.go | 4 + 10 files changed, 148 insertions(+), 87 deletions(-) create mode 100644 cla-backend-go/swagger/common/cla-group-summary.yaml diff --git a/cla-backend-go/project/models.go b/cla-backend-go/project/models.go index 4d9ea93ac..2caa81c81 100644 --- a/cla-backend-go/project/models.go +++ b/cla-backend-go/project/models.go @@ -15,6 +15,7 @@ type DBProjectModel struct { ProjectNameLower string `dynamodbav:"project_name_lower"` ProjectDescription string `dynamodbav:"project_description"` Version string `dynamodbav:"version"` + ProjectTemplateID string `dynamodbav:"project_template_id"` ProjectCclaEnabled bool `dynamodbav:"project_ccla_enabled"` ProjectCclaRequiresIclaSignature bool `dynamodbav:"project_ccla_requires_icla_signature"` ProjectIclaEnabled bool `dynamodbav:"project_icla_enabled"` diff --git a/cla-backend-go/project/projections.go b/cla-backend-go/project/projections.go index 0fd15fa31..f31c811b4 100644 --- a/cla-backend-go/project/projections.go +++ b/cla-backend-go/project/projections.go @@ -24,6 +24,7 @@ func buildProjection() expression.ProjectionBuilder { expression.Name("project_corporate_documents"), expression.Name("project_individual_documents"), expression.Name("project_member_documents"), + expression.Name("project_template_id"), expression.Name("date_created"), expression.Name("date_modified"), expression.Name("version"), diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 9c69d9705..79ee2d552 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -111,6 +111,7 @@ func (repo *repo) CreateCLAGroup(ctx context.Context, claGroupModel *models.ClaG addStringAttribute(input.Item, "foundation_sfid", claGroupModel.FoundationSFID) addStringAttribute(input.Item, "project_description", claGroupModel.ProjectDescription) addStringAttribute(input.Item, "project_name", claGroupModel.ProjectName) + addStringAttribute(input.Item, "project_template_id", claGroupModel.ProjectTemplateID) addStringAttribute(input.Item, "project_name_lower", strings.ToLower(claGroupModel.ProjectName)) addStringSliceAttribute(input.Item, "project_acl", claGroupModel.ProjectACL) addBooleanAttribute(input.Item, "project_icla_enabled", claGroupModel.ProjectICLAEnabled) @@ -355,15 +356,13 @@ func (repo *repo) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSF } } - // log.WithFields(f).Debugf("foundation projects!: %#v ", projects) - return &models.ClaGroups{ ResultCount: int64(len(projects)), Projects: projects, }, nil } -// GetClaGroupsByProjectSFID returns cla_group associated with project +// GetClaGroupByProjectSFID returns cla_group associated with project func (repo *repo) GetClaGroupByProjectSFID(ctx context.Context, projectSFID string, loadRepoDetails bool) (*models.ClaGroup, error) { f := logrus.Fields{ "functionName": "project.repository.GetClaGroupByProjectSFID", @@ -647,6 +646,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG "ProjectICLAEnabled": claGroupModel.ProjectICLAEnabled, "ProjectCCLAEnabled": claGroupModel.ProjectCCLAEnabled, "ProjectCCLARequiresICLA": claGroupModel.ProjectCCLARequiresICLA, + "ProjectTemplateID": claGroupModel.ProjectTemplateID, "ProjectLive": claGroupModel.ProjectLive, "tableName": repo.claGroupTable} log.WithFields(f).Debugf("processing update CLA Group request") @@ -665,6 +665,14 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG return nil, ErrProjectDoesNotExist } + // We don't allow CLA Group templates to be changed - this requires a legal review + if existingCLAGroup.ProjectTemplateID != claGroupModel.ProjectTemplateID { + msg := fmt.Sprintf("problem updating CLA Group - changing CLA templates is not allowed - project: %s with ID: %s - previous template: %s updated template: %s", + claGroupModel.ProjectName, claGroupModel.ProjectID, claGroupModel.ProjectTemplateID, claGroupModel.ProjectTemplateID) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + expressionAttributeNames := map[string]*string{} expressionAttributeValues := map[string]*dynamodb.AttributeValue{} updateExpression := "SET " @@ -890,6 +898,7 @@ func (repo *repo) buildCLAGroupModel(ctx context.Context, dbModel DBProjectModel ProjectCCLAEnabled: dbModel.ProjectCclaEnabled, ProjectICLAEnabled: dbModel.ProjectIclaEnabled, ProjectCCLARequiresICLA: dbModel.ProjectCclaRequiresIclaSignature, + ProjectTemplateID: dbModel.ProjectTemplateID, ProjectLive: dbModel.ProjectLive, ProjectCorporateDocuments: buildCLAGroupDocumentModels(dbModel.ProjectCorporateDocuments), ProjectIndividualDocuments: buildCLAGroupDocumentModels(dbModel.ProjectIndividualDocuments), diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 7f2a2a7f3..dbf93745e 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -5116,83 +5116,7 @@ definitions: $ref: '#/definitions/cla-group-summary' cla-group-summary: - type: object - properties: - foundationLevelCLA: - description: Flag indicating whether CLA is signed at Foundation level (true) or Project level (false) - type: boolean - x-omitempty: false - cla_group_id: - type: string - example: 'b1e86e26-d8c8-4fd8-9f8d-5c723d5dac9f' - description: id of the CLA group - x-omitempty: false - cla_group_name: - $ref: './common/properties/cla-group-name.yaml' - x-omitempty: false - cla_group_description: - $ref: './common/properties/cla-group-description.yaml' - x-omitempty: false - ccla_enabled: - type: boolean - example: true - description: flag to indicate if CCLA is enabled - x-omitempty: false - ccla_requires_icla: - type: boolean - example: true - description: flag to indicate if corporate contributors requires to sign ICLA - x-omitempty: false - icla_enabled: - type: boolean - example: true - description: flag to indicate if ICLA is enabled - x-omitempty: false - foundation_sfid: - type: string - example: 'a09410000182dD2AAI' - description: foundation sfid under which this CLA group is created - x-omitempty: false - root_project_repositories_count: - type: integer - description: number of repositories added to this CLA Group from root project - x-omitempty: false - foundation_name: - type: string - example: 'Academy Software Foundation' - description: foundation name under which this CLA group is created - x-omitempty: false - repositories_count: - type: integer - description: total repositories under this cla-group - x-omitempty: false - total_signatures: - type: integer - description: aggregate count of ICLA and CCLA contributors within this CLA Group - x-omitempty: false - project_list: - x-omitempty: false - description: list of projects under foundation for which this CLA group is created - type: array - items: - $ref: '#/definitions/cla-group-project' - icla_pdf_url: - description: template URL for ICLA document - type: string - example: 'https://cla-signature-files-dev.s3.amazonaws.com/contract-group/b1e86e26-d8c8-4fd8-9f8d-5c723d5dac9f/template/icla.pdf' - x-omitempty: false - ccla_pdf_url: - description: template URL for CCLA document - type: string - example: 'https://cla-signature-files-dev.s3.amazonaws.com/contract-group/b1e86e26-d8c8-4fd8-9f8d-5c723d5dac9f/template/ccla.pdf' - x-omitempty: false - setup_completion_pct: - description: the CLA Group setup complete percentage, values range from 0-100 inclusive - type: integer - minimum: 0 - maximum: 100 - example: 100 - x-omitempty: false + $ref: './common/cla-group-summary.yaml' cla-group-project: type: object diff --git a/cla-backend-go/swagger/common/cla-group-summary.yaml b/cla-backend-go/swagger/common/cla-group-summary.yaml new file mode 100644 index 000000000..454d2d0a4 --- /dev/null +++ b/cla-backend-go/swagger/common/cla-group-summary.yaml @@ -0,0 +1,86 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +title: CLA Group Summary +description: a summary of the CLA Group information +properties: + foundationLevelCLA: + description: Flag indicating whether CLA is signed at Foundation level (true) or Project level (false) + type: boolean + x-omitempty: false + cla_group_id: + type: string + example: 'b1e86e26-d8c8-4fd8-9f8d-5c723d5dac9f' + description: id of the CLA group + x-omitempty: false + cla_group_name: + $ref: './common/properties/cla-group-name.yaml' + x-omitempty: false + cla_group_description: + $ref: './common/properties/cla-group-description.yaml' + x-omitempty: false + ccla_enabled: + type: boolean + example: true + description: flag to indicate if CCLA is enabled + x-omitempty: false + ccla_requires_icla: + type: boolean + example: true + description: flag to indicate if corporate contributors requires to sign ICLA + x-omitempty: false + icla_enabled: + type: boolean + example: true + description: flag to indicate if ICLA is enabled + x-omitempty: false + template_id: + title: CLA group template + description: the ID of the template - used to generate the ICLA and CCLA PDFs + $ref: './common/properties/internal-id.yaml' + foundation_sfid: + type: string + example: 'a09410000182dD2AAI' + description: foundation sfid under which this CLA group is created + x-omitempty: false + root_project_repositories_count: + type: integer + description: number of repositories added to this CLA Group from root project + x-omitempty: false + foundation_name: + type: string + example: 'Academy Software Foundation' + description: foundation name under which this CLA group is created + x-omitempty: false + repositories_count: + type: integer + description: total repositories under this cla-group + x-omitempty: false + total_signatures: + type: integer + description: aggregate count of ICLA and CCLA contributors within this CLA Group + x-omitempty: false + project_list: + x-omitempty: false + description: list of projects under foundation for which this CLA group is created + type: array + items: + $ref: '#/definitions/cla-group-project' + icla_pdf_url: + description: template URL for ICLA document + type: string + example: 'https://cla-signature-files-dev.s3.amazonaws.com/contract-group/b1e86e26-d8c8-4fd8-9f8d-5c723d5dac9f/template/icla.pdf' + x-omitempty: false + ccla_pdf_url: + description: template URL for CCLA document + type: string + example: 'https://cla-signature-files-dev.s3.amazonaws.com/contract-group/b1e86e26-d8c8-4fd8-9f8d-5c723d5dac9f/template/ccla.pdf' + x-omitempty: false + setup_completion_pct: + description: the CLA Group setup complete percentage, values range from 0-100 inclusive + type: integer + minimum: 0 + maximum: 100 + example: 100 + x-omitempty: false diff --git a/cla-backend-go/swagger/common/cla-group.yaml b/cla-backend-go/swagger/common/cla-group.yaml index 4489f32fd..6f11b52ba 100644 --- a/cla-backend-go/swagger/common/cla-group.yaml +++ b/cla-backend-go/swagger/common/cla-group.yaml @@ -31,12 +31,16 @@ properties: description: Flag indicating whether CLA is signed at Foundation level (true) or Project level (false) example: true type: boolean - x-omitempty: false + x-omitempty: false projectCCLAEnabled: description: Flag to indicate if the Corporate/Company Contributor License Agreement is enabled example: true type: boolean x-omitempty: false + projectTemplateID: + title: CLA group template + description: the ID of the template - used to generate the ICLA and CCLA PDFs + $ref: './common/properties/internal-id.yaml' projectICLAEnabled: description: Flag to indicate if the Individual Contributor License Agreement is enabled example: true diff --git a/cla-backend-go/template/repository.go b/cla-backend-go/template/repository.go index 6fba94bb1..546ba85dc 100644 --- a/cla-backend-go/template/repository.go +++ b/cla-backend-go/template/repository.go @@ -39,6 +39,7 @@ var ( type Repository interface { GetTemplates(ctx context.Context) ([]models.Template, error) GetTemplate(templateID string) (models.Template, error) + CLAGroupTemplateExists(ctx context.Context, templateID string) bool GetCLAGroup(claGroupID string) (*models.ClaGroup, error) GetCLADocuments(claGroupID string, claType string) ([]models.ClaGroupDocument, error) UpdateDynamoContractGroupTemplates(ctx context.Context, ContractGroupID string, template models.Template, pdfUrls models.TemplatePdfs, projectCCLAEnabled, projectICLAEnabled bool) error @@ -137,6 +138,12 @@ func (r repository) GetTemplate(templateID string) (models.Template, error) { return template, nil } +// CLAGroupTemplateExists return true if the specified template ID exists, false otherwise +func (r repository) CLAGroupTemplateExists(ctx context.Context, templateID string) bool { + _, ok := templateMap[templateID] + return ok +} + // GetCLAGroup This method belongs in the contract group package. We are leaving it here // because it accesses DynamoDB, but the contract group repository is designed // to connect to postgres diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index 1f5160b2e..a73aa043b 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -39,6 +39,7 @@ type Service interface { CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) GetCLATemplatePreview(ctx context.Context, claGroupID, claType string, watermark bool) ([]byte, error) + CLAGroupTemplateExists(ctx context.Context, templateID string) bool } type service struct { @@ -154,7 +155,7 @@ func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, // Get Template template, err := s.templateRepo.GetTemplate(claGroupFields.TemplateID) if err != nil { - log.WithFields(f).WithError(err).Warnf("Unable to fetch template fields: %s - returning empty template PDFs", + log.WithFields(f).WithError(err).Warnf("Unable to fetch template id: %s - returning empty template PDFs", claGroupFields.TemplateID) return models.TemplatePdfs{}, err } @@ -495,7 +496,7 @@ func (s service) generateTemplateS3FilePath(claGroupID, claType string) string { return fileName } -// SaveTemplateToS3 +// SaveTemplateToS3 uploads the specified template contents to S3 storage func (s service) SaveTemplateToS3(bucket, filepath string, template io.ReadCloser) (string, error) { f := logrus.Fields{ "functionName": "v1.template.service.SaveTemplateToS3", @@ -523,3 +524,8 @@ func (s service) SaveTemplateToS3(bucket, filepath string, template io.ReadClose return result.Location, nil } + +// CLAGroupTemplateExists return true if the specified template ID exists, false otherwise +func (s service) CLAGroupTemplateExists(ctx context.Context, templateID string) bool { + return s.templateRepo.CLAGroupTemplateExists(ctx, templateID) +} diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 8e8189363..fd639b5f4 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -35,11 +35,12 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat if input.ClaGroupName == nil { return false, fmt.Errorf("missing CLA Group parameter") } + foundationSFID := *input.FoundationSfid claGroupName := *input.ClaGroupName f := logrus.Fields{ - "functionName": "validateClaGroupInput", + "functionName": "v2.cla_groups.helpers.validateClaGroupInput", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "ClaGroupName": claGroupName, "ClaGroupDescription": input.ClaGroupDescription, @@ -48,19 +49,37 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat "CclaEnabled": *input.CclaEnabled, "CclaRequiresIcla": *input.CclaRequiresIcla, "ProjectSfidList": strings.Join(input.ProjectSfidList, ","), + "templateID": input.TemplateFields.TemplateID, } log.WithFields(f).Debug("validating CLA Group input...") + + if input.TemplateFields.TemplateID == "" { + msg := "missing CLA Group template ID value" + log.WithFields(f).Warn(msg) + return false, errors.New(msg) + } + if !s.v1TemplateService.CLAGroupTemplateExists(ctx, input.TemplateFields.TemplateID) { + msg := "invalid template ID" + log.WithFields(f).Warn(msg) + return false, errors.New(msg) + } // First, check that all the required flags are set and make sense if foundationSFID == "" { - return false, fmt.Errorf("bad request: foundation_sfid cannot be empty") + msg := "bad request: foundation_sfid cannot be empty" + log.WithFields(f).Warn(msg) + return false, errors.New(msg) } if !*input.IclaEnabled && !*input.CclaEnabled { - return false, fmt.Errorf("bad request: can not create cla group with both icla and ccla disabled") + msg := "bad request: can not create cla group with both icla and ccla disabled" + log.WithFields(f).Warn(msg) + return false, errors.New(msg) } if *input.CclaRequiresIcla { if !(*input.IclaEnabled && *input.CclaEnabled) { - return false, fmt.Errorf("bad request: ccla_requires_icla can not be enabled if one of icla/ccla is disabled") + msg := "bad request: ccla_requires_icla can not be enabled if one of icla/ccla is disabled" + log.WithFields(f).Warn(msg) + return false, errors.New(msg) } } diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index aef0ee9ec..f333dc930 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -106,6 +106,7 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input "CclaRequiresIcla": aws.BoolValue(input.CclaRequiresIcla), "ProjectSfidList": strings.Join(input.ProjectSfidList, ","), "projectManagerLFID": projectManagerLFID, + "claGroupTemplate": input.TemplateFields.TemplateID, } log.WithFields(f).Debug("validating CLA Group input") @@ -142,6 +143,7 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input ProjectACL: []string{projectManagerLFID}, ProjectICLAEnabled: *input.IclaEnabled, ProjectName: *input.ClaGroupName, + ProjectTemplateID: input.TemplateFields.TemplateID, Version: "v2", }) if err != nil { @@ -220,6 +222,7 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input ClaGroupDescription: claGroup.ProjectDescription, ClaGroupID: claGroup.ProjectID, ClaGroupName: claGroup.ProjectName, + TemplateID: claGroup.ProjectTemplateID, FoundationSfid: claGroup.FoundationSFID, FoundationName: foundationName, IclaEnabled: claGroup.ProjectICLAEnabled, @@ -518,6 +521,7 @@ func (s *service) buildClaGroupSummaryResponseModel(ctx context.Context, f logru ClaGroupDescription: v1ClaGroup.ProjectDescription, ClaGroupID: v1ClaGroup.ProjectID, ClaGroupName: v1ClaGroup.ProjectName, + TemplateID: v1ClaGroup.ProjectTemplateID, FoundationSfid: v1ClaGroup.FoundationSFID, FoundationName: foundationName, IclaEnabled: v1ClaGroup.ProjectICLAEnabled, From 6a62224cce4e8bfd3f5526c83ab83cb828b757fd Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 28 May 2021 17:38:08 -0700 Subject: [PATCH 0299/1276] Resolved PCC-1009 - Search Term Query (#2970) --- cla-backend-go/signatures/repository.go | 144 +++++++++++------------ cla-backend-go/v2/signatures/handlers.go | 1 - 2 files changed, 72 insertions(+), 73 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index d08661a7c..dc3a0f6ad 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -468,21 +468,21 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } builder := expression.NewBuilder(). @@ -581,21 +581,21 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } builder := expression.NewBuilder(). @@ -718,13 +718,14 @@ func (repo repository) GetSignatureACL(ctx context.Context, signatureID string) return dbModel.SignatureACL, nil } -func addConditionToFilter(filter expression.ConditionBuilder, cond expression.ConditionBuilder, filterAdded *bool) expression.ConditionBuilder { +func addAndCondition(filter expression.ConditionBuilder, cond expression.ConditionBuilder, filterAdded *bool) expression.ConditionBuilder { if !(*filterAdded) { *filterAdded = true filter = cond } else { filter = filter.And(cond) } + *filterAdded = true return filter } @@ -780,7 +781,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } else { if params.SearchField != nil { searchFieldExpression := expression.Name("signature_reference_type").Equal(expression.Value(params.SearchField)) - filter = addConditionToFilter(filter, searchFieldExpression, &filterAdded) + filter = addAndCondition(filter, searchFieldExpression, &filterAdded) } if params.SignatureType != nil { @@ -789,13 +790,13 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(*params.SignatureType)))) } else { signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(params.SignatureType)) - filter = addConditionToFilter(filter, signatureTypeExpression, &filterAdded) + filter = addAndCondition(filter, signatureTypeExpression, &filterAdded) } if *params.SignatureType == utils.ClaTypeCCLA { signatureReferenceIDExpression := expression.Name("signature_reference_id").AttributeExists() signatureUserCclaCompanyIDExpression := expression.Name("signature_user_ccla_company_id").AttributeNotExists() - filter = addConditionToFilter(filter, signatureReferenceIDExpression, &filterAdded) - filter = addConditionToFilter(filter, signatureUserCclaCompanyIDExpression, &filterAdded) + filter = addAndCondition(filter, signatureReferenceIDExpression, &filterAdded) + filter = addAndCondition(filter, signatureUserCclaCompanyIDExpression, &filterAdded) } } @@ -808,7 +809,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur log.WithFields(f).Debugf("adding filters signature_reference_name_lower: %s or user_email: %s", strings.ToLower(utils.StringValue(params.SearchTerm)), strings.ToLower(utils.StringValue(params.SearchTerm))) searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(utils.StringValue(params.SearchTerm))). Or(expression.Name("user_email").Contains(strings.ToLower(utils.StringValue(params.SearchTerm)))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } } } @@ -817,21 +818,21 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(params.Approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if params.Signed != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(params.Signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } if filterAdded { @@ -997,7 +998,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si } else { if params.SearchField != nil { searchFieldExpression := expression.Name("signature_reference_type").Equal(expression.Value(params.SearchField)) - filter = addConditionToFilter(filter, searchFieldExpression, &filterAdded) + filter = addAndCondition(filter, searchFieldExpression, &filterAdded) } if params.SignatureType != nil { @@ -1006,13 +1007,13 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(*params.SignatureType)))) } else { signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(params.SignatureType)) - filter = addConditionToFilter(filter, signatureTypeExpression, &filterAdded) + filter = addAndCondition(filter, signatureTypeExpression, &filterAdded) } if *params.SignatureType == utils.ClaTypeCCLA { signatureReferenceIDExpression := expression.Name("signature_reference_id").AttributeExists() signatureUserCclaCompanyIDExpression := expression.Name("signature_user_ccla_company_id").AttributeNotExists() - filter = addConditionToFilter(filter, signatureReferenceIDExpression, &filterAdded) - filter = addConditionToFilter(filter, signatureUserCclaCompanyIDExpression, &filterAdded) + filter = addAndCondition(filter, signatureReferenceIDExpression, &filterAdded) + filter = addAndCondition(filter, signatureUserCclaCompanyIDExpression, &filterAdded) } } @@ -1023,7 +1024,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si } else { searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(utils.StringValue(params.SearchTerm))). Or(expression.Name("user_email").Contains(strings.ToLower(utils.StringValue(params.SearchTerm)))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } } } @@ -1032,21 +1033,21 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(params.Approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if params.Signed != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(params.Signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } if len(params.Body) > 0 { @@ -1056,9 +1057,9 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si referenceIDExpressions = append(referenceIDExpressions, expression.Value(value)) } if len(referenceIDExpressions) == 1 { - filter = addConditionToFilter(filter, expression.Name("signature_reference_id").In(referenceIDExpressions[0]), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_id").In(referenceIDExpressions[0]), &filterAdded) } else if len(referenceIDExpressions) > 1 { - filter = addConditionToFilter(filter, expression.Name("signature_reference_id").In(referenceIDExpressions[0], referenceIDExpressions[1:]...), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_id").In(referenceIDExpressions[0], referenceIDExpressions[1:]...), &filterAdded) } } @@ -1231,21 +1232,21 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if signed != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } limit := int64(10) @@ -1393,10 +1394,10 @@ func (repo repository) ProjectSignatures(ctx context.Context, projectID string) // Filter condition to cater for approved and signed signatures signatureApprovedExpression := expression.Name("signature_approved").Equal(expression.Value(true)) - filter = addConditionToFilter(filter, signatureApprovedExpression, &filterAdded) + filter = addAndCondition(filter, signatureApprovedExpression, &filterAdded) signatureSignedExpression := expression.Name("signature_signed").Equal(expression.Value(true)) - filter = addConditionToFilter(filter, signatureSignedExpression, &filterAdded) + filter = addAndCondition(filter, signatureSignedExpression, &filterAdded) if filterAdded { builder = builder.WithFilter(filter) @@ -2954,8 +2955,6 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, "searchTerm": utils.StringValue(searchTerm), - "approved": utils.BoolValue(approved), - "signed": utils.BoolValue(signed), } var filterAdded bool @@ -2965,32 +2964,38 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) if approved != nil { - filterAdded = true + f["approved"] = utils.BoolValue(approved) log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if signed != nil { - filterAdded = true + f["signed"] = utils.BoolValue(signed) log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if approved == nil && signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { - filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addConditionToFilter(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addConditionToFilter(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } if searchTerm != nil { - log.WithFields(f).Debugf("adding search term filter for : %s ", *searchTerm) - searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(*searchTerm)).Or(expression.Name("user_email").Contains(strings.ToLower(*searchTerm))) - filter = addConditionToFilter(filter, searchTermExpression, &filterAdded) + searchTermValue := utils.StringValue(searchTerm) + f["searchTerm"] = searchTermValue + log.WithFields(f).Debugf("adding search term filter for: '%s'", searchTermValue) + searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(searchTermValue)). + Or(expression.Name("user_email").Contains(strings.ToLower(searchTermValue))). + Or(expression.Name("user_github_username").Contains(strings.ToLower(searchTermValue))). + Or(expression.Name("user_docusign_name").Contains(strings.ToLower(searchTermValue))) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } + log.WithFields(f).Debugf("filter: %+v", filter) + // Use the builder to create the expression expr, err := expression.NewBuilder(). WithKeyCondition(condition). @@ -3024,10 +3029,6 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID queryInput.Limit = &pageSize - if searchTerm != nil { - searchTerm = aws.String(strings.ToLower(*searchTerm)) - } - // If we have the next key, set the exclusive start key value if nextKey != "" { log.WithFields(f).Debugf("Received a nextKey, value: %s", nextKey) @@ -3064,7 +3065,7 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID return nil, unmarshallError } - intermediateResponse = append(intermediateResponse, repo.getIntermediateICLAResponse(f, dbSignatures, searchTerm)...) + intermediateResponse = append(intermediateResponse, repo.getIntermediateICLAResponse(f, dbSignatures)...) log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey["signature_id"]) if results.LastEvaluatedKey["signature_id"] != nil { @@ -3100,16 +3101,10 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID return out, nil } -func (repo repository) getIntermediateICLAResponse(f logrus.Fields, dbSignatures []ItemSignature, searchTerm *string) []*iclaSignatureWithDetails { +func (repo repository) getIntermediateICLAResponse(f logrus.Fields, dbSignatures []ItemSignature) []*iclaSignatureWithDetails { var intermediateResponse []*iclaSignatureWithDetails for _, sig := range dbSignatures { - if searchTerm != nil { - if !strings.Contains(sig.SignatureReferenceNameLower, *searchTerm) { - continue - } - } - // Set the signed date/time var sigSignedTime string // Use the user docusign date signed value if it is present - older signatures do not have this @@ -3117,15 +3112,15 @@ func (repo repository) getIntermediateICLAResponse(f logrus.Fields, dbSignatures // Put the date into a standard format t, err := utils.ParseDateTime(sig.UserDocusignDateSigned) if err != nil { - log.WithFields(f).WithError(err).Warn("unable to parse signature docusign date signed time") + log.WithFields(f).WithError(err).Warnf("unable to parse signature docusign date signed time: %s", sig.UserDocusignDateSigned) } else { sigSignedTime = utils.TimeToString(t) } - } else { + } else if sig.DateCreated != "" { // Put the date into a standard format t, err := utils.ParseDateTime(sig.DateCreated) if err != nil { - log.WithFields(f).WithError(err).Warn("unable to parse signature date created time") + log.WithFields(f).WithError(err).Warnf("unable to parse signature date created time: %s", sig.DateCreated) } else { sigSignedTime = utils.TimeToString(t) } @@ -3222,8 +3217,21 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla condition = condition.And(expression.Key("sigtype_signed_approved_id").BeginsWith(sortKeyPrefix)) } + // Create our builder + builder := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildProjection()) + + if searchTerm != nil { + searchTermValue := utils.StringValue(searchTerm) + f["searchTerm"] = searchTermValue + log.WithFields(f).Debugf("adding search term filter for: '%s'", searchTermValue) + builder.WithFilter(expression.Name("signature_reference_name_lower").Contains(strings.ToLower(searchTermValue)). + Or(expression.Name("user_email").Contains(strings.ToLower(searchTermValue))). + Or(expression.Name("github_username").Contains(strings.ToLower(searchTermValue))). + Or(expression.Name("userDocusignName").Contains(strings.ToLower(searchTermValue)))) + } + // Use the builder to create the expression - expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildProjection()).Build() + expr, err := builder.Build() if err != nil { log.WithFields(f).Warnf("error building expression for get cla group icla signatures, claGroupID: %s, error: %v", claGroupID, err) @@ -3236,15 +3244,13 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla ExpressionAttributeValues: expr.Values(), KeyConditionExpression: expr.KeyCondition(), ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), TableName: aws.String(repo.signatureTableName), IndexName: aws.String(SignatureProjectIDSigTypeSignedApprovedIDIndex), Limit: aws.Int64(HugePageSize), } out := &models.CorporateContributorList{List: make([]*models.CorporateContributor, 0)} - if searchTerm != nil { - searchTerm = aws.String(strings.ToLower(*searchTerm)) - } for { // Make the DynamoDB Query API call @@ -3266,12 +3272,6 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla log.WithFields(f).Debugf("located %d signatures...", len(dbSignatures)) for _, sig := range dbSignatures { - if searchTerm != nil { - if !strings.Contains(sig.SignatureReferenceNameLower, *searchTerm) { - continue - } - } - var sigCreatedTime = sig.DateCreated t, err := utils.ParseDateTime(sig.DateCreated) if err != nil { diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 15195a8e6..81cfc3921 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -850,7 +850,6 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj log.Warn(msg) return signatures.NewGetProjectCompanyEmployeeSignaturesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("searching for ICLA signatures...") From 969081e4c5b3aff5f33b24750b16acc8228fbabb Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Jun 2021 08:12:52 -0700 Subject: [PATCH 0300/1276] Resolved PCC-1014 Corporate Signature Query (#2971) --- cla-backend-go/v2/signatures/handlers.go | 32 ++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 81cfc3921..07a5010ee 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -369,8 +369,6 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": params.ClaGroupID, "signatureType": params.SignatureType, - "approved": utils.BoolValue(params.Approved), - "signed": utils.BoolValue(params.Signed), } log.WithFields(f).Debug("looking up CLA Group by ID...") @@ -387,7 +385,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj f["foundationSFID"] = claGroupModel.FoundationSFID // Check to see if this CLA Group is configured for ICLAs... - if !claGroupModel.ProjectICLAEnabled { + if params.SignatureType != nil && utils.StringValue(params.ClaType) == utils.ClaTypeICLA && !claGroupModel.ProjectICLAEnabled { log.WithFields(f).Warn(iclaNotSupportedForCLAGroup) // Return 200 as the retool UI can't handle 400's return signatures.NewGetProjectSignaturesOK().WithXRequestID(reqID).WithPayload(&models.Signatures{ @@ -396,19 +394,27 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj Signatures: []*models.Signature{}, // empty list TotalCount: 0, }) - //return signatures.NewGetProjectSignaturesBadRequest().WithXRequestID(reqID).WithPayload( - // utils.ErrorResponseBadRequest(reqID, iclaNotSupportedForCLAGroup)) } - if false { - log.WithFields(f).Debug("checking access control permissions for user...") - if !isUserHaveAccessToCLAGroupProjects(ctx, authUser, params.ClaGroupID, projectClaGroupsRepo, projectRepo) { - msg := fmt.Sprintf("user '%s' is not authorized to view project ICLA signatures any scope of project", authUser.UserName) - log.Warn(msg) - return signatures.NewGetProjectSignaturesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) - } - log.WithFields(f).Debug("user has access for this query") + // Check to see if this CLA Group is configured for CCLAs... + if params.SignatureType != nil && utils.StringValue(params.ClaType) == utils.ClaTypeCCLA && !claGroupModel.ProjectCCLAEnabled { + log.WithFields(f).Warn(cclaNotSupportedForCLAGroup) + // Return 200 as the retool UI can't handle 400's + return signatures.NewGetProjectSignaturesOK().WithXRequestID(reqID).WithPayload(&models.Signatures{ + ProjectID: params.ClaGroupID, + ResultCount: 0, + Signatures: []*models.Signature{}, // empty list + TotalCount: 0, + }) + } + + log.WithFields(f).Debug("checking access control permissions for user...") + if !isUserHaveAccessToCLAGroupProjects(ctx, authUser, params.ClaGroupID, projectClaGroupsRepo, projectRepo) { + msg := fmt.Sprintf("user '%s' is not authorized to view project ICLA signatures any scope of project", authUser.UserName) + log.Warn(msg) + return signatures.NewGetProjectSignaturesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } + log.WithFields(f).Debug("user has access for this query") log.WithFields(f).Debug("loading project signatures...") projectSignatures, err := v1SignatureService.GetProjectSignatures(ctx, v1Signatures.GetProjectSignaturesParams{ From dff91fc8dc31fa405799a094e6543fe0a4f3ba90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jun 2021 17:43:45 -0700 Subject: [PATCH 0301/1276] Bump urllib3 from 1.25.9 to 1.26.5 in /cla-backend (#2972) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index c0f65349f..2b4150faf 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -53,7 +53,7 @@ six==1.13.0 soupsieve==1.9.5 termcolor==1.1.0 typed-ast==1.4.1 -urllib3==1.25.9 +urllib3==1.26.5 vintage==0.4.1 wcwidth==0.1.7 Werkzeug==0.15.5 From 2a8981b1616541d23321c52320f97c37c9d3a999 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 3 Jun 2021 08:21:23 -0700 Subject: [PATCH 0302/1276] Revert "Bump urllib3 from 1.25.9 to 1.26.5 in /cla-backend" (#2974) --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 2b4150faf..c0f65349f 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -53,7 +53,7 @@ six==1.13.0 soupsieve==1.9.5 termcolor==1.1.0 typed-ast==1.4.1 -urllib3==1.26.5 +urllib3==1.25.9 vintage==0.4.1 wcwidth==0.1.7 Werkzeug==0.15.5 From 872ddc2b40dbadecef20e6e5cd4de9f738505f2a Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 3 Jun 2021 18:41:11 +0300 Subject: [PATCH 0303/1276] [#PCC-494]Feature/ GH Organization CRUD (#2973) Co-authored-by: Harold Wanyama --- cla-backend-go/github_organizations/mock.go | 8 +- cla-backend-go/github_organizations/models.go | 2 + .../github_organizations/repository.go | 99 +++++++++++++------ .../github_organizations/service.go | 2 +- .../swagger/common/github-organization.yaml | 4 + .../v2/github_organizations/service.go | 2 +- 6 files changed, 81 insertions(+), 36 deletions(-) diff --git a/cla-backend-go/github_organizations/mock.go b/cla-backend-go/github_organizations/mock.go index 48f1c20bc..29c6e5215 100644 --- a/cla-backend-go/github_organizations/mock.go +++ b/cla-backend-go/github_organizations/mock.go @@ -143,15 +143,15 @@ func (mr *MockRepositoryMockRecorder) GetGithubOrganizationsByParent(arg0, arg1 } // UpdateGithubOrganization mocks base method -func (m *MockRepository) UpdateGithubOrganization(arg0 context.Context, arg1, arg2 string, arg3 bool, arg4 string, arg5 bool) error { +func (m *MockRepository) UpdateGithubOrganization(arg0 context.Context, arg1, arg2 string, arg3 bool, arg4 string, arg5 bool, arg6 *bool) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateGithubOrganization", arg0, arg1, arg2, arg3, arg4, arg5) + ret := m.ctrl.Call(m, "UpdateGithubOrganization", arg0, arg1, arg2, arg3, arg4, arg5, arg6) ret0, _ := ret[0].(error) return ret0 } // UpdateGithubOrganization indicates an expected call of UpdateGithubOrganization -func (mr *MockRepositoryMockRecorder) UpdateGithubOrganization(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockRepositoryMockRecorder) UpdateGithubOrganization(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubOrganization", reflect.TypeOf((*MockRepository)(nil).UpdateGithubOrganization), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubOrganization", reflect.TypeOf((*MockRepository)(nil).UpdateGithubOrganization), arg0, arg1, arg2, arg3, arg4, arg5, arg6) } diff --git a/cla-backend-go/github_organizations/models.go b/cla-backend-go/github_organizations/models.go index b63ce0958..955ee50ee 100644 --- a/cla-backend-go/github_organizations/models.go +++ b/cla-backend-go/github_organizations/models.go @@ -14,6 +14,7 @@ type GithubOrganization struct { OrganizationNameLower string `json:"organization_name_lower,omitempty"` OrganizationSFID string `json:"organization_sfid,omitempty"` ProjectSFID string `json:"project_sfid"` + Enabled bool `json:"enabled"` AutoEnabled bool `json:"auto_enabled"` BranchProtectionEnabled bool `json:"branch_protection_enabled"` AutoEnabledClaGroupID string `json:"auto_enabled_cla_group_id,omitempty"` @@ -29,6 +30,7 @@ func ToModel(in *GithubOrganization) *models.GithubOrganization { OrganizationName: in.OrganizationName, OrganizationSfid: in.OrganizationSFID, Version: in.Version, + Enabled: in.Enabled, AutoEnabled: in.AutoEnabled, AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, BranchProtectionEnabled: in.BranchProtectionEnabled, diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index e63c20257..21ed9e985 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -42,7 +42,7 @@ type Repository interface { GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) - UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error + UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error } @@ -108,12 +108,15 @@ func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectS } // Attempt to simply update the existing record - we should only have one + // activate GH org by updating the enabled flag + enabled := true updateErr := repo.UpdateGithubOrganization(ctx, projectSFID, utils.StringValue(input.OrganizationName), autoEnabled, autoEnabledCLAGroupID, branchProtectionEnabled, + &enabled, ) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warn("unable to update existing github organization record") @@ -141,6 +144,7 @@ func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectS // No existing records - create one _, currentTime := utils.CurrentTime() + enabled := true githubOrg := &GithubOrganization{ DateCreated: currentTime, DateModified: currentTime, @@ -149,6 +153,7 @@ func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectS OrganizationNameLower: strings.ToLower(*input.OrganizationName), OrganizationSFID: parentProjectSFID, ProjectSFID: projectSFID, + Enabled: aws.BoolValue(&enabled), AutoEnabled: aws.BoolValue(input.AutoEnabled), AutoEnabledClaGroupID: input.AutoEnabledClaGroupID, BranchProtectionEnabled: aws.BoolValue(input.BranchProtectionEnabled), @@ -369,7 +374,7 @@ func (repo repository) GetGithubOrganization(ctx context.Context, githubOrganiza } // UpdateGithubOrganization updates the specified GitHub organization based on the update model provided -func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { +func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.UpdateGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -393,34 +398,46 @@ func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID return lookupErr } + expressionAttributeNames := map[string]*string{ + "#A": aws.String("auto_enabled"), + "#C": aws.String("auto_enabled_cla_group_id"), + "#B": aws.String("branch_protection_enabled"), + "#M": aws.String("date_modified"), + } + expressionAttributeValues := map[string]*dynamodb.AttributeValue{ + ":a": { + BOOL: aws.Bool(autoEnabled), + }, + ":c": { + S: aws.String(autoEnabledClaGroupID), + }, + ":b": { + BOOL: aws.Bool(branchProtectionEnabled), + }, + ":m": { + S: aws.String(currentTime), + }, + } + updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m," + + if enabled != nil { + expressionAttributeNames["#E"] = aws.String("enabled") + expressionAttributeValues[":e"] = &dynamodb.AttributeValue{ + BOOL: aws.Bool(*enabled), + } + updateExpression = updateExpression + " #E = :e " + } + input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ "organization_name": { S: aws.String(githubOrg.OrganizationName), }, }, - ExpressionAttributeNames: map[string]*string{ - "#A": aws.String("auto_enabled"), - "#C": aws.String("auto_enabled_cla_group_id"), - "#B": aws.String("branch_protection_enabled"), - "#M": aws.String("date_modified"), - }, - ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ - ":a": { - BOOL: aws.Bool(autoEnabled), - }, - ":c": { - S: aws.String(autoEnabledClaGroupID), - }, - ":b": { - BOOL: aws.Bool(branchProtectionEnabled), - }, - ":m": { - S: aws.String(currentTime), - }, - }, - UpdateExpression: aws.String("SET #A = :a, #C = :c, #B = :b, #M = :m"), - TableName: aws.String(repo.githubOrgTableName), + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + UpdateExpression: &updateExpression, + TableName: aws.String(repo.githubOrgTableName), } log.WithFields(f).Debug("updating github organization record...") @@ -456,14 +473,36 @@ func (repo repository) DeleteGithubOrganization(ctx context.Context, projectSFID } log.WithFields(f).Debug("Deleting GitHub organization...") - _, err := repo.dynamoDBClient.DeleteItem(&dynamodb.DeleteItemInput{ - Key: map[string]*dynamodb.AttributeValue{ - "organization_name": { - S: aws.String(githubOrganizationName), + // Update enabled flag as false + _, currentTime := utils.CurrentTime() + note := fmt.Sprintf("Enabled set to false due to org deletion at %s ", currentTime) + _, err := repo.dynamoDBClient.UpdateItem( + &dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "organization_name": { + S: aws.String(githubOrganizationName), + }, + }, + ExpressionAttributeNames: map[string]*string{ + "#E": aws.String("enabled"), + "#N": aws.String("note"), + "#D": aws.String("date_modified"), }, + ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":e": { + BOOL: aws.Bool(false), + }, + ":n": { + S: aws.String(note), + }, + ":d": { + S: aws.String(currentTime), + }, + }, + UpdateExpression: aws.String("SET #E = :e, #N = :n, #D = :d"), + TableName: aws.String(repo.githubOrgTableName), }, - TableName: aws.String(repo.githubOrgTableName), - }) + ) if err != nil { errMsg := fmt.Sprintf("error deleting github organization: %s - %+v", githubOrgName, err) log.WithFields(f).Warnf(errMsg) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index f434d64af..f1556b6fb 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -165,7 +165,7 @@ func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID strin return err } } - return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled) + return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) } func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { diff --git a/cla-backend-go/swagger/common/github-organization.yaml b/cla-backend-go/swagger/common/github-organization.yaml index 327bd724f..d66f4d5b0 100644 --- a/cla-backend-go/swagger/common/github-organization.yaml +++ b/cla-backend-go/swagger/common/github-organization.yaml @@ -28,6 +28,10 @@ properties: projectSFID: type: string example: "a0941000002wBz4AAA" + enabled: + type: boolean + description: Flag that indicates whether this Github Organization is active + x-omitempty: false autoEnabled: type: boolean description: Flag to indicate if this GitHub Organization is configured to allow new repositories to be auto-enabled/auto-enrolled in EasyCLA. diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index b0de5faea..d70513a83 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -310,7 +310,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { - return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled) + return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) } func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { From 2bd40e39d1a468a674f44e910301c648d1661f1e Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 3 Jun 2021 12:06:18 -0700 Subject: [PATCH 0304/1276] Added Build for Mac Arm64 (#2976) --- cla-backend-go/.gitignore | 2 +- cla-backend-go/Makefile | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/.gitignore b/cla-backend-go/.gitignore index b73124e8a..9dc1cd0b5 100644 --- a/cla-backend-go/.gitignore +++ b/cla-backend-go/.gitignore @@ -3,7 +3,7 @@ # Project specific ignores cla-backend-go cla -cla-mac +cla-mac* backend-aws-lambda backend-aws-lambda-mac backend-aws-lambda-linux diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 2c3577f48..6f09624a3 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -62,7 +62,7 @@ setup-deploy: @yarn add serverless && yarn install clean: - @rm -rf cla cla-mac cla-linux \ + @rm -rf cla cla-mac* cla-linux \ ./v2/project-service/client ./v2/project-service/models \ ./v2/organization-service/client ./v2/organization-service/models \ ./v2/user-service/client ./v2/user-service/models \ @@ -206,6 +206,11 @@ build-mac: deps env GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(SERVICE)-mac main.go @chmod +x $(SERVICE)-mac +build-mac-arm: deps + @echo "Building Mac OSX Arm64 binary..." + env GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(SERVICE)-mac-arm64 main.go + @chmod +x $(SERVICE)-mac + rebuild-mac: clean fmt build-mac lint ./$(SERVICE)-mac From 0268ca26355e9f34e8de99575d22c4ee01297e61 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 4 Jun 2021 12:28:49 -0700 Subject: [PATCH 0305/1276] Updated Mac Arm64 Build Output File (#2977) --- cla-backend-go/Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 6f09624a3..468f39d57 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -206,10 +206,10 @@ build-mac: deps env GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(SERVICE)-mac main.go @chmod +x $(SERVICE)-mac -build-mac-arm: deps +build-mac-arm64: deps @echo "Building Mac OSX Arm64 binary..." env GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(SERVICE)-mac-arm64 main.go - @chmod +x $(SERVICE)-mac + @chmod +x $(SERVICE)-mac-arm64 rebuild-mac: clean fmt build-mac lint ./$(SERVICE)-mac @@ -258,7 +258,6 @@ build-metrics-report-lambda-mac: deps env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(METRICS_REPORT_BIN)-mac cmd/metrics_report_lambda/main.go @chmod +x $(METRICS_REPORT_BIN)-mac - build-dynamo-events-lambda: build-dynamo-events-lambda-linux build-dynamo-events-lambda-linux: deps @echo "Building a statically linked Linux amd64 binary..." From 613d38c25728c7c96adae24255f0dfe5be249aa4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 7 Jun 2021 11:47:04 -0700 Subject: [PATCH 0306/1276] Documentation Updates (#2978) --- README.md | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 425dee4ee..97e882b36 100644 --- a/README.md +++ b/README.md @@ -16,30 +16,45 @@ This platform supports both GitHub and Gerrit source code repositories. Addition [EasyCLA](#easycla-architecture) -Besides integration with Auth0 and Salesforce, the CLA system has the following third party services: +The EasyCL system leverages the following third party services: * [Docusign](https://www.docusign.com/) for CLA agreement e-sign flow -* [Docraptor](https://docraptor.com/) for converting html CLA template to PDF file +* [Docraptor](https://docraptor.com/) for converting CLA templates into PDF files +* [GitHub](https://github.com/) for GitHub PR CLA authorization checking/gating +* Gerrit for CLA authorization review checking/gating +* Auth0 For Single Sign On +* Salesforce via the LFX Platform APIs ## CLA Backend The CLA project has two backend components: -* The majority of the backend APIs are implemented in python, and can be found in the [cla-backend](cla-backend/) directory. - -* Recent backend development is implemented in Golang, and can be found in the -[cla-backend-go](cla-backend-go/) directory. In particular, this backend contains APIs powering -Automated Templates, GitHub Approval Lists, and Duplicate Company handling in the -Corporate Console. +* Python - some older APIs are implemented in python and can be found in the [cla-backend](cla-backend) directory. +* GoLang - Most of the backend development is implemented in Golang, and can be found in the + [cla-backend-go](cla-backend-go) directory. In particular, this backend contains APIs powering most of the v2 APIs + which integrate with the LFX Platform (including Salesforce data), and the LFX platform permissions model. ## CLA Frontend -CLA frontend consists of three independent SPA built with [Ionic](https://ionicframework.com/) framework. +For EasyCLA version 2, the various consoles are managed in separate repositories. + +* [Project Control Center](https://projectadmin.lfx.linuxfoundation.org/) contains all the old v1 Project Console + capabilities plus many new features. This new console includes not only the EasyCLA components, but also the project + related features for LF ITX and other LFX Platform projects. +* [Corporate Console](https://organization.lfx.linuxfoundation.org/company/dashboard) contains the old v1 Company Console + capabilities. This new console includes not only the EasyCLA components, but also the company related features for LF + ITX and other LFX Platform projects. +* [Contributor Console](https://github.com/communitybridge/easycla-contributor-console) contains the old v1 Contributor Console + capabilities with new features that integrate with the LFX Platform (including the Salesforce data). -* [cla-frontend-project-console](cla-frontend-project-console/) for the LinuxFoundation director/admin/user to manage project CLA -* [cla-frontend-corporate-console](cla-frontend-corporate-console/) for any concrete company CCLA manager to sign a CCLA and manage employee CLA approved list +For EasyCLA version 1, the consoles are: + +* [cla-frontend-project-console](cla-frontend-project-console) for the LinuxFoundation director/admin/user to manage project CLA +* [cla-frontend-corporate-console](cla-frontend-corporate-console) for any concrete company CCLA manager to sign a CCLA and manage employee CLA approved list * [cla-frontend-contributor-console](cla-frontend-contributor-console) for any project contributor to sign ICLA or CCLA +These CLA frontend components of three independent SPA built with [Ionic](https://ionicframework.com/) framework. + ## EasyCLA Architecture The following diagram explains the EasyCLA architecture. From a35a7dfd5cf2223a492a8dc5b4127ea2879241b7 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 7 Jun 2021 17:20:52 -0700 Subject: [PATCH 0307/1276] Minor README Update (#2979) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97e882b36..22e37ca71 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The CLA project has two backend components: ## CLA Frontend -For EasyCLA version 2, the various consoles are managed in separate repositories. +For EasyCLA version 2, all three consoles are hosted in separate repositories. * [Project Control Center](https://projectadmin.lfx.linuxfoundation.org/) contains all the old v1 Project Console capabilities plus many new features. This new console includes not only the EasyCLA components, but also the project From b729646c5c2bee47b4c56d15492763b0e39db042 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 8 Jun 2021 11:45:52 -0700 Subject: [PATCH 0308/1276] Resolved V1 Signature Query Issue (#2983) --- cla-backend-go/signatures/repository.go | 45 +++++++++++++------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index dc3a0f6ad..bf63f8f91 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -718,15 +718,14 @@ func (repo repository) GetSignatureACL(ctx context.Context, signatureID string) return dbModel.SignatureACL, nil } +// Adds the specified expression to the current filter using the And operator. This routine checks the filter added flag +// to determine if a previous filter was set. After this function executes, the filterAdded value will be set to true. func addAndCondition(filter expression.ConditionBuilder, cond expression.ConditionBuilder, filterAdded *bool) expression.ConditionBuilder { - if !(*filterAdded) { - *filterAdded = true - filter = cond - } else { - filter = filter.And(cond) + if *filterAdded { + return filter.And(cond) } *filterAdded = true - return filter + return cond } // GetProjectSignatures returns a list of signatures for the specified project @@ -743,8 +742,6 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur "pageSize": aws.Int64Value(params.PageSize), "nextKey": aws.StringValue(params.NextKey), "sortOrder": aws.StringValue(params.SortOrder), - "approved": utils.BoolValue(params.Approved), - "signed": utils.BoolValue(params.Signed), } // Always sort by date @@ -757,42 +754,48 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // This is the key we want to match condition := expression.Key("signature_project_id").Equal(expression.Value(params.ProjectID)) + builder := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildProjection()) - builder := expression.NewBuilder().WithProjection(buildProjection()) var filter expression.ConditionBuilder - var filterAdded bool + var filterAdded = false if params.ClaType != nil { - filterAdded = true if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { + log.WithFields(f).Debugf("adding filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { + log.WithFields(f).Debugf("adding filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). And(expression.Name("signature_user_ccla_company_id").AttributeExists()) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { + log.WithFields(f).Debugf("adding filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCCLA, utils.SignatureReferenceTypeCompany) filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)). And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany))). And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) } } else { if params.SearchField != nil { + log.WithFields(f).Debugf("adding filters: signature_type: %s", expression.Value(params.SearchField)) searchFieldExpression := expression.Name("signature_reference_type").Equal(expression.Value(params.SearchField)) filter = addAndCondition(filter, searchFieldExpression, &filterAdded) } if params.SignatureType != nil { if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" && (params.FullMatch != nil && !*params.FullMatch) { + log.WithFields(f).Debugf("adding filters: signature_type: %s", strings.ToLower(utils.StringValue(params.SignatureType))) indexName = SignatureProjectIDTypeIndex - condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(*params.SignatureType)))) + condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(utils.StringValue(params.SignatureType))))) } else { - signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(params.SignatureType)) + log.WithFields(f).Debugf("adding filters: signature_type: %s", utils.StringValue(params.SignatureType)) + signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(utils.StringValue(params.SignatureType))) filter = addAndCondition(filter, signatureTypeExpression, &filterAdded) } if *params.SignatureType == utils.ClaTypeCCLA { + log.WithFields(f).Debug("adding filters: signature_reference_id: exists, signature_user_ccla_company_id: not exists") signatureReferenceIDExpression := expression.Name("signature_reference_id").AttributeExists() signatureUserCclaCompanyIDExpression := expression.Name("signature_user_ccla_company_id").AttributeNotExists() filter = addAndCondition(filter, signatureReferenceIDExpression, &filterAdded) @@ -815,13 +818,11 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } if params.Approved != nil { - filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(params.Approved)) searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if params.Signed != nil { - filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(params.Signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) filter = addAndCondition(filter, searchTermExpression, &filterAdded) @@ -829,22 +830,22 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { - filterAdded = true log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) - filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(true)). + And(expression.Name("signature_signed").Equal(expression.Value(true))) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) } + log.WithFields(f).Debugf("filterAdded: %t", filterAdded) if filterAdded { + log.WithFields(f).Debugf("filter: %+v", filter) builder = builder.WithFilter(filter) } - builder = builder.WithKeyCondition(condition) - // Use the nice builder to create the expression + // Use the builder to create the expression expr, err := builder.Build() if err != nil { - log.WithFields(f).Warnf("error building expression for project signature query, projectID: %s, error: %v", - params.ProjectID, err) + log.WithFields(f).WithError(err).Warnf("error building expression for project signature query, projectID: %s, error: %v", params.ProjectID, err) return nil, err } From aead96ad5d11524346efe4a8255eaf0ac3d0da64 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 9 Jun 2021 17:58:57 +0300 Subject: [PATCH 0309/1276] Feature/ Get GH Org (#2984) --- cla-backend-go/github_organizations/repository.go | 3 +++ cla-backend/cla/models/dynamo_models.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index 21ed9e985..9d12823ab 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -199,6 +199,9 @@ func (repo repository) GetGithubOrganizations(ctx context.Context, projectSFID s condition := expression.Key("project_sfid").Equal(expression.Value(projectSFID)) builder := expression.NewBuilder().WithKeyCondition(condition) + filter := expression.Name("enabled").Equal(expression.Value(true)) + builder = builder.WithFilter(filter) + // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index a462d4eeb..c1d2d342f 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -3526,6 +3526,7 @@ class Meta: organization_company_id = UnicodeAttribute(null=True) auto_enabled = BooleanAttribute(null=True) branch_protection_enabled = BooleanAttribute(null=True) + enabled = BooleanAttribute(null=True) note = UnicodeAttribute(null=True) @@ -3536,7 +3537,7 @@ class GitHubOrg(model_interfaces.GitHubOrg): # pylint: disable=too-many-public- def __init__( self, organization_name=None, organization_installation_id=None, organization_sfid=None, - auto_enabled=False, branch_protection_enabled=False, note=None, + auto_enabled=False, branch_protection_enabled=False, note=None, enabled=True ): super(GitHubOrg).__init__() self.model = GitHubOrgModel() @@ -3548,6 +3549,7 @@ def __init__( self.model.auto_enabled = auto_enabled self.model.branch_protection_enabled = branch_protection_enabled self.model.note = note + self.model.enabled = enabled def __str__(self): return ( @@ -3559,6 +3561,7 @@ def __str__(self): f'auto_enabled: {self.model.auto_enabled},' f'branch_protection_enabled: {self.model.branch_protection_enabled},' f'note: {self.model.note}' + f'enabled: {self.model.enabled}' ) def to_dict(self): @@ -3611,6 +3614,9 @@ def get_note(self): :rtype: str """ return self.model.note + + def get_enabled(self): + return self.model.enabled def set_organization_name(self, organization_name): self.model.organization_name = organization_name @@ -3640,6 +3646,9 @@ def set_branch_protection_enabled(self, branch_protection_enabled): def set_note(self, note): self.model.note = note + + def set_enabled(self, enabled): + self.model.enabled = enabled def get_organization_by_sfid(self, sfid) -> List: organization_generator = self.model.organization_sfid_index.query(sfid) From 08aa27e83c11d2cead6ed84ee479112f69ecc8c2 Mon Sep 17 00:00:00 2001 From: Steve Winslow Date: Thu, 10 Jun 2021 11:01:02 -0400 Subject: [PATCH 0310/1276] Fix typo in description of licenses (#2986) --- contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 66e596871..7b7df513d 100644 --- a/contributing.md +++ b/contributing.md @@ -16,7 +16,7 @@ New **code files** should include a [short-form SPDX ID](https://spdx.org/ids) a // SPDX-License-Identifier: MIT ``` -New **documentation files** should include a [short-form SPDX ID](https://spdx.org/ids) at the top, indicating the project license for code, which is CC-BY-4.0. This should look like the following: +New **documentation files** should include a [short-form SPDX ID](https://spdx.org/ids) at the top, indicating the project license for documentation, which is CC-BY-4.0. This should look like the following: ```text SPDX-License-Identifier: CC-BY-4.0 From 0b6fc5e5b1ca952655d54b56abd1d0842ccf81b1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 11 Jun 2021 10:50:55 -0700 Subject: [PATCH 0311/1276] Removed Landing Page Request Access Button/Link (#2987) Signed-off-by: David Deal --- .../cla-console-section.component.html | 5 ---- .../cla-console-section.component.ts | 25 ++++++++----------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index 65907e703..2115f109d 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -35,11 +35,6 @@
    -
    - -
    -
    diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index 2eb973ede..9d0d38c99 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -11,10 +11,11 @@ import { LandingPageService } from 'src/app/service/landing-page.service'; import { environment } from 'src/environments/environment'; declare let process: any; + @Component({ selector: 'app-cla-console-section', templateUrl: './cla-console-section.component.html', - styleUrls: ['./cla-console-section.component.scss'] + styleUrls: ['./cla-console-section.component.scss'], }) export class ClaConsoleSectionComponent implements OnInit { @Input() consoleMetadata: any; @@ -32,7 +33,7 @@ export class ClaConsoleSectionComponent implements OnInit { private storageService: StorageService, private router: ActivatedRoute, private modalService: NgbModal, - private landingPageService: LandingPageService + private landingPageService: LandingPageService, ) { if (!this.landingPageService.hasEventInitilize) { this.landingPageService.hasEventInitilize = true; @@ -51,7 +52,7 @@ export class ClaConsoleSectionComponent implements OnInit { this.version = this.router.snapshot.queryParamMap.get('version'); const element: any = document.getElementById('lfx-header'); let projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK] + '#/login'; - let corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK] + '#/login' + let corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK] + '#/login'; if (this.version === '2') { // Set redirect URL to new V2 console. projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK_V2]; @@ -60,16 +61,16 @@ export class ClaConsoleSectionComponent implements OnInit { this.links = [ { title: 'Project Login', - emit: "project-login-event" + emit: 'project-login-event', }, { title: 'CLA Manager Login', - emit: "corporate-login-event" + emit: 'corporate-login-event', }, { title: 'Developer', - url: AppSettings.CONTRIBUTORS_LEARN_MORE - } + url: AppSettings.CONTRIBUTORS_LEARN_MORE, + }, ]; element.links = this.links; } @@ -92,14 +93,10 @@ export class ClaConsoleSectionComponent implements OnInit { this.modelRef = this.modalService.open(this.versionModal, { centered: true, backdrop: 'static', - keyboard: false + keyboard: false, }); } - onClickRequestAccess() { - window.open(AppSettings.REQUEST_ACCESS_LINK, '_blank'); - } - onClickLearnMore() { window.open(AppSettings.CONTRIBUTORS_LEARN_MORE, '_blank'); } @@ -110,7 +107,7 @@ export class ClaConsoleSectionComponent implements OnInit { onClickVersionProceed() { if (this.selectedVersion === '') { - this.error = 'Please select a EasyCLA Version.' + this.error = 'Please select a EasyCLA Version.'; } else { this.redirectAsPerTypeAndVersion(this.consoleType, this.selectedVersion); } @@ -118,7 +115,7 @@ export class ClaConsoleSectionComponent implements OnInit { redirectAsPerTypeAndVersion(type: string, version: string) { let projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK] + '#/login'; - let corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK] + '#/login' + let corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK] + '#/login'; if (version === '2') { projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK_V2]; From 8d980ed0aa04072a23ff84e6bca7fe63dfb8f0bf Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 11 Jun 2021 13:19:46 -0700 Subject: [PATCH 0312/1276] Resolved PCC-1134 - Updated Signature Tall Query (#2988) --- cla-backend-go/signatures/repository.go | 138 +++++++++++++----------- cla-backend-go/v2/cla_groups/service.go | 25 ++++- 2 files changed, 96 insertions(+), 67 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index bf63f8f91..6f51ab76b 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -460,9 +460,10 @@ func (repo repository) GetIndividualSignature(ctx context.Context, claGroupID, u // These are the keys we want to match for an ICLA Signature with a given CLA Group and User ID condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(userID))) - filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + var filter expression.ConditionBuilder + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) if approved != nil { filterAdded = true @@ -573,21 +574,20 @@ func (repo repository) GetCorporateSignature(ctx context.Context, claGroupID, co // These are the keys we want to match for an CCLA Signature with a given CLA Group and Company ID condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)). And(expression.Key("signature_reference_id").Equal(expression.Value(companyID))) - filter := expression.Name("signature_type").Equal(expression.Value("ccla")). - And(expression.Name("signature_reference_type").Equal(expression.Value("company"))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + var filter expression.ConditionBuilder + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) if approved != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(approved)) - searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))) - filter = addAndCondition(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(approved))), &filterAdded) } if signed != nil { filterAdded = true log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(signed)) - searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))) - filter = addAndCondition(filter, searchTermExpression, &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(signed))), &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters @@ -761,21 +761,20 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur if params.ClaType != nil { if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { - log.WithFields(f).Debugf("adding filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) - filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) - + log.WithFields(f).Debugf("adding ICLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { - log.WithFields(f).Debugf("adding filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) - filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_user_ccla_company_id").AttributeExists()) + log.WithFields(f).Debugf("adding ECLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeExists(), &filterAdded) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { - log.WithFields(f).Debugf("adding filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCCLA, utils.SignatureReferenceTypeCompany) - filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + log.WithFields(f).Debugf("adding CCLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCCLA, utils.SignatureReferenceTypeCompany) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) } } else { if params.SearchField != nil { @@ -818,27 +817,24 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur } if params.Approved != nil { - log.WithFields(f).Debugf("adding filter signature_approved: %t", aws.BoolValue(params.Approved)) - searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(aws.BoolValue(params.Approved))) + log.WithFields(f).Debugf("adding signature_approved: %t filter", aws.BoolValue(params.Approved)) + searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(params.Approved)) filter = addAndCondition(filter, searchTermExpression, &filterAdded) } if params.Signed != nil { - log.WithFields(f).Debugf("adding filter signature_signed: %t", aws.BoolValue(params.Signed)) - searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(aws.BoolValue(params.Signed))) + log.WithFields(f).Debugf("adding signature_signed: %t filter", aws.BoolValue(params.Signed)) + searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(params.Signed)) filter = addAndCondition(filter, searchTermExpression, &filterAdded) } // If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters if params.Approved == nil && params.Signed == nil && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive { - log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true") - searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(true)). - And(expression.Name("signature_signed").Equal(expression.Value(true))) - filter = addAndCondition(filter, searchTermExpression, &filterAdded) + log.WithFields(f).Debug("adding signature_approved: true and signature_signed: true filters") + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) } - log.WithFields(f).Debugf("filterAdded: %t", filterAdded) if filterAdded { - log.WithFields(f).Debugf("filter: %+v", filter) builder = builder.WithFilter(filter) } @@ -883,6 +879,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur params.ProjectID, errQuery) return nil, errQuery } + log.WithFields(f).Debugf("returned %d results", len(results.Items)) // Convert the list of DB models to a list of response models signatureList, modelErr := repo.buildProjectSignatureModels(ctx, results, params.ProjectID, LoadACLDetails) @@ -895,7 +892,6 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // Add to the signatures response model to the list sigs = append(sigs, signatureList...) - //log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey) if results.LastEvaluatedKey["signature_id"] != nil { lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S queryInput.ExclusiveStartKey = results.LastEvaluatedKey @@ -935,6 +931,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur log.WithFields(f).Debugf("lastEvaluatedKey encoded is: %s", encodedString) } + log.WithFields(f).Debugf("returning %d signatures for CLA Group ID: %s", len(sigs), params.ProjectID) return &models.Signatures{ ProjectID: params.ProjectID, ResultCount: int64(len(sigs)), @@ -983,18 +980,20 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si if params.ClaType != nil { filterAdded = true if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { - filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) - + log.WithFields(f).Debugf("adding ICLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { - filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_user_ccla_company_id").AttributeExists()) + log.WithFields(f).Debugf("adding ECLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeExists(), &filterAdded) } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { - filter = expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + log.WithFields(f).Debugf("adding CCLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCCLA, utils.SignatureReferenceTypeCompany) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) } } else { if params.SearchField != nil { @@ -1226,8 +1225,10 @@ func (repo repository) GetProjectCompanySignatures(ctx context.Context, companyI //condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)) condition := expression.Key("signature_project_id").Equal(expression.Value(projectID)). And(expression.Key("signature_reference_id").Equal(expression.Value(companyID))) - filter := expression.Name("signature_type").Equal(expression.Value("ccla")). - And(expression.Name("signature_reference_type").Equal(expression.Value("company"))) + var filter expression.ConditionBuilder + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) if approved != nil { filterAdded = true @@ -1505,18 +1506,22 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, // This is the keys we want to match condition := expression.Key("signature_user_ccla_company_id").Equal(expression.Value(params.CompanyID)).And( expression.Key("signature_project_id").Equal(expression.Value(params.ProjectID))) + + var filterAdded bool + var filter expression.ConditionBuilder + // Check for approved signatures - filter := expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))) + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) if criteria != nil && criteria.GitHubUsername != "" { log.WithFields(f).Debugf("adding Githubusername criteria filter for :%s ", criteria.GitHubUsername) - filter = filter.And(expression.Name("user_github_username").Equal(expression.Value(criteria.GitHubUsername))) + filter = addAndCondition(filter, expression.Name("user_github_username").Equal(expression.Value(criteria.GitHubUsername)), &filterAdded) } if criteria != nil && criteria.UserEmail != "" { log.WithFields(f).Debugf("adding useremail criteria filter for : %s ", criteria.UserEmail) - filter = filter.And(expression.Name("user_email").Equal(expression.Value(criteria.UserEmail))) + filter = addAndCondition(filter, expression.Name("user_email").Equal(expression.Value(criteria.UserEmail)), &filterAdded) } // Use the nice builder to create the expression @@ -1630,12 +1635,13 @@ func (repo repository) GetCompanySignatures(ctx context.Context, params signatur // This is the keys we want to match condition := expression.Key("signature_reference_id").Equal(expression.Value(params.CompanyID)) - // Check for approved signatures - filter := expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))) + var filterAdded bool + var filter expression.ConditionBuilder + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) if params.SignatureType != nil { - filter = filter.And(expression.Name("signature_type").Equal(expression.Value(*params.SignatureType))) + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(*params.SignatureType)), &filterAdded) } // Use the nice builder to create the expression @@ -1754,10 +1760,16 @@ func (repo repository) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Co // These are the keys we want to match condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)) - filter := expression.Name("signature_type").Equal(expression.Value("ccla")). - And(expression.Name("signature_reference_type").Equal(expression.Value("company"))). - And(expression.Name("signature_signed").Equal(expression.Value(aws.Bool(true)))). - And(expression.Name("signature_approved").Equal(expression.Value(aws.Bool(true)))) + + var filterAdded bool + var filter expression.ConditionBuilder + + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) + // Check for approved signatures + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) // Batch size limit := int64(100) @@ -2958,11 +2970,13 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID "searchTerm": utils.StringValue(searchTerm), } - var filterAdded bool condition := expression.Key("signature_project_id").Equal(expression.Value(claGroupID)) - filter := expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)). - And(expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser))). - And(expression.Name("signature_user_ccla_company_id").AttributeNotExists()) + + var filter expression.ConditionBuilder + var filterAdded bool + filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) if approved != nil { f["approved"] = utils.BoolValue(approved) diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index f333dc930..b70f919f7 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -458,15 +458,29 @@ func (s *service) loadMetrics(ctx context.Context, f logrus.Fields, responseMode for idx, responseEntry := range responseModel.List { go func(index int, responseEntry *models.ClaGroupSummary) { - log.WithFields(f).Debugf("fetching project signature metrics for CLA Group (%d): %s - %s", index, responseEntry.ClaGroupID, responseEntry.ClaGroupName) - iclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeICLA), SignatureType: aws.String(utils.SignatureTypeCLA)}) + log.WithFields(f).Debugf("loading project signature metrics for CLA Group (idx:%d): %s - %s", index, responseEntry.ClaGroupID, responseEntry.ClaGroupName) + iclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, + signatures.GetProjectSignaturesParams{ + Approved: utils.Bool(true), + ClaType: aws.String(utils.ClaTypeICLA), + ProjectID: responseEntry.ClaGroupID, + Signed: utils.Bool(true), + }, + ) if err != nil { - log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + log.WithFields(f).WithError(err).Warnf("error while getting ICLA Signature using CLA Group ID %s Error: %v", responseEntry.ClaGroupID, err) } - cclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, signatures.GetProjectSignaturesParams{ProjectID: responseEntry.ClaGroupID, ClaType: aws.String(utils.ClaTypeCCLA), SignatureType: aws.String(utils.SignatureTypeCCLA)}) + cclaSignatureDetails, err := s.signatureService.GetProjectSignatures(ctx, + signatures.GetProjectSignaturesParams{ + Approved: utils.Bool(true), + ProjectID: responseEntry.ClaGroupID, + ClaType: aws.String(utils.ClaTypeCCLA), + Signed: utils.Bool(true), + }, + ) if err != nil { - log.WithFields(f).Warnf("error while getting ICLA Signature using cla group ID %s Error: %v", responseEntry.ClaGroupID, err) + log.WithFields(f).WithError(err).Warnf("error while getting ICLA Signature using CLA Group ID %s Error: %v", responseEntry.ClaGroupID, err) } metricsResultChannel <- &MetricsResult{ @@ -482,6 +496,7 @@ func (s *service) loadMetrics(ctx context.Context, f logrus.Fields, responseMode for range responseModel.List { select { case response := <-metricsResultChannel: + log.WithFields(f).Debugf("Signature Metrics: CCLA Signatures: %d, ICLA Signatures: %d", response.cclaSignatureCount, response.iclaSignatureCount) responseModel.List[response.index].TotalSignatures = response.cclaSignatureCount + response.iclaSignatureCount case <-ctx.Done(): log.WithError(ctx.Err()).Warnf("waiting for metrics failed with timeout") From 7b933c24ba128cd27ae494b261fa6fd65ac12c9e Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 16 Jun 2021 11:22:35 -0700 Subject: [PATCH 0313/1276] Updated Documentation Links (#2994) --- docs/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0f59c3676..93a0c4f17 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,5 @@ # Documentation -User documentation for the EasyCLA application can be found -[under the Linux Foundation documentation pages](https://docs.linuxfoundation.org/lfx/easycla). - +User documentation for the EasyCLA application can be found: +- [EasyCLA v1 documentation](https://docs.linuxfoundation.org/lfx/easycla) +- [EasyCLA v2 documentation](https://docs.linuxfoundation.org/lfx/v/v2/easycla) From 91a71ab1fa6a8de25ba4ceffd7ef4a431f41a54c Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 17 Jun 2021 14:01:32 -0700 Subject: [PATCH 0314/1276] User Service Role Checks Cleanup/Debug (#2995) --- cla-backend/cla/user_service.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cla-backend/cla/user_service.py b/cla-backend/cla/user_service.py index da8c42a34..6cbc0397f 100644 --- a/cla-backend/cla/user_service.py +++ b/cla-backend/cla/user_service.py @@ -6,7 +6,6 @@ from typing import List from urllib.parse import quote - import requests import cla @@ -33,7 +32,7 @@ def get_user_by_sf_id(self, sf_user_id: str): Queries the platform user service for the specified user id. The result will return all the details for the user as a dictionary. """ - fn = 'user_service.get_user_by_sf_id' + fn = 'cla.user_service.get_user_by_sf_id' headers = { 'Authorization': f'bearer {self.get_access_token()}', @@ -58,7 +57,7 @@ def _get_users_by_key_value(self, key: str, value: str) -> List[dict]: The result will return summary information for the users as a dictionary. """ - fn = 'user_service._get_users_by_key_value' + fn = 'cla.user_service._get_users_by_key_value' headers = { 'Authorization': f'bearer {self.get_access_token()}', @@ -102,7 +101,7 @@ def get_users_by_lastname(self, last_name: str) -> List[dict]: def get_users_by_email(self, email: str) -> List[dict]: return self._get_users_by_key_value("email", email) - + def has_role(self, username: str, role: str, organization_id: str, cla_group_id: str) -> bool: """ Function that checks whether lf user has a role @@ -117,7 +116,7 @@ def has_role(self, username: str, role: str, organization_id: str, cla_group_id: :rtype: bool """ scopes = {} - function = 'has_role' + function = 'cla.user_service.has_role' scopes = self._list_org_user_scopes(organization_id, role) if scopes: log.info(f'{function} - Found scopes : {scopes} for organization: {organization_id}') @@ -144,7 +143,7 @@ def has_role(self, username: str, role: str, organization_id: str, cla_group_id: log.info(f'{function} - {username} does not have role scope') return False - + def _has_project_org_scope(self, project_sfid: str, organization_id: str, username: str, scopes: dict) -> bool: """ Helper function that checks whether there exists project_org_scope for given role @@ -158,23 +157,26 @@ def _has_project_org_scope(self, project_sfid: str, organization_id: str, userna :type scopes: dict :rtype: bool """ - function = '_has_project_org_scope_role' + function = 'cla.user_service._has_project_org_scope_role' try: user_roles = scopes['userroles'] - log.info(f'{function} - User roles: {user_roles}') + log.info(f'{function} - User roles for user: \'{username}\' are: {user_roles}') except KeyError as err: - log.warning(f'{function} - error: {err} ') + log.info(f'{function} - user: \'{username}\' scope does not have \'userroles\', error: {err} ' + f'Returning False.') return False + + # For each user role assigned to the user... for user_role in user_roles: + # If the username matches... if user_role['Contact']['Username'] == username: - #Since already filtered by role ...get first item + # Since already filtered by role ...get first item for scope in user_role['RoleScopes'][0]['Scopes']: log.info(f'{function}- Checking objectID for scope: {project_sfid}|{organization_id}') if scope['ObjectID'] == f'{project_sfid}|{organization_id}': return True return False - def _list_org_user_scopes(self, organization_id: str, role: str) -> dict: """ Helper function that lists the org_user_scopes for a given organization related to given role @@ -182,12 +184,10 @@ def _list_org_user_scopes(self, organization_id: str, role: str) -> dict: :type organization_id: string :param role: role to filter the user org scopes :type role: string - :param cla_group_id: cla_group_id thats mapped to salesforce projects - :type cla_group_id: string :return: json dict representing org user role scopes :rtype: dict """ - function = '_list_org_user_scopes' + function = 'cla.user_service._list_org_user_scopes' headers = { 'Authorization': f'bearer {self.get_access_token()}', 'accept': 'application/json' @@ -199,11 +199,12 @@ def _list_org_user_scopes(self, organization_id: str, role: str) -> dict: r = requests.get(url, headers=headers, params=params) return r.json() except requests.exceptions.HTTPError as err: - log.warning('%s - Could not get user org scopes for organization: %s with role: %s , error: %s ', function, organization_id, role, err) + log.warning('%s - Could not get user org scopes for organization: %s with role: %s , error: %s ', function, + organization_id, role, err) return None def get_access_token(self): - fn = 'user_service.get_access_token' + fn = 'cla.user_service.get_access_token' # Use previously cached value, if not expired if self.access_token and datetime.datetime.now() < self.access_token_expires: cla.log.debug(f'{fn} - using cached access token: {self.access_token[0:10]}...') From 8e41d329175c2d9f21f23e0ef1b2ce4e777a780a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Jun 2021 14:02:26 -0700 Subject: [PATCH 0315/1276] Bump glob-parent from 5.1.1 to 5.1.2 in /cla-backend (#2985) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index d672ee8df..9e8e865fb 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -2927,9 +2927,9 @@ glob-all@^3.1.0: yargs "^15.3.1" glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" From 96ef2e879fc9a8a3fec2cefc4540f56b320cf23f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Jun 2021 14:19:20 -0700 Subject: [PATCH 0316/1276] Bump glob-parent from 5.1.1 to 5.1.2 (#2989) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8e21dbb68..a72f22243 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2258,9 +2258,9 @@ github-from-package@0.0.0: integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" From e06a952b2179b6f7a5035b1ff880120ce32aa5eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Jun 2021 14:19:38 -0700 Subject: [PATCH 0317/1276] Bump glob-parent from 5.1.1 to 5.1.2 in /cla-backend-go (#2990) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index 1f3a8d7d4..a94e9c28c 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -2385,9 +2385,9 @@ github-from-package@0.0.0: integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" From ec984eb355e895832ce51579c68d5244c7e7864e Mon Sep 17 00:00:00 2001 From: Leopold Schabel Date: Sat, 19 Jun 2021 03:06:05 +0200 Subject: [PATCH 0318/1276] Fix GitHub typo in email notification (#2997) --- cla-backend-go/signatures/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index bfed7e6b6..3408a38a1 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -523,9 +523,9 @@ func buildApprovalListSummary(approvalListChanges *models.ApprovalList) string { approvalListSummary += appendList(approvalListChanges.RemoveEmailApprovalList, "Removed Email:") approvalListSummary += appendList(approvalListChanges.AddDomainApprovalList, "Added Domain:") approvalListSummary += appendList(approvalListChanges.RemoveDomainApprovalList, "Removed Domain:") - approvalListSummary += appendList(approvalListChanges.AddGithubUsernameApprovalList, "Added GithHub User:") + approvalListSummary += appendList(approvalListChanges.AddGithubUsernameApprovalList, "Added GitHub User:") approvalListSummary += appendList(approvalListChanges.RemoveGithubUsernameApprovalList, "Removed GitHub User:") - approvalListSummary += appendList(approvalListChanges.AddGithubOrgApprovalList, "Added GithHub Organization:") + approvalListSummary += appendList(approvalListChanges.AddGithubOrgApprovalList, "Added GitHub Organization:") approvalListSummary += appendList(approvalListChanges.RemoveGithubOrgApprovalList, "Removed GitHub Organization:") approvalListSummary += "" return approvalListSummary From 2835c0ad0e367af0c2183de7e04fceed60345092 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Sat, 19 Jun 2021 07:00:13 +0300 Subject: [PATCH 0319/1276] Feature/Enroll & Unenroll Projects (#2992) Co-authored-by: Harold Wanyama --- cla-backend-go/v2/cla_groups/helpers.go | 169 +++++++++++++++----- cla-backend-go/v2/cla_groups/models.go | 8 + cla-backend-go/v2/project-service/client.go | 33 ++++ 3 files changed, 169 insertions(+), 41 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index fd639b5f4..772568f01 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -21,6 +21,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" psproject "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/client/project" + v2ProjectServiceModels "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/models" "github.com/sirupsen/logrus" ) @@ -221,9 +222,22 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI return err } + foundationProjectSummary, err := psc.GetSummary(foundationSFID) + if err != nil { + log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) + return err + } + + // build Tree that tracks parent and child projects + projectTree, err := buildProjectTree(foundationProjectSummary) + if err != nil { + log.WithFields(f).Warnf("unable to process project summary :%+v ", foundationProjectSummary) + return err + } + // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) + isLFParent, err := psc.IsTheLinuxFoundation(projectTree.Parent.ID) if err != nil { log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) return err @@ -235,7 +249,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI return err } - if foundationProjectDetails.Parent != "" && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { + if projectTree.Parent != nil && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", foundationProjectDetails.Parent, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) @@ -245,26 +259,12 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI } // Check to see if all the provided enrolled projects are part of this foundation - foundationProjectIDList := utils.NewStringSet() - for _, pr := range foundationProjectDetails.Projects { - foundationProjectIDList.Add(pr.ID) - } - invalidProjectSFIDs := utils.NewStringSet() - for _, projectSFID := range projectSFIDList { - // Ok to have foundation ID in the project list - this means it's a Foundation Level CLA Group - if foundationSFID == projectSFID { - continue - } - // If the input/provided project ID is not in the SF project list... - if !foundationProjectIDList.Include(projectSFID) { - invalidProjectSFIDs.Add(projectSFID) - } - } + exists := projectsExist(projectTree, projectSFIDList) - if invalidProjectSFIDs.Length() != 0 { - log.WithFields(f).Warnf("validation failure - provided projects are not under the SF foundation: %+v", invalidProjectSFIDs.List()) - return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the SF foundation", invalidProjectSFIDs.List()) + if !exists { + log.WithFields(f).Warnf("validation failure - provided projects are not under the SF foundation: %+v", projectTree) + return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the SF foundation", projectTree) } // check if projects are not already enabled @@ -276,7 +276,8 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI for _, pr := range enabledProjects { enabledProjectList.Add(pr.ProjectSFID) } - invalidProjectSFIDs = utils.NewStringSet() + + invalidProjectSFIDs := utils.NewStringSet() for _, projectSFID := range projectSFIDList { // Ok to have foundation ID in the project list - no need to check if it's already in the sub-project enabled list if foundationSFID == projectSFID { @@ -323,6 +324,19 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } + foundationProjectSummary, err := psc.GetSummary(foundationSFID) + if err != nil { + log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) + return err + } + + // build Tree that tracks parent and child projects + projectTree, err := buildProjectTree(foundationProjectSummary) + if err != nil { + log.WithFields(f).Warnf("unable to process project summary :%+v ", foundationProjectSummary) + return err + } + // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) @@ -353,26 +367,11 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS } */ // Check to see if all the provided enrolled projects are part of this foundation - foundationProjectIDList := utils.NewStringSet() - for _, pr := range foundationProjectDetails.Projects { - foundationProjectIDList.Add(pr.ID) - } - invalidProjectSFIDs := utils.NewStringSet() - for _, projectSFID := range projectSFIDList { - // Ok to have foundation ID in the project list - this means it's a Foundation Level CLA Group - if foundationSFID == projectSFID { - continue - } - - // If the input/provided project ID is not in the SF project list... - if !foundationProjectIDList.Include(projectSFID) { - invalidProjectSFIDs.Add(projectSFID) - } - } + exists := projectsExist(projectTree, projectSFIDList) - if invalidProjectSFIDs.Length() != 0 { - log.WithFields(f).Warnf("validation failure - provided projects are not under the SF foundation: %+v", invalidProjectSFIDs.List()) - return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more of the provided projects are not under the SF foundation", invalidProjectSFIDs.List()) + if !exists { + log.WithFields(f).Warnf("validation failure - provided projects are not under the SF foundation: %+v", projectTree) + return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the SF foundation", projectTree) } // check if projects are already enrolled/enabled @@ -384,7 +383,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS for _, pr := range enabledProjects { enabledProjectList.Add(pr.ProjectSFID) } - invalidProjectSFIDs = utils.NewStringSet() + invalidProjectSFIDs := utils.NewStringSet() for _, projectSFID := range projectSFIDList { // Ok to have foundation ID in the project list - no need to check if it's already in the sub-project enabled list if foundationSFID == projectSFID { @@ -728,3 +727,91 @@ func getUniqueCLAGroupIDs(projectCLAGroupMappings []*projects_cla_groups.Project return keys } + +// buildTree helper function that builds tree based on nessted Projects +func buildProjectTree(projectSummaryList []*v2ProjectServiceModels.ProjectSummary) (*ProjectNode, error) { + f := logrus.Fields{ + "functionName": "v2.cla_groups.helpers.buildTree", + } + + log.WithFields(f).Debugf("Building project summary tree for : %+v ...", projectSummaryList) + var root ProjectNode + + if len(projectSummaryList) == 0 { + msg := "project summary list is empty" + log.WithFields(f).Debugf(msg) + return nil, errors.New(msg) + } + projectSummary := projectSummaryList[0] + // Get ParentProject ID + parentProjectID, err := v2ProjectService.GetClient().GetParentProject(projectSummary.ID) + if err != nil { + log.WithFields(f).Debugf("unable to get parent project for : %s ", projectSummary.ID) + } + + // Use Parent as root for projects to help in validation checks for enrolling / unenrolling + root = ProjectNode{ + Parent: nil, + ID: parentProjectID, + Children: []*ProjectNode{{ + ID: projectSummary.ID, + Name: projectSummary.Name, + }}, + } + + // Aggregate projects + for _, summary := range projectSummaryList { + for _, child := range root.Children { + child.addChildren(summary) + } + } + + return &root, nil +} + +func (n *ProjectNode) addChildren(summary *v2ProjectServiceModels.ProjectSummary) { + f := logrus.Fields{ + "functionName": "v2.cla_groups.helpers.addChidlren", + } + + log.WithFields(f).Debugf("Agrregating children for %+v ...", summary) + for _, subProject := range summary.Projects { + child := &ProjectNode{ + Parent: n, + ID: subProject.ID, + Name: subProject.Name, + } + n.Children = append(n.Children, child) + log.WithFields(f).Debugf("added child : %+v ", child) + } +} + +// findByID searches for given projectSFID recursively using DFS algorithm +func findByID(node *ProjectNode, projectSFID string) *ProjectNode { + f := logrus.Fields{ + "functionName": "v2.cla_groups.helpers.fundByID", + } + log.WithFields(f).Debugf("searching for :%s ...", projectSFID) + if node.ID == projectSFID { + return node + } + + if len(node.Children) > 0 { + for _, child := range node.Children { + findByID(child, projectSFID) + } + } + + return nil +} + +// projectsExist searches for given list of projects in foundation items +func projectsExist(node *ProjectNode, projectSFIDs []string) bool { + for _, projectSFID := range projectSFIDs { + found := findByID(node, projectSFID) + if found == nil { + return false + } + } + return true +} diff --git a/cla-backend-go/v2/cla_groups/models.go b/cla-backend-go/v2/cla_groups/models.go index 1473ea896..81834db28 100644 --- a/cla-backend-go/v2/cla_groups/models.go +++ b/cla-backend-go/v2/cla_groups/models.go @@ -38,3 +38,11 @@ type UnassociateCLAGroupWithProjectsModel struct { FoundationSFID string ProjectSFIDList []string } + +// ProjectNode representing nested projects +type ProjectNode struct { + Parent *ProjectNode + ID string + Name string + Children []*ProjectNode +} diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index a9072c2d9..e90795e7f 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -324,3 +324,36 @@ func (pmm *Client) DisableCLA(projectSFID string) error { } return pmm.updateEnabledServices(projectSFID, newEnabledServices, clientAuth) } + +//GetSummary gets projects tree heirachy and project details +func (pmm *Client) GetSummary(projectSFID string) ([]*models.ProjectSummary, error) { + f := logrus.Fields{ + "functionName": "v2.project-service.client.Summary", + "projectID": projectSFID, + } + + tok, err := token.GetToken() + if err != nil { + return nil, err + } + + clientAuth := runtimeClient.BearerToken(tok) + + filter := fmt.Sprintf("id eq %s", projectSFID) + log.WithFields(f).Debugf("Getting project summary for :%s ", projectSFID) + view := "pcc" + + params := &project.GetSummaryParams{ + DollarFilter: &filter, + View: &view, + } + + result, err := pmm.cl.Project.GetSummary(params, clientAuth) + + if err != nil { + log.WithFields(f).Debugf("unable to query project summary for : %s , error: %+v ", projectSFID, err) + return nil, err + } + + return result.Payload.Data, nil +} From 49c1129be338a2c92a107fe4396cf89e1bb4161d Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 21 Jun 2021 15:26:19 -0700 Subject: [PATCH 0320/1276] [PCC-1165] Added Context to Project Summary API Call (#3001) --- cla-backend-go/tests/project_service_test.go | 34 ++++++++++ cla-backend-go/v2/cla_groups/helpers.go | 10 +-- cla-backend-go/v2/project-service/client.go | 69 +++++++++++++------- 3 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 cla-backend-go/tests/project_service_test.go diff --git a/cla-backend-go/tests/project_service_test.go b/cla-backend-go/tests/project_service_test.go new file mode 100644 index 000000000..3ecd3f3b8 --- /dev/null +++ b/cla-backend-go/tests/project_service_test.go @@ -0,0 +1,34 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "os" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/communitybridge/easycla/cla-backend-go/token" + "github.com/communitybridge/easycla/cla-backend-go/utils" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestProjectServiceSummary(t *testing.T) { + var awsSession = session.Must(session.NewSession(&aws.Config{})) + stage := os.Getenv("STAGE") + assert.NotEmpty(t, stage) + configFile, err := config.LoadConfig("", awsSession, stage) + assert.Nil(t, err, "load config error") + token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) + project_service.InitClient(configFile.PlatformAPIGatewayURL) + + client := project_service.GetClient() + projectSummaryModel, err := client.GetSummary(utils.NewContext(), "a096s000000VluyAAC") + assert.Nil(t, err, "Error is nil") + assert.NotNil(t, projectSummaryModel, "Project Summary Response not nil") +} diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 772568f01..7496813b1 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -222,7 +222,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI return err } - foundationProjectSummary, err := psc.GetSummary(foundationSFID) + foundationProjectSummary, err := psc.GetSummary(ctx, foundationSFID) if err != nil { log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) return err @@ -324,7 +324,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } - foundationProjectSummary, err := psc.GetSummary(foundationSFID) + foundationProjectSummary, err := psc.GetSummary(ctx, foundationSFID) if err != nil { log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) return err @@ -516,7 +516,7 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, cla go func(psClient *v2ProjectService.Client, claGroupID, projectSFID string) { defer wg.Done() log.WithFields(f).Debugf("enabling project CLA service for project: %s...", projectSFID) - enableProjectErr := psClient.EnableCLA(projectSFID) + enableProjectErr := psClient.EnableCLA(ctx, projectSFID) if enableProjectErr != nil { log.WithFields(f).WithError(enableProjectErr).Warnf("unable to enable CLA service for project: %s, error: %+v", projectSFID, enableProjectErr) errorList = append(errorList, enableProjectErr) @@ -543,7 +543,7 @@ func (s *service) EnableCLAService(ctx context.Context, authUser *auth.User, cla log.WithFields(f).Debugf("skipping setting the enabled services on The Linux Foundation parent project(s) for parent project SFID: %s", parentProjectSFID) } else { log.WithFields(f).Debugf("enabling parent project CLA service for project SFID: %s...", parentProjectSFID) - enableProjectErr := psClient.EnableCLA(parentProjectSFID) + enableProjectErr := psClient.EnableCLA(ctx, parentProjectSFID) if enableProjectErr != nil { log.WithFields(f).WithError(enableProjectErr).Warnf("unable to enable CLA service for project: %s, error: %+v", parentProjectSFID, enableProjectErr) errorList = append(errorList, enableProjectErr) @@ -598,7 +598,7 @@ func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, cl // Execute as a go routine go func(psClient *v2ProjectService.Client, claGroupID, projectSFID string) { defer wg.Done() - disableProjectErr := psClient.DisableCLA(projectSFID) + disableProjectErr := psClient.DisableCLA(ctx, projectSFID) if disableProjectErr != nil { log.WithFields(f).WithError(disableProjectErr). Warnf("unable to disable CLA service for project: %s, error: %+v", projectSFID, disableProjectErr) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index e90795e7f..28bb7f54f 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -4,6 +4,7 @@ package project_service import ( + "context" "errors" "fmt" "strings" @@ -106,11 +107,12 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed } // GetProjectByName returns project details for the associated project name -func (pmm *Client) GetProjectByName(projectName string) (*models.ProjectListSearch, error) { +func (pmm *Client) GetProjectByName(ctx context.Context, projectName string) (*models.ProjectListSearch, error) { f := logrus.Fields{ - "functionName": "v2.project-service.client.GetProjectByName", - "projectName": projectName, - "apiGWHost": apiGWHost, + "functionName": "v2.project-service.client.GetProjectByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectName": projectName, + "apiGWHost": apiGWHost, } tok, err := token.GetToken() if err != nil { @@ -120,7 +122,8 @@ func (pmm *Client) GetProjectByName(projectName string) (*models.ProjectListSear clientAuth := runtimeClient.BearerToken(tok) result, err := pmm.cl.Project.SearchProjects(&project.SearchProjectsParams{ - Name: []string{projectName}, + Name: []string{projectName}, + Context: ctx, }, clientAuth) if err != nil { log.WithFields(f).WithError(err).Warning("problem searching projects by name") @@ -227,11 +230,12 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) } // EnableCLA enables CLA service in project-service -func (pmm *Client) EnableCLA(projectSFID string) error { +func (pmm *Client) EnableCLA(ctx context.Context, projectSFID string) error { f := logrus.Fields{ - "functionName": "v2.project-service.client.EnableCLA", - "projectSFID": projectSFID, - "apiGWHost": apiGWHost, + "functionName": "v2.project-service.client.EnableCLA", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } theLF, lookupErr := pmm.IsTheLinuxFoundation(projectSFID) @@ -267,33 +271,45 @@ func (pmm *Client) EnableCLA(projectSFID string) error { enabledServices := projectDetails.EnabledServices enabledServices = append(enabledServices, CLA) - return pmm.updateEnabledServices(projectSFID, enabledServices, clientAuth) + return pmm.updateEnabledServices(ctx, projectSFID, enabledServices, clientAuth) } -func (pmm *Client) updateEnabledServices(projectSFID string, enabledServices []string, clientAuth runtime.ClientAuthInfoWriter) error { +func (pmm *Client) updateEnabledServices(ctx context.Context, projectSFID string, enabledServices []string, clientAuth runtime.ClientAuthInfoWriter) error { + f := logrus.Fields{ + "functionName": "v2.project-service.client.updateEnabledServices", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "enabledServices": enabledServices, + "apiGWHost": apiGWHost, + } + params := project.NewUpdateProjectParams() params.ProjectID = projectSFID if len(enabledServices) == 0 { enabledServices = append(enabledServices, NA) } + params.Body = &models.ProjectInput{ ProjectCommon: models.ProjectCommon{ EnabledServices: enabledServices, }, } + _, err := pmm.cl.Project.UpdateProject(params, clientAuth) //nolint if err != nil { - return err + log.WithFields(f).WithError(err).Warnf("problem updating project enabled services") } + return err } // DisableCLA enables CLA service in project-service -func (pmm *Client) DisableCLA(projectSFID string) error { +func (pmm *Client) DisableCLA(ctx context.Context, projectSFID string) error { f := logrus.Fields{ - "functionName": "v2.project-service.client.DisableCLA", - "projectSFID": projectSFID, - "apiGWHost": apiGWHost, + "functionName": "v2.project-service.client.DisableCLA", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "apiGWHost": apiGWHost, } tok, err := token.GetToken() @@ -322,14 +338,15 @@ func (pmm *Client) DisableCLA(projectSFID string) error { // CLA already disabled return nil } - return pmm.updateEnabledServices(projectSFID, newEnabledServices, clientAuth) + return pmm.updateEnabledServices(ctx, projectSFID, newEnabledServices, clientAuth) } -//GetSummary gets projects tree heirachy and project details -func (pmm *Client) GetSummary(projectSFID string) ([]*models.ProjectSummary, error) { +//GetSummary gets projects tree hierarchy and project details +func (pmm *Client) GetSummary(ctx context.Context, projectSFID string) ([]*models.ProjectSummary, error) { f := logrus.Fields{ - "functionName": "v2.project-service.client.Summary", - "projectID": projectSFID, + "functionName": "v2.project-service.client.Summary", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": projectSFID, } tok, err := token.GetToken() @@ -342,16 +359,24 @@ func (pmm *Client) GetSummary(projectSFID string) ([]*models.ProjectSummary, err filter := fmt.Sprintf("id eq %s", projectSFID) log.WithFields(f).Debugf("Getting project summary for :%s ", projectSFID) view := "pcc" + offsetDefault := int64(0) + orderByDefault := string("createddate") + pageSizeDefault := int64(100) params := &project.GetSummaryParams{ DollarFilter: &filter, + MyProjects: nil, + Offset: &offsetDefault, + OrderBy: &orderByDefault, + PageSize: &pageSizeDefault, View: &view, + Context: ctx, // must set for the GetSummary API call, otherwise we get a Err:context.deadlineExceededError{} } result, err := pmm.cl.Project.GetSummary(params, clientAuth) if err != nil { - log.WithFields(f).Debugf("unable to query project summary for : %s , error: %+v ", projectSFID, err) + log.WithFields(f).WithError(err).Debugf("unable to query project summary for : %s , error: %+v ", projectSFID, err) return nil, err } From 571b317f97497c45e149866b30da20cc8cf4cf7b Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 21 Jun 2021 15:37:51 -0700 Subject: [PATCH 0321/1276] [PCC-1165] Added Context to Project Summary API Call (#3005) --- cla-backend-go/tests/project_service_test.go | 33 ++++++++++---------- cla-backend-go/v2/project-service/client.go | 3 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cla-backend-go/tests/project_service_test.go b/cla-backend-go/tests/project_service_test.go index 3ecd3f3b8..bec878f70 100644 --- a/cla-backend-go/tests/project_service_test.go +++ b/cla-backend-go/tests/project_service_test.go @@ -4,31 +4,32 @@ package tests import ( - "os" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/communitybridge/easycla/cla-backend-go/config" "github.com/communitybridge/easycla/cla-backend-go/token" "github.com/communitybridge/easycla/cla-backend-go/utils" project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" - - "testing" - "github.com/stretchr/testify/assert" + "os" + "testing" ) +var functionalTestEnabled = false + func TestProjectServiceSummary(t *testing.T) { - var awsSession = session.Must(session.NewSession(&aws.Config{})) - stage := os.Getenv("STAGE") - assert.NotEmpty(t, stage) - configFile, err := config.LoadConfig("", awsSession, stage) - assert.Nil(t, err, "load config error") - token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) - project_service.InitClient(configFile.PlatformAPIGatewayURL) + if functionalTestEnabled { // nolint + var awsSession = session.Must(session.NewSession(&aws.Config{})) + stage := os.Getenv("STAGE") + assert.NotEmpty(t, stage) + configFile, err := config.LoadConfig("", awsSession, stage) + assert.Nil(t, err, "load config error") + token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) + project_service.InitClient(configFile.PlatformAPIGatewayURL) - client := project_service.GetClient() - projectSummaryModel, err := client.GetSummary(utils.NewContext(), "a096s000000VluyAAC") - assert.Nil(t, err, "Error is nil") - assert.NotNil(t, projectSummaryModel, "Project Summary Response not nil") + client := project_service.GetClient() + projectSummaryModel, err := client.GetSummary(utils.NewContext(), "a096s000000VluyAAC") + assert.Nil(t, err, "Error is nil") + assert.NotNil(t, projectSummaryModel, "Project Summary Response not nil") + } } diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 28bb7f54f..e6d99a31a 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -7,9 +7,8 @@ import ( "context" "errors" "fmt" - "strings" - "github.com/sirupsen/logrus" + "strings" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" From a8cd3145de96c342578b790db0eb3feb36a7e55d Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 21 Jun 2021 16:55:37 -0700 Subject: [PATCH 0322/1276] Resolve Lint Issue (#3006) --- cla-backend-go/tests/project_service_test.go | 5 +++-- cla-backend-go/v2/project-service/client.go | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/tests/project_service_test.go b/cla-backend-go/tests/project_service_test.go index bec878f70..1b18f47bc 100644 --- a/cla-backend-go/tests/project_service_test.go +++ b/cla-backend-go/tests/project_service_test.go @@ -4,6 +4,9 @@ package tests import ( + "os" + "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/communitybridge/easycla/cla-backend-go/config" @@ -11,8 +14,6 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/stretchr/testify/assert" - "os" - "testing" ) var functionalTestEnabled = false diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index e6d99a31a..28bb7f54f 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -7,9 +7,10 @@ import ( "context" "errors" "fmt" - "github.com/sirupsen/logrus" "strings" + "github.com/sirupsen/logrus" + log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" From 4e75671ebe3d3b01eb767d3e43b720cc113185da Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 22 Jun 2021 11:49:04 -0700 Subject: [PATCH 0323/1276] [PCC-1165] Resolved Helper Invalid Memory Address (#3007) --- cla-backend-go/.golangci.yaml | 4 +- .../cla_manager/cla_manager.go | 2 +- cla-backend-go/events/mockrepo.go | 2 +- .../branch_protection/protected_branch_v4.go | 21 +++++----- .../github_organizations/handlers.go | 2 +- .../github_organizations/repository.go | 32 +++++++++------ .../github_organizations/service.go | 33 +++++++++------ cla-backend-go/signatures/repository.go | 6 +-- cla-backend-go/template/handlers.go | 2 +- cla-backend-go/template/repository.go | 33 +++++++-------- cla-backend-go/template/service.go | 31 +++++++------- cla-backend-go/user/repository.go | 21 +++++----- cla-backend-go/user/service.go | 4 +- cla-backend-go/v2/cla_groups/helpers.go | 12 ++++-- cla-backend-go/v2/cla_groups/service.go | 4 +- cla-backend-go/v2/dynamo_events/autoenable.go | 4 +- cla-backend-go/v2/dynamo_events/service.go | 4 +- cla-backend-go/v2/dynamo_events/signatures.go | 7 ++-- cla-backend-go/v2/github_activity/service.go | 6 +-- .../v2/github_organizations/service.go | 6 +-- cla-backend-go/v2/signatures/handlers.go | 4 +- cla-backend-go/v2/signatures/service.go | 40 +++++++++---------- cla-backend-go/v2/template/handlers.go | 2 +- 23 files changed, 153 insertions(+), 129 deletions(-) diff --git a/cla-backend-go/.golangci.yaml b/cla-backend-go/.golangci.yaml index d7b828cf9..29ee03471 100644 --- a/cla-backend-go/.golangci.yaml +++ b/cla-backend-go/.golangci.yaml @@ -34,7 +34,7 @@ linters-settings: check-blank: true govet: check-shadowing: true - golint: + revive: min-confidence: 0 dupl: threshold: 100 @@ -48,7 +48,7 @@ linters-settings: linters: disable-all: true enable: - - golint + - revive - govet - errcheck - deadcode diff --git a/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go b/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go index a7edc9b98..fa015d3ab 100644 --- a/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go +++ b/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go @@ -16,7 +16,7 @@ import ( var ( claManagerToken string claProspectiveManagerToken string - claManagerCreateRequestID string = "no-set" + claManagerCreateRequestID = "no-set" ) const ( diff --git a/cla-backend-go/events/mockrepo.go b/cla-backend-go/events/mockrepo.go index 61c647466..82f9025a8 100644 --- a/cla-backend-go/events/mockrepo.go +++ b/cla-backend-go/events/mockrepo.go @@ -88,7 +88,7 @@ func (repo *mockRepository) LogEventWithContext(ctx context.Context, args *LogEv var events []*models.Event // NewMockRepository creates a new instance of the mock event repository -func NewMockRepository() *mockRepository { +func NewMockRepository() *mockRepository { // nolint return &mockRepository{} } diff --git a/cla-backend-go/github/branch_protection/protected_branch_v4.go b/cla-backend-go/github/branch_protection/protected_branch_v4.go index 20ce3ca71..935354870 100644 --- a/cla-backend-go/github/branch_protection/protected_branch_v4.go +++ b/cla-backend-go/github/branch_protection/protected_branch_v4.go @@ -76,22 +76,24 @@ type UpdateRepoBranchProtectionMutation struct { } `graphql:"updateBranchProtectionRule(input: $input)"` } -type branchProtectionRepositoryV4 struct { +// BranchProtectionRepositoryV4 wraps a v4 github client +type BranchProtectionRepositoryV4 struct { client *githubv4.Client } -// NewBranchProtectionRepositoryV4 creates a new branchProtectionRepositoryV4 -func NewBranchProtectionRepositoryV4(installationID int64) (*branchProtectionRepositoryV4, error) { +// NewBranchProtectionRepositoryV4 creates a new BranchProtectionRepositoryV4 +func NewBranchProtectionRepositoryV4(installationID int64) (*BranchProtectionRepositoryV4, error) { client, clientErr := github.NewGithubV4AppClient(installationID) if clientErr != nil { return nil, clientErr } - return &branchProtectionRepositoryV4{ + return &BranchProtectionRepositoryV4{ client: client, }, nil } -func (r *branchProtectionRepositoryV4) GetRepositoryBranchProtections(ctx context.Context, repositoryOwner, repositoryName string) (*RepoBranchProtectionQueryResult, error) { +// GetRepositoryBranchProtections returns the repository branch protections for the specified repository +func (r *BranchProtectionRepositoryV4) GetRepositoryBranchProtections(ctx context.Context, repositoryOwner, repositoryName string) (*RepoBranchProtectionQueryResult, error) { var queryResult RepoBranchProtectionQueryResult variables := map[string]interface{}{ @@ -107,7 +109,8 @@ func (r *branchProtectionRepositoryV4) GetRepositoryBranchProtections(ctx contex return &queryResult, nil } -func (r *branchProtectionRepositoryV4) CreateBranchProtection(ctx context.Context, input *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) { +// CreateBranchProtection creates the repository branch protections for the specified repository +func (r *BranchProtectionRepositoryV4) CreateBranchProtection(ctx context.Context, input *githubv4.CreateBranchProtectionRuleInput) (*CreateRepoBranchProtectionMutation, error) { var createMutationResult CreateRepoBranchProtectionMutation err := r.client.Mutate(ctx, &createMutationResult, *input, nil) if err != nil { @@ -116,7 +119,8 @@ func (r *branchProtectionRepositoryV4) CreateBranchProtection(ctx context.Contex return &createMutationResult, nil } -func (r *branchProtectionRepositoryV4) UpdateBranchProtection(ctx context.Context, input *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) { +// UpdateBranchProtection updates the repository branch protections for the specified repository +func (r *BranchProtectionRepositoryV4) UpdateBranchProtection(ctx context.Context, input *githubv4.UpdateBranchProtectionRuleInput) (*UpdateRepoBranchProtectionMutation, error) { var updateMutationResult UpdateRepoBranchProtectionMutation err := r.client.Mutate(ctx, &updateMutationResult, *input, nil) if err != nil { @@ -126,7 +130,7 @@ func (r *branchProtectionRepositoryV4) UpdateBranchProtection(ctx context.Contex } // GetRepositoryIDFromName when provided the organization and repository name, returns the repository ID -func (r *branchProtectionRepositoryV4) GetRepositoryIDFromName(ctx context.Context, repositoryOwner, repositoryName string) (string, error) { +func (r *BranchProtectionRepositoryV4) GetRepositoryIDFromName(ctx context.Context, repositoryOwner, repositoryName string) (string, error) { // Define the graphql query //"query": "query{repository(name: \"test1\", owner: \"deal-test-org\") {id}}" @@ -155,6 +159,5 @@ func (r *branchProtectionRepositoryV4) GetRepositoryIDFromName(ctx context.Conte // EnableBranchProtectionForPattern enables branch protection for the given branch protection input func EnableBranchProtectionForPattern(ctx context.Context, repositoryOwner, repositoryName string, input *BranchProtectionRule) error { - return nil } diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index c9f1b690c..85495681c 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -22,7 +22,7 @@ import ( ) // Configure setups handlers on api with service -func Configure(api *operations.ClaAPI, service Service, eventService events.Service) { +func Configure(api *operations.ClaAPI, service ServiceInterface, eventService events.Service) { api.GithubOrganizationsGetProjectGithubOrganizationsHandler = github_organizations.GetProjectGithubOrganizationsHandlerFunc( func(params github_organizations.GetProjectGithubOrganizationsParams, claUser *user.CLAUser) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index 9d12823ab..5f3ab77b1 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -35,8 +35,8 @@ var ( ErrOrganizationDoesNotExist = errors.New("github organization does not exist in cla") ) -// Repository interface defines the functions for the github organizations data model -type Repository interface { +// RepositoryInterface interface defines the functions for the github organizations data model +type RepositoryInterface interface { AddGithubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) @@ -47,15 +47,16 @@ type Repository interface { DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error } -type repository struct { +// Repository object/struct +type Repository struct { stage string dynamoDBClient *dynamodb.DynamoDB githubOrgTableName string } // NewRepository creates a new instance of the githubOrganizations repository -func NewRepository(awsSession *session.Session, stage string) repository { - return repository{ +func NewRepository(awsSession *session.Session, stage string) Repository { + return Repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), githubOrgTableName: fmt.Sprintf("cla-%s-github-orgs", stage), @@ -63,7 +64,7 @@ func NewRepository(awsSession *session.Session, stage string) repository { } // AddGithubOrganization add github organization logic -func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +func (repo Repository) AddGithubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -189,7 +190,7 @@ func (repo repository) AddGithubOrganization(ctx context.Context, parentProjectS } // GetGithubOrganizations get github organizations based on the project SFID -func (repo repository) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { +func (repo Repository) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -244,7 +245,8 @@ func (repo repository) GetGithubOrganizations(ctx context.Context, projectSFID s return &models.GithubOrganizations{List: ghOrgList}, nil } -func (repo repository) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { +// GetGithubOrganizationsByParent returns a list of github organizations by parent project SFID +func (repo Repository) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGithubOrganizationsByParent", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -295,7 +297,8 @@ func (repo repository) GetGithubOrganizationsByParent(ctx context.Context, paren return &models.GithubOrganizations{List: ghOrgList}, nil } -func (repo repository) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { +// GetGithubOrganizationByName get github organization by name +func (repo Repository) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -343,7 +346,8 @@ func (repo repository) GetGithubOrganizationByName(ctx context.Context, githubOr return &models.GithubOrganizations{List: ghOrgList}, nil } -func (repo repository) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { +// GetGithubOrganization by organization name +func (repo Repository) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -377,7 +381,7 @@ func (repo repository) GetGithubOrganization(ctx context.Context, githubOrganiza } // UpdateGithubOrganization updates the specified GitHub organization based on the update model provided -func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { +func (repo Repository) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.UpdateGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -453,7 +457,8 @@ func (repo repository) UpdateGithubOrganization(ctx context.Context, projectSFID return nil } -func (repo repository) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { +// DeleteGithubOrganization deletes the github organization by project SFID +func (repo Repository) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -515,7 +520,8 @@ func (repo repository) DeleteGithubOrganization(ctx context.Context, projectSFID return nil } -func (repo repository) DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error { +// DeleteGithubOrganizationByParent deletes the github organization by parent SFID +func (repo Repository) DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index f1556b6fb..a4224055b 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -19,8 +19,8 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/repositories" ) -// Service contains functions of GithubOrganizations service -type Service interface { +// ServiceInterface contains functions of GithubOrganizations service +type ServiceInterface interface { AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) @@ -30,22 +30,24 @@ type Service interface { RemoveDuplicates(input []*models.GithubOrganization) []*models.GithubOrganization } -type service struct { - repo Repository +// Service object/struct +type Service struct { + repo RepositoryInterface ghRepository repositories.Repository claRepository projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo Repository, ghRepository repositories.Repository, claRepository projects_cla_groups.Repository) Service { - return service{ +func NewService(repo RepositoryInterface, ghRepository repositories.Repository, claRepository projects_cla_groups.Repository) Service { + return Service{ repo: repo, ghRepository: ghRepository, claRepository: claRepository, } } -func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +// AddGithubOrganization adds the github organization for the specified project +func (s Service) AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -71,7 +73,8 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, return s.repo.AddGithubOrganization(ctx, parentProjectSFID, projectSFID, input) } -func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { +// GetGithubOrganizations returns the github organization for the specified project +func (s Service) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "GetGitHubOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -130,11 +133,13 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return &gitHubOrgModels, err } -func (s service) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { +// GetGithubOrganizationsByParent returns the github organizations for the specified parent project SFID +func (s Service) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { return s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) } -func (s service) GetGithubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) { +// GetGithubOrganizationByName returns the github organizations for the specified github organization name +func (s Service) GetGithubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "GetGitHubOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -158,7 +163,8 @@ func (s service) GetGithubOrganizationByName(ctx context.Context, githubOrgName return gitHubOrgs.List[0], err } -func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { +// UpdateGithubOrganization updates the specified github organization based on the project SFID, organization name provided values +func (s Service) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { // check if valid cla group id is passed if autoEnabledClaGroupID != "" { if _, err := s.claRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { @@ -168,7 +174,8 @@ func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID strin return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) } -func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { +// DeleteGithubOrganization removes the specified github organization under the projectSFID +func (s Service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { f := logrus.Fields{ "functionName": "DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -193,7 +200,7 @@ func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID strin } // RemoveDuplicates removes any duplicates from the specified list -func (s service) RemoveDuplicates(input []*models.GithubOrganization) []*models.GithubOrganization { +func (s Service) RemoveDuplicates(input []*models.GithubOrganization) []*models.GithubOrganization { if input == nil { return nil } diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 6f51ab76b..03af483fb 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -108,13 +108,13 @@ type repository struct { usersRepo users.UserRepository eventsService events.Service repositoriesRepo repositories.Repository - ghOrgRepo github_organizations.Repository + ghOrgRepo github_organizations.RepositoryInterface gerritService gerrits.Service signatureTableName string } // NewRepository creates a new instance of the whitelist service -func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.Repository, gerritService gerrits.Service) SignatureRepository { +func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.RepositoryInterface, gerritService gerrits.Service) SignatureRepository { return repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), @@ -2563,7 +2563,7 @@ func (repo repository) sendEmail(ctx context.Context, email string, approvalList } // check for signature type (CCLA, ICLA, ECLA) - var removalType string = "" + var removalType = "" // case 1 CCLA if len(iclas) == 0 && len(eclas) == 0 { diff --git a/cla-backend-go/template/handlers.go b/cla-backend-go/template/handlers.go index c4074da60..4bb758510 100644 --- a/cla-backend-go/template/handlers.go +++ b/cla-backend-go/template/handlers.go @@ -14,7 +14,7 @@ import ( ) // Configure API call -func Configure(api *operations.ClaAPI, service Service, eventsService events.Service) { +func Configure(api *operations.ClaAPI, service ServiceInterface, eventsService events.Service) { // Retrieve a list of available templates api.TemplateGetTemplatesHandler = template.GetTemplatesHandlerFunc(func(params template.GetTemplatesParams, claUser *user.CLAUser) middleware.Responder { diff --git a/cla-backend-go/template/repository.go b/cla-backend-go/template/repository.go index 546ba85dc..99267674f 100644 --- a/cla-backend-go/template/repository.go +++ b/cla-backend-go/template/repository.go @@ -35,8 +35,8 @@ var ( ASWFStyleTemplateID = "18b8ad08-d7d4-4d75-ad25-30bbfffd59cf" ) -// Repository interface functions -type Repository interface { +// RepositoryInterface interface functions +type RepositoryInterface interface { GetTemplates(ctx context.Context) ([]models.Template, error) GetTemplate(templateID string) (models.Template, error) CLAGroupTemplateExists(ctx context.Context, templateID string) bool @@ -45,7 +45,8 @@ type Repository interface { UpdateDynamoContractGroupTemplates(ctx context.Context, ContractGroupID string, template models.Template, pdfUrls models.TemplatePdfs, projectCCLAEnabled, projectICLAEnabled bool) error } -type repository struct { +// Repository object/struct +type Repository struct { stage string // The AWS stage (dev, staging, prod) dynamoDBClient *dynamodb.DynamoDB } @@ -98,16 +99,16 @@ type DocumentTab struct { DocumentTabAnchorIgnoreIfNotPresent bool `json:"document_tab_anchor_ignore_if_not_present"` } -// NewRepository creates a new instance of the repository service -func NewRepository(awsSession *session.Session, stage string) repository { - return repository{ +// NewRepository creates a new instance of the Repository service +func NewRepository(awsSession *session.Session, stage string) Repository { + return Repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), } } // GetTemplates returns a list containing all the template models -func (r repository) GetTemplates(ctx context.Context) ([]models.Template, error) { +func (r Repository) GetTemplates(ctx context.Context) ([]models.Template, error) { f := logrus.Fields{ "functionName": "GetTemplates", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -129,7 +130,7 @@ func (r repository) GetTemplates(ctx context.Context) ([]models.Template, error) } // GetTemplate returns the template based on the template ID -func (r repository) GetTemplate(templateID string) (models.Template, error) { +func (r Repository) GetTemplate(templateID string) (models.Template, error) { template, ok := templateMap[templateID] if !ok { return models.Template{}, ErrTemplateNotFound @@ -139,15 +140,15 @@ func (r repository) GetTemplate(templateID string) (models.Template, error) { } // CLAGroupTemplateExists return true if the specified template ID exists, false otherwise -func (r repository) CLAGroupTemplateExists(ctx context.Context, templateID string) bool { +func (r Repository) CLAGroupTemplateExists(ctx context.Context, templateID string) bool { _, ok := templateMap[templateID] return ok } // GetCLAGroup This method belongs in the contract group package. We are leaving it here -// because it accesses DynamoDB, but the contract group repository is designed +// because it accesses DynamoDB, but the contract group Repository is designed // to connect to postgres -func (r repository) GetCLAGroup(claGroupID string) (*models.ClaGroup, error) { +func (r Repository) GetCLAGroup(claGroupID string) (*models.ClaGroup, error) { log.Debugf("GetCLAGroup - claGroupID: %s", claGroupID) dbModel, err := r.fetchCLAGroup(claGroupID) @@ -158,7 +159,7 @@ func (r repository) GetCLAGroup(claGroupID string) (*models.ClaGroup, error) { } // GetCLADocuments fetches the cla documents inside of the CLA Group, it's separate method for perf reasons -func (r repository) GetCLADocuments(claGroupID string, claType string) ([]models.ClaGroupDocument, error) { +func (r Repository) GetCLADocuments(claGroupID string, claType string) ([]models.ClaGroupDocument, error) { log.Debugf("GetCLADocuments - claGroupID: %s - claType : %s", claGroupID, claType) dbModel, err := r.fetchCLAGroup(claGroupID) if err != nil { @@ -179,7 +180,7 @@ func (r repository) GetCLADocuments(claGroupID string, claType string) ([]models return projectDocuments, nil } -func (r repository) buildProjectDocuments(dbProjectDocumentModels []DBProjectDocumentModel) []models.ClaGroupDocument { +func (r Repository) buildProjectDocuments(dbProjectDocumentModels []DBProjectDocumentModel) []models.ClaGroupDocument { if len(dbProjectDocumentModels) == 0 { return nil } @@ -204,7 +205,7 @@ func (r repository) buildProjectDocuments(dbProjectDocumentModels []DBProjectDoc } // fetchCLAGroup brings back the CLA db model from dynamodb -func (r repository) fetchCLAGroup(claGroupID string) (*DBProjectModel, error) { +func (r Repository) fetchCLAGroup(claGroupID string) (*DBProjectModel, error) { var dbModel DBProjectModel tableName := fmt.Sprintf("cla-%s-projects", r.stage) @@ -230,7 +231,7 @@ func (r repository) fetchCLAGroup(claGroupID string) (*DBProjectModel, error) { } // buildProjectModel maps the database model to the API response model -func (r repository) buildProjectModel(dbModel DBProjectModel) *models.ClaGroup { +func (r Repository) buildProjectModel(dbModel DBProjectModel) *models.ClaGroup { return &models.ClaGroup{ ProjectID: dbModel.ProjectID, ProjectExternalID: dbModel.ProjectExternalID, @@ -246,7 +247,7 @@ func (r repository) buildProjectModel(dbModel DBProjectModel) *models.ClaGroup { } // UpdateDynamoContractGroupTemplates updates the templates in the data store -func (r repository) UpdateDynamoContractGroupTemplates(ctx context.Context, claGroupID string, template models.Template, pdfUrls models.TemplatePdfs, projectCCLAEnabled, projectICLAEnabled bool) error { +func (r Repository) UpdateDynamoContractGroupTemplates(ctx context.Context, claGroupID string, template models.Template, pdfUrls models.TemplatePdfs, projectCCLAEnabled, projectICLAEnabled bool) error { f := logrus.Fields{ "functionName": "UpdateDynamoContractGroupTemplates", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index a73aa043b..4782a5d4e 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -33,8 +33,8 @@ const ( claTypeCCLA = "ccla" ) -// Service interface -type Service interface { +// ServiceInterface interface +type ServiceInterface interface { GetTemplates(ctx context.Context) ([]models.Template, error) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) @@ -42,16 +42,17 @@ type Service interface { CLAGroupTemplateExists(ctx context.Context, templateID string) bool } -type service struct { +// Service object/struct +type Service struct { stage string // The AWS stage (dev, staging, prod) - templateRepo Repository + templateRepo RepositoryInterface docRaptorClient docraptor.Client s3Client *s3manager.Uploader } // NewService API call -func NewService(stage string, templateRepo Repository, docRaptorClient docraptor.Client, awsSession *session.Session) service { - return service{ +func NewService(stage string, templateRepo RepositoryInterface, docRaptorClient docraptor.Client, awsSession *session.Session) Service { + return Service{ stage: stage, templateRepo: templateRepo, docRaptorClient: docRaptorClient, @@ -60,7 +61,7 @@ func NewService(stage string, templateRepo Repository, docRaptorClient docraptor } // GetTemplates API call -func (s service) GetTemplates(ctx context.Context) ([]models.Template, error) { +func (s Service) GetTemplates(ctx context.Context) ([]models.Template, error) { f := logrus.Fields{ "functionName": "v1.template.service.GetTemplates", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -82,7 +83,8 @@ func (s service) GetTemplates(ctx context.Context) ([]models.Template, error) { return templates, nil } -func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) { +// CreateTemplatePreview returns a PDF using the specified CLA Group field values and template identifier +func (s Service) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) { f := logrus.Fields{ "functionName": "v1.template.service.CreateTemplatePreview", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -137,7 +139,7 @@ func (s service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode } // CreateCLAGroupTemplate service method -func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) { +func (s Service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) { f := logrus.Fields{ "functionName": "v1.template.service.CreateCLAGroupTemplate", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -266,7 +268,8 @@ func (s service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, return pdfUrls, nil } -func (s service) GetCLATemplatePreview(ctx context.Context, claGroupID, claType string, watermark bool) ([]byte, error) { +// GetCLATemplatePreview returns a preview of the specified CLA Group and CLA type +func (s Service) GetCLATemplatePreview(ctx context.Context, claGroupID, claType string, watermark bool) ([]byte, error) { f := logrus.Fields{ "functionName": "v1.template.service.GetCLATemplatePreview", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -434,7 +437,7 @@ func getLatestDocument(ctx context.Context, documents []models.ClaGroupDocument) } // InjectProjectInformationIntoTemplate service function -func (s service) InjectProjectInformationIntoTemplate(template models.Template, metaFields []*models.MetaField) (string, string, error) { +func (s Service) InjectProjectInformationIntoTemplate(template models.Template, metaFields []*models.MetaField) (string, string, error) { f := logrus.Fields{ "functionName": "v1.template.service.InjectProjectInformationIntoTemplate", "templateName": template.Name, @@ -480,7 +483,7 @@ func (s service) InjectProjectInformationIntoTemplate(template models.Template, } // generateTemplateS3FilePath helper function to generate a suitable s3 path and filename for the template -func (s service) generateTemplateS3FilePath(claGroupID, claType string) string { +func (s Service) generateTemplateS3FilePath(claGroupID, claType string) string { fileNameTemplate := "contract-group/%s/template/%s" var ext string switch claType { @@ -497,7 +500,7 @@ func (s service) generateTemplateS3FilePath(claGroupID, claType string) string { } // SaveTemplateToS3 uploads the specified template contents to S3 storage -func (s service) SaveTemplateToS3(bucket, filepath string, template io.ReadCloser) (string, error) { +func (s Service) SaveTemplateToS3(bucket, filepath string, template io.ReadCloser) (string, error) { f := logrus.Fields{ "functionName": "v1.template.service.SaveTemplateToS3", "bucket": bucket, @@ -526,6 +529,6 @@ func (s service) SaveTemplateToS3(bucket, filepath string, template io.ReadClose } // CLAGroupTemplateExists return true if the specified template ID exists, false otherwise -func (s service) CLAGroupTemplateExists(ctx context.Context, templateID string) bool { +func (s Service) CLAGroupTemplateExists(ctx context.Context, templateID string) bool { return s.templateRepo.CLAGroupTemplateExists(ctx, templateID) } diff --git a/cla-backend-go/user/repository.go b/cla-backend-go/user/repository.go index 976c2e463..c531c0506 100644 --- a/cla-backend-go/user/repository.go +++ b/cla-backend-go/user/repository.go @@ -11,26 +11,27 @@ import ( "github.com/jmoiron/sqlx" ) -// Repository interface methods -type Repository interface { +// RepositoryInterface interface methods +type RepositoryInterface interface { GetUserAndProfilesByLFID(lfidUsername string) (CLAUser, error) GetUserProjectIDs(userID string) ([]string, error) GetClaManagerCorporateClaIDs(userID string) ([]string, error) } -type repository struct { +// Repository object/struct +type Repository struct { db *sqlx.DB } // NewRepository creates a new user repository -func NewRepository(db *sqlx.DB) repository { - return repository{ +func NewRepository(db *sqlx.DB) Repository { + return Repository{ db: db, } } // GetUserAndProfilesByLFID get user profile by LFID -func (repo repository) GetUserAndProfilesByLFID(lfidUsername string) (CLAUser, error) { +func (repo Repository) GetUserAndProfilesByLFID(lfidUsername string) (CLAUser, error) { log.Debugf("lfidUsername: %s", lfidUsername) sql := ` SELECT @@ -74,13 +75,13 @@ func (repo repository) GetUserAndProfilesByLFID(lfidUsername string) (CLAUser, e } // GetUserByGithubID returns the user details based on the github ID -func (repo repository) GetUserByGithubID(githubID string) (CLAUser, error) { +func (repo Repository) GetUserByGithubID(githubID string) (CLAUser, error) { // TODO: Implement when adding authentication to the Corporate Console return CLAUser{}, nil } // GetUserProjectIDs get the user project ID's based on the specified user ID -func (repo repository) GetUserProjectIDs(userID string) ([]string, error) { +func (repo Repository) GetUserProjectIDs(userID string) ([]string, error) { getUserProjectIDsSQL := ` SELECT project_sfdc_id @@ -113,7 +114,7 @@ func (repo repository) GetUserProjectIDs(userID string) ([]string, error) { } // GetClaManagerCorporateClaIDs returns a list of CLA manager corporate CLAs associated with the specified user -func (repo repository) GetClaManagerCorporateClaIDs(userID string) ([]string, error) { +func (repo Repository) GetClaManagerCorporateClaIDs(userID string) ([]string, error) { getClaManagerCorporateClaIDsSQL := ` SELECT corporate_cla_group_id @@ -146,6 +147,6 @@ func (repo repository) GetClaManagerCorporateClaIDs(userID string) ([]string, er } // GetUserCompanyIDs returns a list of company IDs based on the user -func (repo repository) GetUserCompanyIDs(userID string) ([]string, error) { +func (repo Repository) GetUserCompanyIDs(userID string) ([]string, error) { return []string{}, nil } diff --git a/cla-backend-go/user/service.go b/cla-backend-go/user/service.go index 2ed8164d8..24daf8b0a 100644 --- a/cla-backend-go/user/service.go +++ b/cla-backend-go/user/service.go @@ -11,11 +11,11 @@ type Service interface { // nolint } type service struct { - repo Repository + repo RepositoryInterface } // NewService creates a new user service -func NewService(repo Repository) Service { +func NewService(repo RepositoryInterface) Service { return service{ repo: repo, } diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 7496813b1..1d35947d4 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -237,10 +237,14 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent, err := psc.IsTheLinuxFoundation(projectTree.Parent.ID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) - return err + isLFParent := false + // if we have a project tree parent ID - check to see if it is one of our root parents + if projectTree != nil && projectTree.Parent != nil && projectTree.Parent.ID != "" { + isLFParent, err = psc.IsTheLinuxFoundation(projectTree.Parent.ID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) + return err + } } for _, projectSFID := range projectSFIDList { diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index b70f919f7..87d436c4f 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -41,7 +41,7 @@ import ( type service struct { v1ProjectService v1Project.Service - v1TemplateService v1Template.Service + v1TemplateService v1Template.ServiceInterface projectsClaGroupsRepo projects_cla_groups.Repository claManagerRequests v1ClaManager.IService signatureService signatureService.SignatureService @@ -68,7 +68,7 @@ type Service interface { } // NewService returns instance of CLA group service -func NewService(projectService v1Project.Service, templateService v1Template.Service, projectsClaGroupsRepo projects_cla_groups.Repository, claMangerRequests v1ClaManager.IService, signatureService signatureService.SignatureService, metricsRepo metrics.Repository, gerritService gerrits.Service, repositoriesService repositories.Service, eventsService events.Service) Service { +func NewService(projectService v1Project.Service, templateService v1Template.ServiceInterface, projectsClaGroupsRepo projects_cla_groups.Repository, claMangerRequests v1ClaManager.IService, signatureService signatureService.SignatureService, metricsRepo metrics.Repository, gerritService gerrits.Service, repositoriesService repositories.Service, eventsService events.Service) Service { return &service{ v1ProjectService: projectService, // aka cla_group service of v1 v1TemplateService: templateService, diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index 79a7fabac..a0d9a7dfb 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -41,7 +41,7 @@ type AutoEnableService interface { // NewAutoEnableService creates a new AutoEnableService func NewAutoEnableService(repositoryService repositories.Service, githubRepo repositories.Repository, - githubOrgRepo github_organizations.Repository, + githubOrgRepo github_organizations.RepositoryInterface, claRepository projects_cla_groups.Repository, claService project.Service, ) AutoEnableService { @@ -59,7 +59,7 @@ func NewAutoEnableService(repositoryService repositories.Service, type autoEnableServiceProvider struct { repositoryService repositories.Service githubRepo repositories.Repository - githubOrgRepo github_organizations.Repository + githubOrgRepo github_organizations.RepositoryInterface claRepository projects_cla_groups.Repository claService project.Service } diff --git a/cla-backend-go/v2/dynamo_events/service.go b/cla-backend-go/v2/dynamo_events/service.go index 7fcef6ef3..896167d40 100644 --- a/cla-backend-go/v2/dynamo_events/service.go +++ b/cla-backend-go/v2/dynamo_events/service.go @@ -58,7 +58,7 @@ type service struct { eventsRepo claevent.Repository projectRepo project.ProjectRepository projectService project.Service - githubOrgService github_organizations.Service + githubOrgService github_organizations.ServiceInterface repositoryService repositories.Service gerritService gerrits.Service autoEnableService *autoEnableServiceProvider @@ -80,7 +80,7 @@ func NewService(stage string, eventsRepo claevent.Repository, projectRepo project.ProjectRepository, projService project.Service, - githubOrgService github_organizations.Service, + githubOrgService github_organizations.ServiceInterface, repositoryService repositories.Service, gerritService gerrits.Service, claManagerRequestsRepo cla_manager.IRepository, diff --git a/cla-backend-go/v2/dynamo_events/signatures.go b/cla-backend-go/v2/dynamo_events/signatures.go index a7faff862..6fe85c609 100644 --- a/cla-backend-go/v2/dynamo_events/signatures.go +++ b/cla-backend-go/v2/dynamo_events/signatures.go @@ -227,14 +227,15 @@ func (s *service) SignatureSignedEvent(event events.DynamoDBEventRecord) error { } } else { for _, projectCLAGroup := range projectCLAGroups { + pcg := projectCLAGroup // make a copy of the loop variable to use in the closure, avoids the loopclosure: loop variable projectCLAGroup captured by func literal lint error eg.Go(func() error { // Remove any roles that were previously assigned for cla-manager-designee log.WithFields(f).Debugf("removing existing %s role for project: '%s' (%s) and company: '%s' (%s)", - utils.CLADesigneeRole, projectCLAGroup.ProjectName, projectCLAGroup.ProjectSFID, companyModel.CompanyName, companyModel.CompanyExternalID) - err = s.removeCLAPermissionsByProjectOrganizationRole(ctx, projectCLAGroup.ProjectSFID, companyModel.CompanyExternalID, []string{utils.CLADesigneeRole}) + utils.CLADesigneeRole, pcg.ProjectName, pcg.ProjectSFID, companyModel.CompanyName, companyModel.CompanyExternalID) + err = s.removeCLAPermissionsByProjectOrganizationRole(ctx, pcg.ProjectSFID, companyModel.CompanyExternalID, []string{utils.CLADesigneeRole}) if err != nil { log.WithFields(f).Warnf("failed to remove %s roles for project: '%s' (%s) and company: '%s' (%s), error: %+v", - utils.CLADesigneeRole, projectCLAGroup.ProjectName, projectCLAGroup.ProjectSFID, companyModel.CompanyName, companyModel.CompanyExternalID, err) + utils.CLADesigneeRole, pcg.ProjectName, pcg.ProjectSFID, companyModel.CompanyName, companyModel.CompanyExternalID, err) return err } diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index 19b12d989..a44c0398e 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -37,7 +37,7 @@ type Service interface { type eventHandlerService struct { githubRepo repositories.Repository - githubOrgRepo v1GithubOrg.Repository + githubOrgRepo v1GithubOrg.RepositoryInterface eventService events.Service autoEnableService dynamo_events.AutoEnableService emailService emails.Service @@ -46,7 +46,7 @@ type eventHandlerService struct { // NewService creates a new instance of the Event Handler Service func NewService(githubRepo repositories.Repository, - githubOrgRepo v1GithubOrg.Repository, + githubOrgRepo v1GithubOrg.RepositoryInterface, eventService events.Service, autoEnableService dynamo_events.AutoEnableService, emailService emails.Service) Service { @@ -55,7 +55,7 @@ func NewService(githubRepo repositories.Repository, } func newService(githubRepo repositories.Repository, - githubOrgRepo v1GithubOrg.Repository, + githubOrgRepo v1GithubOrg.RepositoryInterface, eventService events.Service, autoEnableService dynamo_events.AutoEnableService, emailService emails.Service, diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index d70513a83..4a7d9e553 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -46,14 +46,14 @@ type Service interface { } type service struct { - repo v1GithubOrg.Repository + repo v1GithubOrg.RepositoryInterface ghRepository v1Repositories.Repository - ghService v1GithubOrg.Service + ghService v1GithubOrg.ServiceInterface projectsCLAGroupService projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo v1GithubOrg.Repository, ghRepository v1Repositories.Repository, projectsCLAGroupService projects_cla_groups.Repository, ghService v1GithubOrg.Service) Service { +func NewService(repo v1GithubOrg.RepositoryInterface, ghRepository v1Repositories.Repository, projectsCLAGroupService projects_cla_groups.Repository, ghService v1GithubOrg.ServiceInterface) Service { return service{ repo: repo, ghRepository: ghRepository, diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 07a5010ee..2d315fb9a 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -40,7 +40,7 @@ import ( ) // Configure setups handlers on api with service -func Configure(api *operations.EasyclaAPI, claGroupService project.Service, projectRepo project.ProjectRepository, companyService company.IService, v1SignatureService signatureService.SignatureService, sessionStore *dynastore.Store, eventsService events.Service, v2service Service, projectClaGroupsRepo projects_cla_groups.Repository) { //nolint +func Configure(api *operations.EasyclaAPI, claGroupService project.Service, projectRepo project.ProjectRepository, companyService company.IService, v1SignatureService signatureService.SignatureService, sessionStore *dynastore.Store, eventsService events.Service, v2service ServiceInterface, projectClaGroupsRepo projects_cla_groups.Repository) { //nolint const problemLoadingCLAGroupByID = "problem loading cla group by ID" const iclaNotSupportedForCLAGroup = "individual contribution is not supported for this project" @@ -1370,7 +1370,7 @@ func isUserHaveAccessOfSignedSignaturePDF(ctx context.Context, authUser *auth.Us } // Corporate signature...we can check the company details - if signature.SignatureType == CclaSignatureType { + if signature.SignatureType == utils.SignatureTypeCCLA { comp, err := companyService.GetCompany(ctx, signature.SignatureReferenceID) if err != nil { log.WithFields(f).WithError(err).Warnf("failed to load company record using signature reference id: %s", signature.SignatureReferenceID) diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 1984e5ed7..08507fc6a 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -34,30 +34,17 @@ import ( // constants const ( - // used when we want to query all data from dependent service. - HugePageSize = int64(10000) - CclaSignatureType = "ccla" - ClaSignatureType = "cla" + // HugePageSize constant for querying signatures + HugePageSize = int64(10000) ) -// errors var ( + // ErrZipNotPresent error ErrZipNotPresent = errors.New("zip file not present") ) -type service struct { - v1ProjectService project.Service - v1CompanyService company.IService - v1SignatureService signatures.SignatureService - v1SignatureRepo signatures.SignatureRepository - usersService users.Service - projectsClaGroupsRepo projects_cla_groups.Repository - s3 *s3.S3 - signaturesBucket string -} - -// Service contains method of v2 signature service -type Service interface { +// ServiceInterface contains method of v2 signature service +type ServiceInterface interface { GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) @@ -70,6 +57,17 @@ type Service interface { InvalidateICLA(ctx context.Context, claGroupID string, userID string, authUser *auth.User, eventsService events.Service, eventArgs *events.LogEventArgs) error } +type service struct { + v1ProjectService project.Service + v1CompanyService company.IService + v1SignatureService signatures.SignatureService + v1SignatureRepo signatures.SignatureRepository + usersService users.Service + projectsClaGroupsRepo projects_cla_groups.Repository + s3 *s3.S3 + signaturesBucket string +} + // NewService creates instance of v2 signature service func NewService(awsSession *session.Session, signaturesBucketName string, v1ProjectService project.Service, v1CompanyService company.IService, @@ -206,14 +204,14 @@ func (s service) GetSignedDocument(ctx context.Context, signatureID string) (*mo if err != nil { return nil, err } - if sig.SignatureType == ClaSignatureType && sig.CompanyName != "" { + if sig.SignatureType == utils.SignatureTypeCLA && sig.CompanyName != "" { return nil, errors.New("bad request. employee signature does not have signed document") } var url string switch sig.SignatureType { - case ClaSignatureType: + case utils.SignatureTypeCLA: url = utils.SignedCLAFilename(sig.ProjectID, "icla", sig.SignatureReferenceID, sig.SignatureID) - case CclaSignatureType: + case utils.SignatureTypeCCLA: url = utils.SignedCLAFilename(sig.ProjectID, "ccla", sig.SignatureReferenceID, sig.SignatureID) } signedURL, err := utils.GetDownloadLink(url) diff --git a/cla-backend-go/v2/template/handlers.go b/cla-backend-go/v2/template/handlers.go index 9f305052b..d854234e0 100644 --- a/cla-backend-go/v2/template/handlers.go +++ b/cla-backend-go/v2/template/handlers.go @@ -29,7 +29,7 @@ import ( ) // Configure API call -func Configure(api *operations.EasyclaAPI, service v1Template.Service, v1ProjectClaGroupService v1ProjectsCLAGroups.Service, eventsService v1Events.Service) { +func Configure(api *operations.EasyclaAPI, service v1Template.ServiceInterface, v1ProjectClaGroupService v1ProjectsCLAGroups.Service, eventsService v1Events.Service) { // Retrieve a list of available templates api.TemplateGetTemplatesHandler = template.GetTemplatesHandlerFunc(func(params template.GetTemplatesParams, user *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) From a40e5f5b020c067b4196928eabd7ace64e32efb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:43:50 -0700 Subject: [PATCH 0324/1276] Bump set-getter from 0.1.0 to 0.1.1 in /cla-backend-go (#3004) Bumps [set-getter](https://github.com/doowb/set-getter) from 0.1.0 to 0.1.1. - [Release notes](https://github.com/doowb/set-getter/releases) - [Commits](https://github.com/doowb/set-getter/commits/0.1.1) --- updated-dependencies: - dependency-name: set-getter dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index a94e9c28c..c19b7b764 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -4384,9 +4384,9 @@ set-blocking@~2.0.0: integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= + version "0.1.1" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102" + integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw== dependencies: to-object-path "^0.3.0" From 75f34b20094c82a395e13c0ab1c97a63adfb6b0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:44:10 -0700 Subject: [PATCH 0325/1276] Bump set-getter from 0.1.0 to 0.1.1 in /cla-frontend-project-console (#3003) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index c334447b7..e032b9b67 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -5625,9 +5625,9 @@ set-blocking@~2.0.0: integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= + version "0.1.1" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102" + integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw== dependencies: to-object-path "^0.3.0" From 8322bea8d22b709aec545b97abb87a12e10a0e38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:45:23 -0700 Subject: [PATCH 0326/1276] Bump set-getter from 0.1.0 to 0.1.1 in /cla-frontend-contributor-console (#2999) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 71ba0f0e7..72f50ceb6 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -5713,9 +5713,9 @@ set-blocking@~2.0.0: integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= + version "0.1.1" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102" + integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw== dependencies: to-object-path "^0.3.0" From b04f7e104d24f7c3830c0a8e261b63fc9e283f88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:46:07 -0700 Subject: [PATCH 0327/1276] Bump set-getter from 0.1.0 to 0.1.1 in /cla-frontend-corporate-console (#3002) Bumps [set-getter](https://github.com/doowb/set-getter) from 0.1.0 to 0.1.1. - [Release notes](https://github.com/doowb/set-getter/releases) - [Commits](https://github.com/doowb/set-getter/commits/0.1.1) --- updated-dependencies: - dependency-name: set-getter dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 1fa89e32d..3d08370c1 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -5669,9 +5669,9 @@ set-blocking@~2.0.0: integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= + version "0.1.1" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102" + integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw== dependencies: to-object-path "^0.3.0" From 28815aacb4a76cab51aa6aea859e8895186dbd37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:46:32 -0700 Subject: [PATCH 0328/1276] Bump set-getter from 0.1.0 to 0.1.1 in /cla-backend (#3000) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 9e8e865fb..bb921ef4b 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -5452,9 +5452,9 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= + version "0.1.1" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102" + integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw== dependencies: to-object-path "^0.3.0" From 6b0f525aa1a5d0f3b197d54ca2a3fea45a98f01b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:46:58 -0700 Subject: [PATCH 0329/1276] Bump color-string from 1.5.4 to 1.5.5 in /cla-backend (#3011) Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.4 to 1.5.5. - [Release notes](https://github.com/Qix-/color-string/releases) - [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md) - [Commits](https://github.com/Qix-/color-string/compare/1.5.4...1.5.5) --- updated-dependencies: - dependency-name: color-string dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index bb921ef4b..31b8c1bf3 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -1754,9 +1754,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" From cabe01a50d95a11f7d5e70b80dc5d7ca1673afe7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:47:27 -0700 Subject: [PATCH 0330/1276] Bump color-string from 1.5.4 to 1.5.5 in /cla-frontend-corporate-console (#3012) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 3d08370c1..8c8b1f913 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -1624,9 +1624,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" From 880d096f4079df445b009804f5244dd55559fa65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:47:55 -0700 Subject: [PATCH 0331/1276] Bump color-string from 1.5.4 to 1.5.5 in /cla-frontend-contributor-console (#3010) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 72f50ceb6..d7e1b8ee0 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -1637,9 +1637,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" From 0ef7a8ebd94dddf6a74cc60d5f46a069d7708f6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:48:37 -0700 Subject: [PATCH 0332/1276] Bump color-string from 1.5.4 to 1.5.5 (#3009) Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.4 to 1.5.5. - [Release notes](https://github.com/Qix-/color-string/releases) - [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md) - [Commits](https://github.com/Qix-/color-string/compare/1.5.4...1.5.5) --- updated-dependencies: - dependency-name: color-string dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a72f22243..d323fbb34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1267,9 +1267,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" From eeb7f53accf37083ac3c374619e6d339eae9e275 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 12:49:31 -0700 Subject: [PATCH 0333/1276] Bump color-string from 1.5.4 to 1.5.5 in /cla-backend-go (#3008) Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.4 to 1.5.5. - [Release notes](https://github.com/Qix-/color-string/releases) - [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md) - [Commits](https://github.com/Qix-/color-string/compare/1.5.4...1.5.5) --- updated-dependencies: - dependency-name: color-string dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index c19b7b764..678865e5d 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -1376,9 +1376,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" From 2f821a1957f57688c275ac8758ea0388a0cf9984 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Fri, 25 Jun 2021 18:03:38 +0300 Subject: [PATCH 0334/1276] removing the signature_acl default argument set which was causing issues (#3013) --- cla-backend/cla/models/dynamo_models.py | 40 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index c1d2d342f..221106e6e 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2272,7 +2272,7 @@ class Meta: signature_return_url = UnicodeAttribute(null=True) signature_callback_url = UnicodeAttribute(null=True) signature_user_ccla_company_id = UnicodeAttribute(null=True) - signature_acl = UnicodeSetAttribute(default=set()) + signature_acl = UnicodeSetAttribute() signature_project_index = ProjectSignatureIndex() signature_reference_index = ReferenceSignatureIndex() signature_envelope_id = UnicodeAttribute(null=True) @@ -2538,7 +2538,7 @@ def get_signature_user_ccla_company_id(self): return self.model.signature_user_ccla_company_id def get_signature_acl(self): - return self.model.signature_acl + return self.model.signature_acl or set() def get_signature_return_url_type(self): # Refers to either Gerrit or GitHub @@ -2716,11 +2716,15 @@ def set_signature_project_external_id(self, signature_project_external_id): self.model.signature_project_external_id = signature_project_external_id def add_signature_acl(self, username): + if not self.model.signature_acl: + self.model.signature_acl = set() self.model.signature_acl.add(username) def remove_signature_acl(self, username): - if username in self.model.signature_acl: - self.model.signature_acl.remove(username) + current_acl = self.model.signature_acl or set() + if username not in current_acl: + return + self.model.signature_acl.remove(username) def set_user_email(self, user_email): self.model.user_email = user_email @@ -2939,6 +2943,34 @@ def get_employee_signature_by_company_project(self, company_id, project_id, user "Why do we have more than one employee signature for this user? - Will return the first one only.") return signatures[0] + def get_employee_signature_by_company_project_list(self, company_id, project_id, user_id) -> Optional[List[Signature]]: + """ + Returns the employee signature for the specified user associated with + the project/company. Returns None if no employee signature exists for + this set of query parameters. + """ + signature_attributes = { + "signature_signed": True, + "signature_approved": True, + "signature_type": 'cla', + "signature_reference_type": 'user', + "signature_project_id": project_id, + "signature_user_ccla_company_id": company_id + } + filter_condition = create_filter(signature_attributes, SignatureModel) + signature_generator = self.model.signature_reference_index.query( + user_id, filter_condition=filter_condition + ) + signatures = [] + for signature_model in signature_generator: + signature = Signature() + signature.model = signature_model + signatures.append(signature) + # No employee signatures were found that were signed/approved + if len(signatures) == 0: + return None + return signatures + def get_employee_signatures_by_company_project_model(self, company_id, project_id) -> List[Signature]: signature_attributes = { "signature_signed": True, From 71b7108e25c57b6509bf532276db52b45ca46cc3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 25 Jun 2021 14:33:44 -0700 Subject: [PATCH 0335/1276] Updated Parent/Child/GrandChild Tree Logic (#3014) - Added additional checks for nested tree - Added string function to printout the project tree Signed-off-by: David Deal --- cla-backend-go/.golangci.yaml | 9 +- cla-backend-go/Makefile | 2 +- .../service_discovery/service_discovery.go | 93 ---------- cla-backend-go/v2/cla_groups/helpers.go | 168 +++++++++--------- cla-backend-go/v2/project-service/client.go | 100 +++++++++-- cla-backend-go/v2/signatures/service.go | 41 +++-- 6 files changed, 208 insertions(+), 205 deletions(-) delete mode 100644 cla-backend-go/service_discovery/service_discovery.go diff --git a/cla-backend-go/.golangci.yaml b/cla-backend-go/.golangci.yaml index 29ee03471..ad4f90bca 100644 --- a/cla-backend-go/.golangci.yaml +++ b/cla-backend-go/.golangci.yaml @@ -34,6 +34,7 @@ linters-settings: check-blank: true govet: check-shadowing: true + fieldalignment: true revive: min-confidence: 0 dupl: @@ -41,10 +42,7 @@ linters-settings: goconst: min-len: 2 min-occurrences: 2 - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - + linters: disable-all: true enable: @@ -67,8 +65,9 @@ linters: - unparam - unused - nakedret - - maligned + #- maligned # The repository of the linter has been archived by the owner. Replaced by govet 'fieldalignment'. #- dupl + - bodyclose issues: exclude-use-default: false diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 468f39d57..852fe6fe4 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -19,7 +19,7 @@ LDFLAGS=-ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X m BUILD_TAGS=-tags aws_lambda LINT_TOOL=$(shell go env GOPATH)/bin/golangci-lint -LINT_VERSION=v1.37.0 +LINT_VERSION=v1.41.1 SWAGGER_TOOL_VERSION=v0.24.0 GO_PKGS=$(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') diff --git a/cla-backend-go/service_discovery/service_discovery.go b/cla-backend-go/service_discovery/service_discovery.go deleted file mode 100644 index 0eddb94d3..000000000 --- a/cla-backend-go/service_discovery/service_discovery.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package service_discovery - -import ( - "github.com/communitybridge/easycla/cla-backend-go/company" - "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/project" - "github.com/communitybridge/easycla/cla-backend-go/signatures" - "github.com/communitybridge/easycla/cla-backend-go/users" - v2CompanyService "github.com/communitybridge/easycla/cla-backend-go/v2/company" - v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project" -) - -// ServiceDiscovery interface -type ServiceDiscovery interface { - SetEventService(eventService events.Service) - GetEventService() events.Service - SetUserService(userService users.Service) - GetUserService() users.Service - SetV1CompanyService(companyService company.IService) - GetV1CompanyService() company.IService - SetV2CompanyService(companyService v2CompanyService.Service) - GetV2CompanyService() v2CompanyService.Service - GetV1ProjectService() project.Service - GetV2ProjectService() v2ProjectService.Service - GetV1SignatureService() signatures.SignatureService -} - -type service struct { - eventService events.Service - userService users.Service - v1CompanyService company.IService - v2CompanyService v2CompanyService.Service - v1ProjectService project.Service - v2ProjectService v2ProjectService.Service - v1SignatureService signatures.SignatureService -} - -// NewServiceDiscovery creates a new whitelist service -func NewServiceDiscovery() ServiceDiscovery { - return service{} -} - -// GetEventService sets the event service reference -func (s service) SetEventService(eventService events.Service) { - s.eventService = eventService -} - -// GetEventService returns a reference to the the event service -func (s service) GetEventService() events.Service { - return s.eventService -} - -// SetUserService sets the user service reference -func (s service) SetUserService(userService users.Service) { - s.userService = userService -} - -// GetUserService returns a reference to the the user service -func (s service) GetUserService() users.Service { - return s.userService -} - -// SetV1CompanyService sets the v1 company service reference -func (s service) SetV1CompanyService(companyService company.IService) { - s.v1CompanyService = companyService -} - -// GetV1CompanyService returns a reference to the the v1 company service -func (s service) GetV1CompanyService() company.IService { - return s.v1CompanyService -} - -// SetV2CompanyService sets the v1 company service reference -func (s service) SetV2CompanyService(companyService v2CompanyService.Service) { - s.v2CompanyService = companyService -} - -// GetV2CompanyService returns a reference to the the v2 company service -func (s service) GetV2CompanyService() v2CompanyService.Service { - return s.v2CompanyService -} - -// GetV1ProjectService returns a reference to the the v1 project service -func (s service) GetV1ProjectService() project.Service { return s.v1ProjectService } - -// GetV2ProjectService returns a reference to the the v2 project service -func (s service) GetV2ProjectService() v2ProjectService.Service { return s.v2ProjectService } - -// GetV1SignatureService returns a reference to the the v1 signature service -func (s service) GetV1SignatureService() signatures.SignatureService { return s.v1SignatureService } diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 1d35947d4..f426104a1 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -229,11 +229,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI } // build Tree that tracks parent and child projects - projectTree, err := buildProjectTree(foundationProjectSummary) - if err != nil { - log.WithFields(f).Warnf("unable to process project summary :%+v ", foundationProjectSummary) - return err - } + projectTree := buildProjectNode(foundationProjectSummary) // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") @@ -253,22 +249,18 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI return err } - if projectTree.Parent != nil && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { + if projectTree != nil && projectTree.Parent != nil && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", foundationProjectDetails.Parent, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) } - } // Check to see if all the provided enrolled projects are part of this foundation - - exists := projectsExist(projectTree, projectSFIDList) - - if !exists { - log.WithFields(f).Warnf("validation failure - provided projects are not under the SF foundation: %+v", projectTree) - return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the SF foundation", projectTree) + if !allProjectsExistInTree(projectTree, projectSFIDList) { + log.WithFields(f).Warnf("validation failure - one or more provided projects are not under the project tree: %+v", projectTree.String()) + return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the parent", projectTree.String()) } // check if projects are not already enabled @@ -335,11 +327,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS } // build Tree that tracks parent and child projects - projectTree, err := buildProjectTree(foundationProjectSummary) - if err != nil { - log.WithFields(f).Warnf("unable to process project summary :%+v ", foundationProjectSummary) - return err - } + projectTree := buildProjectNode(foundationProjectSummary) // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") @@ -364,18 +352,10 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS } - // Comment out the below as we want to support stand-alone projects - /* if len(foundationProjectDetails.Projects) == 0 { - log.WithFields(f).Warn("validation failure - project does not have any subprojects") - return fmt.Errorf("bad request: invalid input to enroll projects. project does not have any subprojects") - } */ - // Check to see if all the provided enrolled projects are part of this foundation - exists := projectsExist(projectTree, projectSFIDList) - - if !exists { - log.WithFields(f).Warnf("validation failure - provided projects are not under the SF foundation: %+v", projectTree) - return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the SF foundation", projectTree) + if !allProjectsExistInTree(projectTree, projectSFIDList) { + log.WithFields(f).Warnf("validation failure - one or more provided projects are not under the project tree: %+v", projectTree.String()) + return fmt.Errorf("bad request: invalid project_sfid: %+v. One or more provided projects are not under the parent", projectTree.String()) } // check if projects are already enrolled/enabled @@ -732,85 +712,88 @@ func getUniqueCLAGroupIDs(projectCLAGroupMappings []*projects_cla_groups.Project return keys } -// buildTree helper function that builds tree based on nessted Projects -func buildProjectTree(projectSummaryList []*v2ProjectServiceModels.ProjectSummary) (*ProjectNode, error) { - f := logrus.Fields{ - "functionName": "v2.cla_groups.helpers.buildTree", +func buildProjectNode(projectSummaryList []*v2ProjectServiceModels.ProjectSummary) *ProjectNode { + root := &ProjectNode{ + ID: "", + Name: "", + Children: nil, } - log.WithFields(f).Debugf("Building project summary tree for : %+v ...", projectSummaryList) - var root ProjectNode + parentSFID := "" + parentName := "" + for _, projectSummaryEntry := range projectSummaryList { + // Get ParentProject + parentProjectModel, err := v2ProjectService.GetClient().GetParentProjectModel(projectSummaryEntry.ID) - if len(projectSummaryList) == 0 { - msg := "project summary list is empty" - log.WithFields(f).Debugf(msg) - return nil, errors.New(msg) - } - projectSummary := projectSummaryList[0] - // Get ParentProject ID - parentProjectID, err := v2ProjectService.GetClient().GetParentProject(projectSummary.ID) - if err != nil { - log.WithFields(f).Debugf("unable to get parent project for : %s ", projectSummary.ID) - } + if parentSFID == "" && err == nil && parentProjectModel != nil { + // Update our root node + root.Parent = nil + root.ID = parentProjectModel.ID + root.Name = parentProjectModel.Name - // Use Parent as root for projects to help in validation checks for enrolling / unenrolling - root = ProjectNode{ - Parent: nil, - ID: parentProjectID, - Children: []*ProjectNode{{ - ID: projectSummary.ID, - Name: projectSummary.Name, - }}, - } + // Save the parentSFID + parentSFID = parentProjectModel.ID + parentName = parentProjectModel.Name + } - // Aggregate projects - for _, summary := range projectSummaryList { - for _, child := range root.Children { - child.addChildren(summary) + if parentSFID != "" && err == nil && parentProjectModel != nil && parentSFID != parentProjectModel.ID { + //We have different parents !!! + log.Warnf("current parent Name: %s ID: %s does not match other parent Name: %s, parent ID: %s", parentName, parentSFID, parentProjectModel.Name, parentProjectModel.ID) } + + root.Children = append(root.Children, getLeafNodeFromProjectSFID(projectSummaryEntry.ID, parentName, parentSFID)) } - return &root, nil + return root } -func (n *ProjectNode) addChildren(summary *v2ProjectServiceModels.ProjectSummary) { - f := logrus.Fields{ - "functionName": "v2.cla_groups.helpers.addChidlren", +func getLeafNodeFromProjectSFID(projectSFID, parentName, parentSFID string) *ProjectNode { + + // Get ParentProject + projectModel, err := v2ProjectService.GetClient().GetProject(projectSFID) + if err != nil { + return nil } - log.WithFields(f).Debugf("Agrregating children for %+v ...", summary) - for _, subProject := range summary.Projects { - child := &ProjectNode{ - Parent: n, - ID: subProject.ID, - Name: subProject.Name, - } - n.Children = append(n.Children, child) - log.WithFields(f).Debugf("added child : %+v ", child) + node := &ProjectNode{ + ID: projectModel.ID, + Name: projectModel.Name, + Parent: &ProjectNode{ + ID: parentName, + Name: parentSFID, + }, + } + + // For this node, collect the list of child nodes... + for _, childNode := range projectModel.Projects { + node.Children = append(node.Children, getLeafNodeFromProjectSFID(childNode.ID, projectModel.Name, projectModel.ID)) + } + + return node } // findByID searches for given projectSFID recursively using DFS algorithm func findByID(node *ProjectNode, projectSFID string) *ProjectNode { - f := logrus.Fields{ - "functionName": "v2.cla_groups.helpers.fundByID", + if node == nil { + return nil } - log.WithFields(f).Debugf("searching for :%s ...", projectSFID) if node.ID == projectSFID { return node } - if len(node.Children) > 0 { - for _, child := range node.Children { - findByID(child, projectSFID) + for _, child := range node.Children { + foundNode := findByID(child, projectSFID) + if foundNode != nil { + return foundNode } } return nil } -// projectsExist searches for given list of projects in foundation items -func projectsExist(node *ProjectNode, projectSFIDs []string) bool { +// allProjectsExistInTree searches for given list of projects in foundation items +func allProjectsExistInTree(node *ProjectNode, projectSFIDs []string) bool { for _, projectSFID := range projectSFIDs { found := findByID(node, projectSFID) if found == nil { @@ -819,3 +802,28 @@ func projectsExist(node *ProjectNode, projectSFIDs []string) bool { } return true } + +func (n *ProjectNode) String() string { + if n == nil { + return "" + } + msg := fmt.Sprintf("projectSFID: '%s', projectName: '%s'\n", n.ID, n.Name) + + if n.Parent == nil { + msg = fmt.Sprintf("%s 'parentProjectSFID':'%s','parentProjectName':'%s'\n", msg, "", "") + } else { + msg = fmt.Sprintf("%s 'parentProjectSFID':'%s','parentProjectName':'%s'\n", msg, n.Parent.ID, n.Parent.Name) + } + + if len(n.Children) == 0 { + msg = fmt.Sprintf("%s children: no children\n", msg) + } else { + for _, child := range n.Children { + if child != nil { + msg = fmt.Sprintf("%s 'child': %s\n", msg, child.String()) + } + } + } + + return msg +} diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 28bb7f54f..72a6f1c12 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -38,8 +38,8 @@ type Client struct { var ( projectServiceClient *Client // Short term cache - only for the lifetime of this lambda - projectServiceModes = make(map[string]*models.ProjectOutputDetailed) - apiGWHost string + projectServiceModels = make(map[string]*models.ProjectOutputDetailed) + apiGWHost string ) // InitClient initializes the user_service client @@ -78,12 +78,12 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed } // Lookup in cache first - existingModel, exists := projectServiceModes[projectSFID] + existingModel, exists := projectServiceModels[projectSFID] if exists { - log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModes)) + log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) return existingModel, nil } - log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModes)) + log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModels)) tok, err := token.GetToken() if err != nil { @@ -100,8 +100,8 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed } // Update our cache for next time - projectServiceModes[projectSFID] = projectModel - log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModes)) + projectServiceModels[projectSFID] = projectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) return projectModel, nil } @@ -142,12 +142,12 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Lookup in cache first - existingModel, exists := projectServiceModes[projectSFID] + existingModel, exists := projectServiceModels[projectSFID] if exists { - log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModes)) + log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) return existingModel.Parent, nil } - log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModes)) + log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModels)) log.WithFields(f).Debug("looking up projectModel in SF by projectSFID") projectModel, err := pmm.GetProject(projectSFID) @@ -157,8 +157,8 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { } // Update our cache for next time - projectServiceModes[projectSFID] = projectModel - log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModes)) + projectServiceModels[projectSFID] = projectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) // Do they have a parent? if projectModel.Parent == "" || (projectModel.Foundation != nil && @@ -171,6 +171,82 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { return projectModel.Parent, nil } +// GetParentProjectModel returns the parent project model if there is a parent, otherwise returns nil +func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOutputDetailed, error) { + f := logrus.Fields{ + "functionName": "v2.project-service.client.GetParentProjectModel", + "projectSFID": projectSFID, + "apiGWHost": apiGWHost, + } + + // Lookup in cache first + var exists bool + var existingModel *models.ProjectOutputDetailed + var existingParentModel *models.ProjectOutputDetailed + + // Current project in the cache? + existingModel, exists = projectServiceModels[projectSFID] + if exists { + log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) + + // Parent in the cache? + existingParentModel, exists = projectServiceModels[existingModel.Parent] + if exists { + return existingParentModel, nil + } + + // Parent project not in the cache - lookup + parentProjectModel, err := pmm.GetProject(existingModel.Parent) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", existingModel.Parent) + return nil, err + } + + // Update our cache for next time + projectServiceModels[existingModel.Parent] = parentProjectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + + return parentProjectModel, nil + } + + log.WithFields(f).Debugf("cache miss - looking up projectModel in projectSFID: %s", projectSFID) + projectModel, err := pmm.GetProject(projectSFID) + if err != nil { + log.WithFields(f).Warnf("unable to lookup projectModel in projectModel service by projectSFID, error: %+v", err) + return nil, err + } + + // Update our cache for next time + projectServiceModels[projectSFID] = projectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + + // Do they have a parent? + if projectModel.Parent == "" || (projectModel.Foundation != nil && + (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { + log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) + return nil, nil + } + + // Parent in the cache? + existingParentModel, exists = projectServiceModels[projectModel.Parent] + if exists { + return existingParentModel, nil + } + + // Parent project not in the cache - lookup + parentProjectModel, err := pmm.GetProject(projectModel.Parent) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", projectModel.Parent) + return nil, err + } + + // Update our cache for next time + projectServiceModels[existingModel.Parent] = parentProjectModel + log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + + return parentProjectModel, nil +} + // IsTheLinuxFoundation returns true if the specified project SFID is the The Linux Foundation project func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { f := logrus.Fields{ diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 08507fc6a..fc57adb58 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -57,7 +57,8 @@ type ServiceInterface interface { InvalidateICLA(ctx context.Context, claGroupID string, userID string, authUser *auth.User, eventsService events.Service, eventArgs *events.LogEventArgs) error } -type service struct { +// Service structure/model +type Service struct { v1ProjectService project.Service v1CompanyService company.IService v1SignatureService signatures.SignatureService @@ -72,8 +73,8 @@ type service struct { func NewService(awsSession *session.Session, signaturesBucketName string, v1ProjectService project.Service, v1CompanyService company.IService, v1SignatureService signatures.SignatureService, - pcgRepo projects_cla_groups.Repository, v1SignatureRepo signatures.SignatureRepository, usersService users.Service) *service { - return &service{ + pcgRepo projects_cla_groups.Repository, v1SignatureRepo signatures.SignatureRepository, usersService users.Service) *Service { + return &Service{ v1ProjectService: v1ProjectService, v1CompanyService: v1CompanyService, v1SignatureService: v1SignatureService, @@ -85,7 +86,8 @@ func NewService(awsSession *session.Session, signaturesBucketName string, v1Proj } } -func (s *service) GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) { +// GetProjectCompanySignatures return the signatures for the specified project and company information +func (s *Service) GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) { pm, err := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { return nil, err @@ -106,6 +108,7 @@ func (s *service) GetProjectCompanySignatures(ctx context.Context, companyID, co return v2SignaturesReplaceCompanyID(resp, companyID, companySFID) } +// eclaSigCsvLine returns a single ECLA signature CSV line func eclaSigCsvLine(sig *v1Models.CorporateContributor) string { var dateTime string t, err := utils.ParseDateTime(sig.Timestamp) @@ -118,7 +121,8 @@ func eclaSigCsvLine(sig *v1Models.CorporateContributor) string { return fmt.Sprintf("\n%s,%s,%s,%s,\"%s\"", sig.GithubID, sig.LinuxFoundationID, sig.Name, sig.Email, dateTime) } -func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companyID string) ([]byte, error) { +// GetClaGroupCorporateContributorsCsv returns the CLA Group corporate contributors as a CSV +func (s *Service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGroupID string, companyID string) ([]byte, error) { var b bytes.Buffer result, err := s.v1SignatureService.GetClaGroupCorporateContributors(ctx, claGroupID, &companyID, nil) if err != nil { @@ -136,7 +140,8 @@ func (s service) GetClaGroupCorporateContributorsCsv(ctx context.Context, claGro return b.Bytes(), nil } -func (s service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { +// GetProjectIclaSignaturesCsv returns the ICLA signatures as a CSV file for the specified CLA Group +func (s *Service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { var b bytes.Buffer result, err := s.v1SignatureService.GetClaGroupICLASignatures(ctx, claGroupID, nil, nil, nil, 0, "") if err != nil { @@ -149,7 +154,8 @@ func (s service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID str return b.Bytes(), nil } -func (s service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { +// GetProjectCclaSignaturesCsv returns the ICLA signatures as a CSV file for the specified CLA Group and search term filters +func (s *Service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { f := logrus.Fields{ "functionName": "GetProjectCclaSignaturesCsv", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -173,7 +179,8 @@ func (s service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID str return b.Bytes(), nil } -func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) { +// GetProjectIclaSignatures returns the ICLA signatures for the specified CLA Group and search term filters +func (s *Service) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) { f := logrus.Fields{ "functionName": "v2.signatures.service.GetProjectIclaSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -199,7 +206,8 @@ func (s service) GetProjectIclaSignatures(ctx context.Context, claGroupID string return &out, nil } -func (s service) GetSignedDocument(ctx context.Context, signatureID string) (*models.SignedDocument, error) { +// GetSignedDocument returns the signed document for the specified signature ID +func (s *Service) GetSignedDocument(ctx context.Context, signatureID string) (*models.SignedDocument, error) { sig, err := s.v1SignatureService.GetSignature(ctx, signatureID) if err != nil { return nil, err @@ -224,7 +232,8 @@ func (s service) GetSignedDocument(ctx context.Context, signatureID string) (*mo }, nil } -func (s service) GetSignedCclaZipPdf(claGroupID string) (*models.URLObject, error) { +// GetSignedCclaZipPdf returns the signed CCLA Zip PDF reference +func (s *Service) GetSignedCclaZipPdf(claGroupID string) (*models.URLObject, error) { url := utils.SignedClaGroupZipFilename(claGroupID, CCLA) ok, err := s.IsZipPresentOnS3(url) if err != nil { @@ -242,7 +251,8 @@ func (s service) GetSignedCclaZipPdf(claGroupID string) (*models.URLObject, erro }, nil } -func (s service) GetSignedIclaZipPdf(claGroupID string) (*models.URLObject, error) { +// GetSignedIclaZipPdf returns the signed ICLA Zip PDF reference +func (s *Service) GetSignedIclaZipPdf(claGroupID string) (*models.URLObject, error) { url := utils.SignedClaGroupZipFilename(claGroupID, ICLA) ok, err := s.IsZipPresentOnS3(url) if err != nil { @@ -260,7 +270,8 @@ func (s service) GetSignedIclaZipPdf(claGroupID string) (*models.URLObject, erro }, nil } -func (s service) IsZipPresentOnS3(zipFilePath string) (bool, error) { +// IsZipPresentOnS3 returns true if the specified file is present in S3 +func (s *Service) IsZipPresentOnS3(zipFilePath string) (bool, error) { _, err := s.s3.GetObject(&s3.GetObjectInput{ Bucket: aws.String(s.signaturesBucket), Key: aws.String(zipFilePath), @@ -275,7 +286,8 @@ func (s service) IsZipPresentOnS3(zipFilePath string) (bool, error) { return true, nil } -func (s service) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID string, searchTerm *string) (*models.CorporateContributorList, error) { +// GetClaGroupCorporateContributors returns the list of corporate contributors for the specified CLA Group and company +func (s *Service) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID string, searchTerm *string) (*models.CorporateContributorList, error) { f := logrus.Fields{ "functionName": "GetClaGroupCorporateContributors", "claGroupID": claGroupID, @@ -301,7 +313,8 @@ func (s service) GetClaGroupCorporateContributors(ctx context.Context, claGroupI return &resp, nil } -func (s service) InvalidateICLA(ctx context.Context, claGroupID string, userID string, authUser *auth.User, eventsService events.Service, eventArgs *events.LogEventArgs) error { +// InvalidateICLA invalidates the specified signature record using the supplied parameters +func (s *Service) InvalidateICLA(ctx context.Context, claGroupID string, userID string, authUser *auth.User, eventsService events.Service, eventArgs *events.LogEventArgs) error { f := logrus.Fields{ "functionName": "v2.signatures.service.InvalidateICLA", "claGroupID": claGroupID, From 693c73c9249d8081be72d7a6eebbfac95979dae0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 25 Jun 2021 16:17:00 -0700 Subject: [PATCH 0336/1276] Updated golang CI/CD Version (#3015) --- .circleci/config.yml | 4 +- cla-backend-go/Makefile | 9 ++-- cla-backend-go/swagger/cla.v1.yaml | 14 ++++-- cla-backend-go/swagger/cla.v2.yaml | 79 +++++------------------------- 4 files changed, 29 insertions(+), 77 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d70015f06..93a29ae55 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -198,7 +198,7 @@ jobs: buildGoBackend: &buildGoBackendAnchor docker: - - image: circleci/golang:1.15.6 + - image: circleci/golang:1.16.5 working_directory: /go/src/github.com/communitybridge/easycla/ steps: - checkout @@ -888,7 +888,7 @@ jobs: functionalTestsGo: &functionalTestsGo docker: - - image: circleci/golang:1.15.6 + - image: circleci/golang:1.16.5 steps: - attach_workspace: at: ~/ diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 852fe6fe4..a9e8110d6 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -116,7 +116,8 @@ swagger-build-project-service: swagger -q generate client \ --copyright-file=copyright-header.txt \ -t v2/project-service \ - -f swagger/project-service.yaml + -f swagger/project-service.yaml \ + --skip-validation # needed, currently seeing: body.default.Filename in body must be of type string: "null", and definitions.artifact-upload-init-request.default.Filename in body must be of type string: "null" issues, notified PS team swagger-build-organization-service: @echo @@ -127,7 +128,8 @@ swagger-build-organization-service: swagger -q generate client \ --copyright-file=copyright-header.txt \ -t v2/organization-service \ - -f swagger/organization-service.yaml + -f swagger/organization-service.yaml \ + --skip-validation # needed, currently seeing: - username in query must be of type string: "null" swagger-build-user-service: @echo @@ -138,7 +140,8 @@ swagger-build-user-service: swagger -q generate client \ --copyright-file=copyright-header.txt \ -t v2/user-service \ - -f swagger/user-service.yaml + -f swagger/user-service.yaml \ + --skip-validation # needed, many validation errors swagger-build-acs-service: @echo diff --git a/cla-backend-go/swagger/cla.v1.yaml b/cla-backend-go/swagger/cla.v1.yaml index e9c8836e1..dd822172b 100644 --- a/cla-backend-go/swagger/cla.v1.yaml +++ b/cla-backend-go/swagger/cla.v1.yaml @@ -65,16 +65,14 @@ paths: description: The unique request ID value - assigned/set by the API Gateway based on the session schema: $ref: '#/definitions/health' - '503': - description: '' - schema: - $ref: '#/definitions/health' '400': $ref: '#/responses/invalid-request' '401': $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '503': + $ref: '#/responses/service-unavailable' tags: - health @@ -3180,6 +3178,14 @@ responses: description: The unique request ID value - assigned/set by the API Gateway based on the session schema: $ref: '#/definitions/error-response' + service-unavailable: + description: 'Service unavailable' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/error-response' conflict: description: The request could not be completed due to a conflict with the current state of the target resource. headers: diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index dbf93745e..e370f0929 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -57,16 +57,14 @@ paths: description: The unique request ID value - assigned/set by the API Gateway based on the session schema: $ref: '#/definitions/health' - '503': - description: 'Service unavailable' - schema: - $ref: '#/definitions/health' '400': $ref: '#/responses/invalid-request' '401': $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '503': + $ref: '#/responses/service-unavailable' tags: - health @@ -3708,6 +3706,14 @@ responses: description: The unique request ID value - assigned/set by the API Gateway based on the session schema: $ref: '#/definitions/error-response' + service-unavailable: + description: Service unavailable + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/error-response' conflict: description: Duplicate Resource headers: @@ -3838,13 +3844,6 @@ parameters: in: query type: string pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id - signingEntityName: - name: signingEntityName - description: The signing entity name of a Company (Salesforce and EasyCLA) - in: query - type: string - required: true - pattern: '[^<>]*' # allow everything except greater than and less than symbols companyID: name: companyID description: The internal company ID representing signing entity name instance (EasyCLA) @@ -3935,12 +3934,6 @@ parameters: pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?4[a-fA-F0-9]{3}-?[89ab][a-fA-F0-9]{3}-?[a-fA-F0-9]{12}$' # uuidv4 minLength: 5 maxLength: 255 - companySFID: - name: companySFID - description: salesforce id of the company - in: query - type: string - pattern: '^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$' # see: https://stackoverflow.com/questions/9742913/validating-a-salesforce-id gerritHost: name: gerritHost description: host of the gerrit server @@ -4112,9 +4105,6 @@ definitions: template-pdfs: $ref: './common/template-pdfs.yaml' - github-organizations: - $ref: './common/github-organizations.yaml' - github-organization: $ref: './common/github-organization.yaml' @@ -4375,7 +4365,7 @@ definitions: Employees: type: string description: The number of employees of the company - example: "500 - 4999, 10000+" + example: "500 - 4999" pattern: '^([\w\d\s\-\,\.\/]+){2,}$' signingEntityNames: type: array @@ -4527,17 +4517,6 @@ definitions: companySFID: type: string - contributors: - type: object - title: contributors - description: List of contributor roles for a user - properties: - list: - type: array - items: - $ref: - '#/definitions/contributor' - contributor: type: object title: Contributor @@ -4569,42 +4548,6 @@ definitions: example: 'contributor' x-omitempty: false - company-owner: - type: object - title: Company Owner - description: Company Owner - properties: - lf_username: - type: string - description: 'the LF username' - x-omitempty: false - example: 'username' - name: - type: string - description: 'name of the user' - example: 'john' - x-omitempty: false - user_sfid: - type: string - description: 'the user SalesForce ID' - x-omitempty: false - email: - $ref: './common/properties/email.yaml' - x-omitempty: false - type: - type: string - x-omitempty: false - example: 'contact' - assigned_on: - type: string - x-omitempty: false - company_sfid: - type: string - description: 'the Organization SalesForce ID' - x-omitempty: false - example: 'abc134234adsdf43' - - cla-manager-designee: type: object title: Company CLA Designee From d21a918d47f9b24cfdf1c4d96ce4dd5f4035f976 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 25 Jun 2021 17:21:22 -0700 Subject: [PATCH 0337/1276] Resolved PCC-1158 - Event Log Created When Template Updated (#3016) --- cla-backend-go/events/event_data.go | 399 +++++++++++++------------ cla-backend-go/template/repository.go | 21 ++ cla-backend-go/template/service.go | 17 ++ cla-backend-go/v2/template/handlers.go | 19 +- 4 files changed, 264 insertions(+), 192 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index ec1be67d1..e0fefa7c1 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -1,6 +1,6 @@ // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT -//nolint + package events import ( @@ -10,105 +10,114 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" ) +const ( + // noReason constant value + noReason = "No reason" +) + // EventData returns event data string which is used for event logging and containsPII field type EventData interface { GetEventDetailsString(args *LogEventArgs) (eventData string, containsPII bool) GetEventSummaryString(args *LogEventArgs) (eventData string, containsPII bool) } +// CLAGroupEnrolledProjectData event data model type CLAGroupEnrolledProjectData struct { } +// CLAGroupUnenrolledProjectData event data model type CLAGroupUnenrolledProjectData struct { } +// ProjectServiceCLAEnabledData event data model type ProjectServiceCLAEnabledData struct { } +// ProjectServiceCLADisabledData event data model type ProjectServiceCLADisabledData struct { } -// RepositoryAddedEventData . . . +// RepositoryAddedEventData event data model type RepositoryAddedEventData struct { RepositoryName string } -// RepositoryDisabledEventData . . . +// RepositoryDisabledEventData event data model type RepositoryDisabledEventData struct { RepositoryName string } -// RepositoryRenamedEventData . . . +// RepositoryRenamedEventData event data model type RepositoryRenamedEventData struct { NewRepositoryName string OldRepositoryName string } -// RepositoryTransferredEventData . . . +// RepositoryTransferredEventData event data model type RepositoryTransferredEventData struct { RepositoryName string OldGithubOrgName string NewGithubOrgName string } -// RepositoryUpdatedEventData . . . +// RepositoryUpdatedEventData event data model type RepositoryUpdatedEventData struct { RepositoryName string } -// RepositoryBranchProtectionAddedEventData . . . +// RepositoryBranchProtectionAddedEventData event data model type RepositoryBranchProtectionAddedEventData struct { RepositoryName string } -// RepositoryBranchProtectionDisabledEventData . . . +// RepositoryBranchProtectionDisabledEventData event data model type RepositoryBranchProtectionDisabledEventData struct { RepositoryName string } -// RepositoryBranchProtectionUpdatedEventData . . . +// RepositoryBranchProtectionUpdatedEventData event data model type RepositoryBranchProtectionUpdatedEventData struct { RepositoryName string } -// GerritProjectDeletedEventData . . . +// GerritProjectDeletedEventData event data model type GerritProjectDeletedEventData struct { DeletedCount int } -// GerritAddedEventData . . . +// GerritAddedEventData data model type GerritAddedEventData struct { GerritRepositoryName string } -// GerritDeletedEventData . . . +// GerritDeletedEventData data model type GerritDeletedEventData struct { GerritRepositoryName string } -// GerritUserAddedEventData . . . +// GerritUserAddedEventData data model type GerritUserAddedEventData struct { Username string GroupName string } -// GerritUserRemovedEventData . . . +// GerritUserRemovedEventData data model type GerritUserRemovedEventData struct { Username string GroupName string } -// GitHubProjectDeletedEventData . . . +// GitHubProjectDeletedEventData data model type GitHubProjectDeletedEventData struct { DeletedCount int } -// SignatureProjectInvalidatedEventData . . . +// SignatureProjectInvalidatedEventData data model type SignatureProjectInvalidatedEventData struct { InvalidatedCount int } -//SignatureInvalidatedApprovalRejectionEventData . . . +//SignatureInvalidatedApprovalRejectionEventData data model type SignatureInvalidatedApprovalRejectionEventData struct { GHUsername string Email string @@ -117,47 +126,49 @@ type SignatureInvalidatedApprovalRejectionEventData struct { CLAGroupID string } -// UserCreatedEventData . . . +// UserCreatedEventData data model type UserCreatedEventData struct{} -// UserDeletedEventData . . . +// UserDeletedEventData data model type UserDeletedEventData struct { DeletedUserID string } -// UserUpdatedEventData . . . +// UserUpdatedEventData data model type UserUpdatedEventData struct{} -// CompanyACLRequestAddedEventData . . . +// CompanyACLRequestAddedEventData data model type CompanyACLRequestAddedEventData struct { UserName string UserID string UserEmail string } -// CompanyACLRequestApprovedEventData . . . +// CompanyACLRequestApprovedEventData data model type CompanyACLRequestApprovedEventData struct { UserName string UserID string UserEmail string } -// CompanyACLRequestDeniedEventData . . . +// CompanyACLRequestDeniedEventData data model type CompanyACLRequestDeniedEventData struct { UserName string UserID string UserEmail string } -// CompanyACLUserAddedEventData . . . +// CompanyACLUserAddedEventData data model type CompanyACLUserAddedEventData struct { UserLFID string } -// CLATemplateCreatedEventData . . . -type CLATemplateCreatedEventData struct{} +// CLATemplateCreatedEventData data model +type CLATemplateCreatedEventData struct { + TemplateName string +} -// GitHubOrganizationAddedEventData . . . +// GitHubOrganizationAddedEventData data model type GitHubOrganizationAddedEventData struct { GitHubOrganizationName string AutoEnabled bool @@ -165,34 +176,34 @@ type GitHubOrganizationAddedEventData struct { BranchProtectionEnabled bool } -// GitHubOrganizationDeletedEventData . . . +// GitHubOrganizationDeletedEventData data model type GitHubOrganizationDeletedEventData struct { GitHubOrganizationName string } -// GitHubOrganizationUpdatedEventData . . . +// GitHubOrganizationUpdatedEventData data model type GitHubOrganizationUpdatedEventData struct { GitHubOrganizationName string AutoEnabled bool AutoEnabledClaGroupID string } -// CCLAApprovalListRequestCreatedEventData . . . +// CCLAApprovalListRequestCreatedEventData data model type CCLAApprovalListRequestCreatedEventData struct { RequestID string } -// CCLAApprovalListRequestApprovedEventData . . . +// CCLAApprovalListRequestApprovedEventData data model type CCLAApprovalListRequestApprovedEventData struct { RequestID string } -// CCLAApprovalListRequestRejectedEventData . . . +// CCLAApprovalListRequestRejectedEventData data model type CCLAApprovalListRequestRejectedEventData struct { RequestID string } -// CLAManagerCreatedEventData . . . +// CLAManagerCreatedEventData data model type CLAManagerCreatedEventData struct { CompanyName string ProjectName string @@ -201,7 +212,7 @@ type CLAManagerCreatedEventData struct { UserLFID string } -// CLAManagerDeletedEventData . . . +// CLAManagerDeletedEventData data model type CLAManagerDeletedEventData struct { CompanyName string ProjectName string @@ -210,7 +221,7 @@ type CLAManagerDeletedEventData struct { UserLFID string } -// CLAManagerRequestCreatedEventData . . . +// CLAManagerRequestCreatedEventData data model type CLAManagerRequestCreatedEventData struct { RequestID string CompanyName string @@ -220,7 +231,7 @@ type CLAManagerRequestCreatedEventData struct { UserLFID string } -// CLAManagerRequestApprovedEventData . . . +// CLAManagerRequestApprovedEventData data model type CLAManagerRequestApprovedEventData struct { RequestID string CompanyName string @@ -231,7 +242,7 @@ type CLAManagerRequestApprovedEventData struct { ManagerEmail string } -// CLAManagerRequestDeniedEventData . . . +// CLAManagerRequestDeniedEventData data model type CLAManagerRequestDeniedEventData struct { RequestID string CompanyName string @@ -242,7 +253,7 @@ type CLAManagerRequestDeniedEventData struct { ManagerEmail string } -// CLAManagerRequestDeletedEventData . . . +// CLAManagerRequestDeletedEventData data model type CLAManagerRequestDeletedEventData struct { RequestID string CompanyName string @@ -253,71 +264,71 @@ type CLAManagerRequestDeletedEventData struct { ManagerEmail string } -// CLAApprovalListAddEmailData . . . +// CLAApprovalListAddEmailData data model type CLAApprovalListAddEmailData struct { ApprovalListEmail string } -// CLAApprovalListRemoveEmailData . . . +// CLAApprovalListRemoveEmailData data model type CLAApprovalListRemoveEmailData struct { ApprovalListEmail string } -// CLAApprovalListAddDomainData . . . +// CLAApprovalListAddDomainData data model type CLAApprovalListAddDomainData struct { ApprovalListDomain string } -// CLAApprovalListRemoveDomainData . . . +// CLAApprovalListRemoveDomainData data model type CLAApprovalListRemoveDomainData struct { ApprovalListDomain string } -// CLAApprovalListAddGitHubUsernameData . . . +// CLAApprovalListAddGitHubUsernameData data model type CLAApprovalListAddGitHubUsernameData struct { ApprovalListGitHubUsername string } -// CLAApprovalListRemoveGitHubUsernameData . . . +// CLAApprovalListRemoveGitHubUsernameData data model type CLAApprovalListRemoveGitHubUsernameData struct { ApprovalListGitHubUsername string } -// CLAApprovalListAddGitHubOrgData . . . +// CLAApprovalListAddGitHubOrgData data model type CLAApprovalListAddGitHubOrgData struct { ApprovalListGitHubOrg string } -// CLAApprovalListRemoveGitHubOrgData . . . +// CLAApprovalListRemoveGitHubOrgData data model type CLAApprovalListRemoveGitHubOrgData struct { ApprovalListGitHubOrg string } -// ApprovalListGitHubOrganizationAddedEventData . . . +// ApprovalListGitHubOrganizationAddedEventData data model type ApprovalListGitHubOrganizationAddedEventData struct { GitHubOrganizationName string } -// ApprovalListGitHubOrganizationDeletedEventData . . . +// ApprovalListGitHubOrganizationDeletedEventData data model type ApprovalListGitHubOrganizationDeletedEventData struct { GitHubOrganizationName string } -// ClaManagerAccessRequestAddedEventData . . . +// ClaManagerAccessRequestAddedEventData data model type ClaManagerAccessRequestAddedEventData struct { ProjectName string CompanyName string } -// ClaManagerAccessRequestDeletedEventData . . . +// ClaManagerAccessRequestDeletedEventData data model type ClaManagerAccessRequestDeletedEventData struct { RequestID string } -// CLAGroupCreatedEventData . . . +// CLAGroupCreatedEventData data model type CLAGroupCreatedEventData struct{} -// CLAGroupUpdatedEventData . . . +// CLAGroupUpdatedEventData data model type CLAGroupUpdatedEventData struct { NewClaGroupName string NewClaGroupDescription string @@ -325,34 +336,34 @@ type CLAGroupUpdatedEventData struct { OldClaGroupDescription string } -// CLAGroupDeletedEventData . . . +// CLAGroupDeletedEventData data model type CLAGroupDeletedEventData struct{} -// ContributorNotifyCompanyAdminData . . . +// ContributorNotifyCompanyAdminData data model type ContributorNotifyCompanyAdminData struct { AdminName string AdminEmail string } -// ContributorNotifyCLADesignee . . . +// ContributorNotifyCLADesignee data model type ContributorNotifyCLADesignee struct { DesigneeName string DesigneeEmail string } -// ContributorAssignCLADesignee . . . +// ContributorAssignCLADesignee data model type ContributorAssignCLADesignee struct { DesigneeName string DesigneeEmail string } -// UserConvertToContactData . . . +// UserConvertToContactData data model type UserConvertToContactData struct { UserName string UserEmail string } -// AssignRoleScopeData . . . +// AssignRoleScopeData data model type AssignRoleScopeData struct { Role string Scope string @@ -360,7 +371,7 @@ type AssignRoleScopeData struct { UserEmail string } -// ClaManagerRoleCreatedData . . . +// ClaManagerRoleCreatedData data model type ClaManagerRoleCreatedData struct { Role string Scope string @@ -368,7 +379,7 @@ type ClaManagerRoleCreatedData struct { UserEmail string } -// ClaManagerRoleDeletedData . . . +// ClaManagerRoleDeletedData data model type ClaManagerRoleDeletedData struct { Role string Scope string @@ -376,7 +387,7 @@ type ClaManagerRoleDeletedData struct { UserEmail string } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAGroupEnrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The project %s (%s) was enrolled into the CLA Group %s (%s)", args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID) if args.UserName != "" { @@ -386,7 +397,7 @@ func (ed *CLAGroupEnrolledProjectData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAGroupUnenrolledProjectData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The project %s (%s) was unenrolled from the CLA Group %s (%s)", args.ProjectName, args.ProjectID, args.CLAGroupName, args.CLAGroupID) if args.UserName != "" { @@ -396,7 +407,7 @@ func (ed *CLAGroupUnenrolledProjectData) GetEventDetailsString(args *LogEventArg return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ProjectServiceCLAEnabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Service for the project %s (%s) was enabled", args.ProjectName, args.ProjectID) if args.UserName != "" { @@ -406,7 +417,7 @@ func (ed *ProjectServiceCLAEnabledData) GetEventDetailsString(args *LogEventArgs return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ProjectServiceCLADisabledData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Service for the project %s (%s) was disabled", args.ProjectName, args.ProjectID) if args.UserName != "" { @@ -416,7 +427,7 @@ func (ed *ProjectServiceCLADisabledData) GetEventDetailsString(args *LogEventArg return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository: %s was added for the project %s", ed.RepositoryName, args.ProjectName) if args.UserName != "" { @@ -426,7 +437,7 @@ func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (s return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was deleted for the project %s", ed.RepositoryName, args.ProjectName) if args.UserName != "" { @@ -436,7 +447,7 @@ func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryRenamedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository renamed from %s to %s for the project %s", ed.OldRepositoryName, ed.NewRepositoryName, args.ProjectName) if args.UserName != "" { @@ -446,7 +457,7 @@ func (ed *RepositoryRenamedEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryTransferredEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository : %s transferred from %s to %s Github Organization for the project %s", ed.RepositoryName, ed.OldGithubOrgName, ed.NewGithubOrgName, args.ProjectName) if args.UserName != "" { @@ -456,7 +467,7 @@ func (ed *RepositoryTransferredEventData) GetEventDetailsString(args *LogEventAr return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was updated for the project %s", ed.RepositoryName, args.ProjectName) if args.UserName != "" { @@ -466,7 +477,7 @@ func (ed *RepositoryUpdatedEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryBranchProtectionAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository branch protection %s was added for the project %s", ed.RepositoryName, args.ProjectName) if args.UserName != "" { @@ -476,7 +487,7 @@ func (ed *RepositoryBranchProtectionAddedEventData) GetEventDetailsString(args * return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryBranchProtectionDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository branch protection %s was disabled for the project %s", ed.RepositoryName, args.ProjectName) if args.UserName != "" { @@ -486,7 +497,7 @@ func (ed *RepositoryBranchProtectionDisabledEventData) GetEventDetailsString(arg return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository branch protection %s was updated for the project %s", ed.RepositoryName, args.ProjectName) if args.UserName != "" { @@ -496,7 +507,7 @@ func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventDetailsString(args return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *UserCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User was added : %+v", args.UserModel) if args.UserName != "" { @@ -506,7 +517,7 @@ func (ed *UserCreatedEventData) GetEventDetailsString(args *LogEventArgs) (strin return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *UserUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User details updated: %+v", *args.UserModel) if args.UserName != "" { @@ -516,7 +527,7 @@ func (ed *UserUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (strin return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *UserDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User ID: %s was deleted", ed.DeletedUserID) if args.UserName != "" { @@ -526,7 +537,7 @@ func (ed *UserDeletedEventData) GetEventDetailsString(args *LogEventArgs) (strin return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CompanyACLRequestAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s added pending invite with ID: %s and Email: %s for Company: %s", ed.UserName, ed.UserID, ed.UserEmail, args.CompanyName) @@ -537,7 +548,7 @@ func (ed *CompanyACLRequestAddedEventData) GetEventDetailsString(args *LogEventA return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CompanyACLRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Access Aproved for User: %s, ID: %s, Email: %s Company Group: %s", ed.UserName, args.CompanyName, ed.UserID, ed.UserEmail) @@ -548,7 +559,7 @@ func (ed *CompanyACLRequestApprovedEventData) GetEventDetailsString(args *LogEve return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CompanyACLRequestDeniedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Access Denied for User: %s, ID: %s, Email: %s Company Group: %s.", ed.UserName, args.CompanyName, ed.UserID, ed.UserEmail) @@ -559,7 +570,7 @@ func (ed *CompanyACLRequestDeniedEventData) GetEventDetailsString(args *LogEvent return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CompanyACLUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User with LF Username: %s added to the ACL for Company: %s", args.LFUser.Name, args.CompanyName) @@ -570,9 +581,18 @@ func (ed *CompanyACLUserAddedEventData) GetEventDetailsString(args *LogEventArgs return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLATemplateCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("PDF Templates created for Project: %s", args.ProjectName) + data := "A CLA Group template was created or updated" // nolint + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if ed.TemplateName != "" { + data = data + fmt.Sprintf(" using template: %s", ed.TemplateName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -580,7 +600,7 @@ func (ed *CLATemplateCreatedEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("GitHub Organization: %s was added with auto-enabled: %t, with branch protection enabled: %t", ed.GitHubOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) @@ -594,7 +614,7 @@ func (ed *GitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEvent return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("GitHub Organization: %s was deleted ", ed.GitHubOrganizationName) if args.UserName != "" { @@ -604,7 +624,7 @@ func (ed *GitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEve return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("GitHub Organization:%s was updated with auto-enabled: %t", ed.GitHubOrganizationName, ed.AutoEnabled) @@ -618,21 +638,21 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEve return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CCLAApprovalListRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s approved a CCLA Approval Request for Project: %s and Company: %s with Request ID: %s.", args.UserName, args.ProjectName, args.CompanyName, ed.RequestID) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CCLAApprovalListRequestRejectedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s rejected a CCLA Approval Request for Project: %s, Company: %s with Request ID: %s.", args.UserName, args.ProjectName, args.CompanyName, ed.RequestID) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAManagerRequestCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s, LFID: %s, Email: %s added CLA Manager Request: %s for Company: %s, Project: %s.", ed.UserName, ed.UserLFID, ed.UserEmail, ed.RequestID, ed.CompanyName, ed.ProjectName) @@ -643,7 +663,7 @@ func (ed *CLAManagerRequestCreatedEventData) GetEventDetailsString(args *LogEven return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAManagerCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user: %s LFID: %s, email: %s was added as CLA Manager", ed.UserName, ed.UserLFID, ed.UserEmail) if args.CLAGroupName != "" { @@ -665,7 +685,7 @@ func (ed *CLAManagerCreatedEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAManagerDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user: %s LFID: %s, email: %s was removed as CLA Manager", ed.UserName, ed.UserLFID, ed.UserEmail) if args.CLAGroupName != "" { @@ -687,7 +707,7 @@ func (ed *CLAManagerDeletedEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAManagerRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager Request: %s was approved for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s", ed.RequestID, ed.UserName, ed.UserEmail, ed.ManagerName, ed.ManagerEmail, ed.CompanyName, ed.ProjectName) @@ -698,7 +718,7 @@ func (ed *CLAManagerRequestApprovedEventData) GetEventDetailsString(args *LogEve return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAManagerRequestDeniedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager Request: %s was denied for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s", ed.RequestID, ed.UserName, ed.UserEmail, ed.ManagerName, ed.ManagerEmail, ed.CompanyName, ed.ProjectName) @@ -709,7 +729,7 @@ func (ed *CLAManagerRequestDeniedEventData) GetEventDetailsString(args *LogEvent return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAManagerRequestDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Manager Request: %s was deleted for User %s, Email: %s by Manager: %s, Email: %s for Company: %s, Project: %s", ed.RequestID, ed.UserName, ed.UserEmail, ed.ManagerName, ed.ManagerEmail, ed.CompanyName, ed.ProjectName) @@ -720,7 +740,7 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventDetailsString(args *LogEven return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListAddEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address %s was added to the approval list", ed.ApprovalListEmail) if args.CLAGroupName != "" { @@ -742,7 +762,7 @@ func (ed *CLAApprovalListAddEmailData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListRemoveEmailData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address %s was removed from the approval list", ed.ApprovalListEmail) if args.CLAGroupName != "" { @@ -764,7 +784,7 @@ func (ed *CLAApprovalListRemoveEmailData) GetEventDetailsString(args *LogEventAr return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListAddDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address domain %s was added to the approval list", ed.ApprovalListDomain) if args.CLAGroupName != "" { @@ -786,7 +806,7 @@ func (ed *CLAApprovalListAddDomainData) GetEventDetailsString(args *LogEventArgs return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListRemoveDomainData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address domain %s was removed from the approval list", ed.ApprovalListDomain) if args.CLAGroupName != "" { @@ -808,7 +828,7 @@ func (ed *CLAApprovalListRemoveDomainData) GetEventDetailsString(args *LogEventA return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListAddGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub username %s was added to the approval list", ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { @@ -830,7 +850,7 @@ func (ed *CLAApprovalListAddGitHubUsernameData) GetEventDetailsString(args *LogE return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub username %s was removed from the approval list", ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { @@ -852,7 +872,7 @@ func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventDetailsString(args *L return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListAddGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub organization %s was added to the approval list", ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { @@ -874,7 +894,7 @@ func (ed *CLAApprovalListAddGitHubOrgData) GetEventDetailsString(args *LogEventA return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub organization %s was removed from the approval list", ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { @@ -896,7 +916,7 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEve return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CCLAApprovalListRequestCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CCLA Approval Request was created for the Project: %s, Company: %s with Request ID: %s", args.ProjectName, args.CompanyName, ed.RequestID) @@ -907,7 +927,7 @@ func (ed *CCLAApprovalListRequestCreatedEventData) GetEventDetailsString(args *L return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub Organization: %s was added to the approval list for the Company %s, Project: %s", ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) @@ -918,7 +938,7 @@ func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventDetailsString(ar return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub Organization: %s was removed from the approval list for the Company: %s, Project: %s", ed.GitHubOrganizationName, args.CompanyName, args.ProjectName) @@ -929,21 +949,21 @@ func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventDetailsString( return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ClaManagerAccessRequestAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s has requested to be CLA Manager for Company %s, Project: %s.", args.UserName, ed.CompanyName, ed.ProjectName) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ClaManagerAccessRequestDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s has deleted CLA Manager Request with ID: %s.", args.UserName, ed.RequestID) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Group ID: %s, Name: %s was created", args.ProjectID, args.ProjectName) if args.UserName != "" { @@ -953,7 +973,7 @@ func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (s return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { var nameUpdated bool @@ -978,7 +998,7 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *CLAGroupDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("CLA Group ID: %s was deleted", args.ProjectID) if args.UserName != "" { @@ -988,7 +1008,7 @@ func (ed *CLAGroupDeletedEventData) GetEventDetailsString(args *LogEventArgs) (s return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GerritProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d Gerrit Repositories were deleted due to CLA Group/Project: %s deletion", ed.DeletedCount, args.ProjectName) @@ -999,7 +1019,7 @@ func (ed *GerritProjectDeletedEventData) GetEventDetailsString(args *LogEventArg return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GerritAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Gerrit Repository: %s was added", ed.GerritRepositoryName) if args.UserName != "" { @@ -1009,7 +1029,7 @@ func (ed *GerritAddedEventData) GetEventDetailsString(args *LogEventArgs) (strin return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GerritDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("Gerrit Repository: %s was deleted", ed.GerritRepositoryName) if args.UserName != "" { @@ -1019,7 +1039,7 @@ func (ed *GerritDeletedEventData) GetEventDetailsString(args *LogEventArgs) (str return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GerritUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The username %s was add to the gerrit group %s", ed.Username, ed.GroupName) if args.UserName != "" { @@ -1029,7 +1049,7 @@ func (ed *GerritUserAddedEventData) GetEventDetailsString(args *LogEventArgs) (s return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GerritUserRemovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The username %s was removed from the gerrit group %s", ed.Username, ed.GroupName) if args.UserName != "" { @@ -1039,7 +1059,7 @@ func (ed *GerritUserRemovedEventData) GetEventDetailsString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *GitHubProjectDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d GitHub Repositories were deleted due to CLA Group/Project: [%s] deletion", ed.DeletedCount, args.ProjectName) @@ -1050,7 +1070,7 @@ func (ed *GitHubProjectDeletedEventData) GetEventDetailsString(args *LogEventArg return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *SignatureProjectInvalidatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d Signatures were invalidated (approved set to false) due to CLA Group/Project: %s deletion", ed.InvalidatedCount, args.ProjectName) @@ -1061,9 +1081,9 @@ func (ed *SignatureProjectInvalidatedEventData) GetEventDetailsString(args *LogE return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - reason := "No reason" + reason := noReason if ed.Email != "" { reason = fmt.Sprintf("GH Username: %s approval removal ", ed.GHUsername) } else if ed.GHUsername != "" { @@ -1077,14 +1097,14 @@ func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventDetailsString( return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ContributorNotifyCompanyAdminData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s notified Company Admin: %s by Email: %s for Company ID: %s, Name: %s.", args.UserName, ed.AdminName, ed.AdminEmail, args.CompanyName, args.CompanyID) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s notified CLA Designee: %s by Email: %s for Project Name : %s, ID: %s and Company Name: %s, ID: %s.", args.UserName, ed.DesigneeName, ed.DesigneeEmail, @@ -1093,7 +1113,7 @@ func (ed *ContributorNotifyCLADesignee) GetEventDetailsString(args *LogEventArgs return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ContributorAssignCLADesignee) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User Name: %s, Email: %s was assigned as CLA Manager Designee for project Name: %s, ID: %s and Company Name: %s, ID: %s", ed.DesigneeName, ed.DesigneeEmail, @@ -1106,14 +1126,14 @@ func (ed *ContributorAssignCLADesignee) GetEventDetailsString(args *LogEventArgs return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *UserConvertToContactData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s was converted to Contact state for Project: %s with ID: %s.", args.UserName, args.ProjectName, args.ProjectSFID) return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { @@ -1135,13 +1155,13 @@ func (ed *AssignRoleScopeData) GetEventDetailsString(args *LogEventArgs) (string return data, true } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ClaManagerRoleCreatedData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s, Email: %s was added to Role: %s with Scope: %s by: %s.", ed.UserName, ed.UserEmail, ed.Role, ed.Scope, args.UserName) return data, false } -// GetEventDetailsString . . . +// GetEventDetailsString returns the details string for this event func (ed *ClaManagerRoleDeletedData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s, Email: %s was removed from Role: %s with Scope: %s by: %s.", ed.UserName, ed.UserEmail, ed.Role, ed.Scope, args.UserName) return data, false @@ -1149,7 +1169,7 @@ func (ed *ClaManagerRoleDeletedData) GetEventDetailsString(args *LogEventArgs) ( // Event Summary started -// GetEventDetailsString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAGroupEnrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The project %s was enrolled into the CLA Group %s", args.ProjectName, args.CLAGroupName) if args.UserName != "" { @@ -1159,7 +1179,7 @@ func (ed *CLAGroupEnrolledProjectData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAGroupUnenrolledProjectData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The project %s was unenrolled from the CLA Group %s", args.ProjectName, args.CLAGroupName) if args.UserName != "" { @@ -1169,7 +1189,7 @@ func (ed *CLAGroupUnenrolledProjectData) GetEventSummaryString(args *LogEventArg return data, true } -// GetEventDetailsString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := "CLA Service was enabled" if args.CLAGroupName != "" { @@ -1188,7 +1208,7 @@ func (ed *ProjectServiceCLAEnabledData) GetEventSummaryString(args *LogEventArgs return data, true } -// GetEventSummaryString function for ProjectServiceCLADisabledData +// GetEventSummaryString returns the summary string for this event func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := "CLA Service was disabled" if args.CLAGroupName != "" { @@ -1207,7 +1227,7 @@ func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArg return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was added", ed.RepositoryName) if args.CLAGroupName != "" { @@ -1226,7 +1246,7 @@ func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (s return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was deleted", ed.RepositoryName) if args.CLAGroupName != "" { @@ -1245,7 +1265,7 @@ func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *RepositoryRenamedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository was renamed from %s to %s", ed.OldRepositoryName, ed.NewRepositoryName) if args.CLAGroupName != "" { @@ -1264,7 +1284,7 @@ func (ed *RepositoryRenamedEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *RepositoryTransferredEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository : %s was transferred from %s to %s Github Organization", ed.RepositoryName, ed.OldGithubOrgName, ed.NewGithubOrgName) if args.CLAGroupName != "" { @@ -1283,7 +1303,7 @@ func (ed *RepositoryTransferredEventData) GetEventSummaryString(args *LogEventAr return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *RepositoryUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository %s was updated", ed.RepositoryName) if args.CLAGroupName != "" { @@ -1302,7 +1322,7 @@ func (ed *RepositoryUpdatedEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventDetailsString . . . +// GetEventSummaryString returns the details string for this event func (ed *RepositoryBranchProtectionAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository branch protection %s was added", ed.RepositoryName) if args.CLAGroupName != "" { @@ -1321,7 +1341,7 @@ func (ed *RepositoryBranchProtectionAddedEventData) GetEventSummaryString(args * return data, true } -// GetEventDetailsString . . . +// GetEventSummaryString returns the details string for this event func (ed *RepositoryBranchProtectionDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository branch protection %s was disabled", ed.RepositoryName) if args.CLAGroupName != "" { @@ -1340,7 +1360,7 @@ func (ed *RepositoryBranchProtectionDisabledEventData) GetEventSummaryString(arg return data, true } -// GetEventDetailsString . . . +// GetEventSummaryString returns the details string for this event func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository branch protection %s was updated", ed.RepositoryName) if args.CLAGroupName != "" { @@ -1359,24 +1379,24 @@ func (ed *RepositoryBranchProtectionUpdatedEventData) GetEventSummaryString(args return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *UserCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s was added with the user details: %+v.", args.UserName, args.UserModel) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *UserUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { return fmt.Sprintf("The user %s was updated with the user details: %+v.", args.UserName, *args.UserModel), true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *UserDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user ID %s was deleted by the user %s.", ed.DeletedUserID, args.UserName) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CompanyACLRequestAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s with ID %s and with the email %s requested a company invitation", ed.UserName, ed.UserID, ed.UserEmail) @@ -1393,7 +1413,7 @@ func (ed *CompanyACLRequestAddedEventData) GetEventSummaryString(args *LogEventA return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CompanyACLRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("A company invite was approved for the user %s with the ID of %s with the email %s", ed.UserName, ed.UserID, ed.UserEmail) @@ -1413,7 +1433,7 @@ func (ed *CompanyACLRequestApprovedEventData) GetEventSummaryString(args *LogEve return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CompanyACLRequestDeniedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("A company invite was denied for the user %s with the ID of %s with the email %s", ed.UserName, ed.UserID, ed.UserEmail) @@ -1433,19 +1453,22 @@ func (ed *CompanyACLRequestDeniedEventData) GetEventSummaryString(args *LogEvent return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CompanyACLUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user with LF username %s was added to the access list for the company %s by the user %s.", args.LFUser.Name, args.CompanyName, args.UserName) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLATemplateCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := "The PDF templates were created" + data := "A CLA Group template was created or updated" // nolint if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } + if ed.TemplateName != "" { + data = data + fmt.Sprintf(" using template: %s", ed.TemplateName) + } if args.ProjectName != "" { data = data + fmt.Sprintf(" for the project %s", args.ProjectName) } @@ -1456,7 +1479,7 @@ func (ed *CLATemplateCreatedEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub organization %s was added with auto-enabled set to %t with branch protection enabled set to %t", ed.GitHubOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) @@ -1476,7 +1499,7 @@ func (ed *GitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEvent return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub organization %s was deleted", ed.GitHubOrganizationName) if args.CLAGroupName != "" { @@ -1492,7 +1515,7 @@ func (ed *GitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEve return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("GitHub Organization: %s was updated with auto-enabled: %t", ed.GitHubOrganizationName, ed.AutoEnabled) @@ -1512,7 +1535,7 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEve return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CCLAApprovalListRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s approved a CCLA approval request", args.UserName) if args.CLAGroupName != "" { @@ -1528,7 +1551,7 @@ func (ed *CCLAApprovalListRequestApprovedEventData) GetEventSummaryString(args * return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CCLAApprovalListRequestRejectedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s rejected a CCLA approval request", args.UserName) if args.CLAGroupName != "" { @@ -1544,7 +1567,7 @@ func (ed *CCLAApprovalListRequestRejectedEventData) GetEventSummaryString(args * return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAManagerRequestCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s added a CLA Manager request", args.UserName) if args.CLAGroupName != "" { @@ -1560,7 +1583,7 @@ func (ed *CLAManagerRequestCreatedEventData) GetEventSummaryString(args *LogEven return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAManagerCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s was added as CLA Manager", ed.UserName) if args.CLAGroupName != "" { @@ -1579,7 +1602,7 @@ func (ed *CLAManagerCreatedEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAManagerDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s was removed as CLA Manager", ed.UserName) if args.CLAGroupName != "" { @@ -1598,7 +1621,7 @@ func (ed *CLAManagerDeletedEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAManagerRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Manager request for the user %s was approved by the CLA Manager %s", ed.UserName, ed.ManagerName) @@ -1618,7 +1641,7 @@ func (ed *CLAManagerRequestApprovedEventData) GetEventSummaryString(args *LogEve return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAManagerRequestDeniedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Manager request for the user %s was denied by the CLA Manager %s", ed.UserName, ed.ManagerName) @@ -1635,7 +1658,7 @@ func (ed *CLAManagerRequestDeniedEventData) GetEventSummaryString(args *LogEvent return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAManagerRequestDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Manager request for the user %s was deleted by the CLA Manager %s", ed.UserName, ed.ManagerName) @@ -1652,7 +1675,7 @@ func (ed *CLAManagerRequestDeletedEventData) GetEventSummaryString(args *LogEven return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address %s was added to the approval list", ed.ApprovalListEmail) if args.CLAGroupName != "" { @@ -1671,7 +1694,7 @@ func (ed *CLAApprovalListAddEmailData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address %s was removed from the approval list", ed.ApprovalListEmail) if args.CLAGroupName != "" { @@ -1690,7 +1713,7 @@ func (ed *CLAApprovalListRemoveEmailData) GetEventSummaryString(args *LogEventAr return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address domain %s was added to the approval list", ed.ApprovalListDomain) if args.CLAGroupName != "" { @@ -1709,7 +1732,7 @@ func (ed *CLAApprovalListAddDomainData) GetEventSummaryString(args *LogEventArgs return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The email address domain %s was removed from the approval list", ed.ApprovalListDomain) if args.CLAGroupName != "" { @@ -1728,7 +1751,7 @@ func (ed *CLAApprovalListRemoveDomainData) GetEventSummaryString(args *LogEventA return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub username %s was added to the approval list", ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { @@ -1747,7 +1770,7 @@ func (ed *CLAApprovalListAddGitHubUsernameData) GetEventSummaryString(args *LogE return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub username %s was removed from the approval list", ed.ApprovalListGitHubUsername) if args.CLAGroupName != "" { @@ -1766,7 +1789,7 @@ func (ed *CLAApprovalListRemoveGitHubUsernameData) GetEventSummaryString(args *L return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub organization %s was added to the approval list", ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { @@ -1785,7 +1808,7 @@ func (ed *CLAApprovalListAddGitHubOrgData) GetEventSummaryString(args *LogEventA return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub organization %s was removed from the approval list", ed.ApprovalListGitHubOrg) if args.CLAGroupName != "" { @@ -1804,7 +1827,7 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEve return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CCLAApprovalListRequestCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s created a CCLA Approval Request", args.UserName) if args.CLAGroupName != "" { @@ -1820,7 +1843,7 @@ func (ed *CCLAApprovalListRequestCreatedEventData) GetEventSummaryString(args *L return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Manager %s added the GitHub organization %s to the approval list", args.UserName, ed.GitHubOrganizationName) if args.CLAGroupName != "" { @@ -1836,7 +1859,7 @@ func (ed *ApprovalListGitHubOrganizationAddedEventData) GetEventSummaryString(ar return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Manager %s removed the GitHub organization %s from the approval list", args.UserName, ed.GitHubOrganizationName) if args.CLAGroupName != "" { @@ -1852,7 +1875,7 @@ func (ed *ApprovalListGitHubOrganizationDeletedEventData) GetEventSummaryString( return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ClaManagerAccessRequestAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s has requested to be CLA Manager", args.UserName) if args.CLAGroupName != "" { @@ -1868,7 +1891,7 @@ func (ed *ClaManagerAccessRequestAddedEventData) GetEventSummaryString(args *Log return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ClaManagerAccessRequestDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s has deleted a request to be CLA Manager", args.UserName) if args.CLAGroupName != "" { @@ -1884,13 +1907,13 @@ func (ed *ClaManagerAccessRequestDeletedEventData) GetEventSummaryString(args *L return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Group %s was created by the user %s.", args.ProjectName, args.UserName) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { var nameUpdated, descriptionUpdated bool @@ -1918,7 +1941,7 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *CLAGroupDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CLA Group %s was deleted", args.ProjectName) if args.UserName != "" { @@ -1928,7 +1951,7 @@ func (ed *CLAGroupDeletedEventData) GetEventSummaryString(args *LogEventArgs) (s return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GerritProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d Gerrit repositories were deleted due to CLA Group/Project deletion", ed.DeletedCount) if args.CLAGroupName != "" { @@ -1944,7 +1967,7 @@ func (ed *GerritProjectDeletedEventData) GetEventSummaryString(args *LogEventArg return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GerritAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The Gerrit repository %s was added", ed.GerritRepositoryName) if args.CLAGroupName != "" { @@ -1960,7 +1983,7 @@ func (ed *GerritAddedEventData) GetEventSummaryString(args *LogEventArgs) (strin return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GerritDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The Gerrit repository %s was deleted", ed.GerritRepositoryName) if args.CLAGroupName != "" { @@ -1976,7 +1999,7 @@ func (ed *GerritDeletedEventData) GetEventSummaryString(args *LogEventArgs) (str return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GerritUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The username %s was add to the gerrit group %s", ed.Username, ed.GroupName) if args.CLAGroupName != "" { @@ -1992,7 +2015,7 @@ func (ed *GerritUserAddedEventData) GetEventSummaryString(args *LogEventArgs) (s return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GerritUserRemovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The username %s was removed from the gerrit group %s", ed.Username, ed.GroupName) if args.CLAGroupName != "" { @@ -2008,7 +2031,7 @@ func (ed *GerritUserRemovedEventData) GetEventSummaryString(args *LogEventArgs) return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *GitHubProjectDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d GitHub repositories were deleted due to CLA Group/project deletion", ed.DeletedCount) @@ -2025,7 +2048,7 @@ func (ed *GitHubProjectDeletedEventData) GetEventSummaryString(args *LogEventArg return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *SignatureProjectInvalidatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("%d signatures were invalidated (approved set to false) due to CLA Group/Project %s deletion", ed.InvalidatedCount, args.ProjectName) @@ -2045,9 +2068,9 @@ func (ed *SignatureProjectInvalidatedEventData) GetEventSummaryString(args *LogE return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - reason := "No reason" + reason := noReason if ed.Email != "" { reason = fmt.Sprintf("Email: %s approval removal ", ed.Email) } else if ed.GHUsername != "" { @@ -2070,7 +2093,7 @@ func (ed *SignatureInvalidatedApprovalRejectionEventData) GetEventSummaryString( return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ContributorNotifyCompanyAdminData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s notified the company admin %s by the email address %s", args.UserName, ed.AdminName, ed.AdminEmail) @@ -2087,7 +2110,7 @@ func (ed *ContributorNotifyCompanyAdminData) GetEventSummaryString(args *LogEven return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ContributorNotifyCLADesignee) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s notified the CLA Designee %s by email %s", args.UserName, ed.DesigneeName, ed.DesigneeEmail) if args.CLAGroupName != "" { @@ -2103,7 +2126,7 @@ func (ed *ContributorNotifyCLADesignee) GetEventSummaryString(args *LogEventArgs return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s was assigned as CLA Manager Designee", ed.DesigneeName) if args.CLAGroupName != "" { @@ -2122,7 +2145,7 @@ func (ed *ContributorAssignCLADesignee) GetEventSummaryString(args *LogEventArgs return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user '%s' with email '%s' was converted to a contact", ed.UserName, ed.UserEmail) if args.CLAGroupName != "" { @@ -2141,7 +2164,7 @@ func (ed *UserConvertToContactData) GetEventSummaryString(args *LogEventArgs) (s return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { @@ -2160,7 +2183,7 @@ func (ed *AssignRoleScopeData) GetEventSummaryString(args *LogEventArgs) (string return data, true } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ClaManagerRoleCreatedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { @@ -2179,7 +2202,7 @@ func (ed *ClaManagerRoleCreatedData) GetEventSummaryString(args *LogEventArgs) ( return data, false } -// GetEventSummaryString . . . +// GetEventSummaryString returns the summary string for this event func (ed *ClaManagerRoleDeletedData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user '%s' with email '%s' was added to the role %s", ed.UserName, ed.UserEmail, ed.Role) if args.CLAGroupName != "" { diff --git a/cla-backend-go/template/repository.go b/cla-backend-go/template/repository.go index 99267674f..76c20eaf0 100644 --- a/cla-backend-go/template/repository.go +++ b/cla-backend-go/template/repository.go @@ -38,6 +38,7 @@ var ( // RepositoryInterface interface functions type RepositoryInterface interface { GetTemplates(ctx context.Context) ([]models.Template, error) + GetTemplateName(ctx context.Context, templateID string) (string, error) GetTemplate(templateID string) (models.Template, error) CLAGroupTemplateExists(ctx context.Context, templateID string) bool GetCLAGroup(claGroupID string) (*models.ClaGroup, error) @@ -129,6 +130,26 @@ func (r Repository) GetTemplates(ctx context.Context) ([]models.Template, error) return templates, nil } +// GetTemplateName returns the template name when provided the template ID +func (r Repository) GetTemplateName(ctx context.Context, templateID string) (string, error) { + f := logrus.Fields{ + "functionName": "v1.template.repository.GetTemplateName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "templateID": templateID, + } + + // For each template... + for _, template := range templateMap { + // If we have a match + if template.ID == templateID { + return template.Name, nil + } + } + + log.WithFields(f).Warnf("unable to locate template with ID: %s", templateID) + return "", nil +} + // GetTemplate returns the template based on the template ID func (r Repository) GetTemplate(templateID string) (models.Template, error) { template, ok := templateMap[templateID] diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index 4782a5d4e..db79189ca 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -36,6 +36,7 @@ const ( // ServiceInterface interface type ServiceInterface interface { GetTemplates(ctx context.Context) ([]models.Template, error) + GetTemplateName(ctx context.Context, templateID string) (string, error) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, claGroupFields *models.CreateClaGroupTemplate) (models.TemplatePdfs, error) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) GetCLATemplatePreview(ctx context.Context, claGroupID, claType string, watermark bool) ([]byte, error) @@ -83,6 +84,22 @@ func (s Service) GetTemplates(ctx context.Context) ([]models.Template, error) { return templates, nil } +// GetTemplateName returns the template name when provided the template ID +func (s Service) GetTemplateName(ctx context.Context, templateID string) (string, error) { + f := logrus.Fields{ + "functionName": "v1.template.service.GetTemplateName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "templateID": templateID, + } + templateName, err := s.templateRepo.GetTemplateName(ctx, templateID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem loading template by ID: %s", templateID) + return "", err + } + + return templateName, nil +} + // CreateTemplatePreview returns a PDF using the specified CLA Group field values and template identifier func (s Service) CreateTemplatePreview(ctx context.Context, claGroupFields *models.CreateClaGroupTemplate, templateFor string) ([]byte, error) { f := logrus.Fields{ diff --git a/cla-backend-go/v2/template/handlers.go b/cla-backend-go/v2/template/handlers.go index d854234e0..db572dab0 100644 --- a/cla-backend-go/v2/template/handlers.go +++ b/cla-backend-go/v2/template/handlers.go @@ -92,11 +92,22 @@ func Configure(api *operations.EasyclaAPI, service v1Template.ServiceInterface, return template.NewGetTemplatesBadRequest().WithPayload(errorResponse(reqID, err)) } + // Need the template name for the event log + templateName, lookupErr := service.GetTemplateName(ctx, input.TemplateID) + if lookupErr != nil || templateName == "" { + log.WithFields(f).WithError(lookupErr).Warnf("Error looking up template name with ID: %s", input.TemplateID) + return template.NewGetTemplatesBadRequest().WithPayload(errorResponse(reqID, err)) + } + eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLATemplateCreated, - ProjectID: params.ClaGroupID, - LfUsername: authUser.UserName, - EventData: &events.CLATemplateCreatedEventData{}, + EventType: events.CLATemplateCreated, + ProjectID: params.ClaGroupID, + ProjectSFID: projectCLAGroups[0].ProjectSFID, + ParentProjectSFID: projectCLAGroups[0].FoundationSFID, + LfUsername: authUser.UserName, + EventData: &events.CLATemplateCreatedEventData{ + TemplateName: templateName, + }, }) response := &models.TemplatePdfs{} From 9c6e09b430302b9a1c94e25df7e014a9df4cf646 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 28 Jun 2021 11:36:39 -0700 Subject: [PATCH 0338/1276] Updated Add/Update Template Event Log Message Content (#3017) --- cla-backend-go/events/event_data.go | 36 ++++++++++------------ cla-backend-go/template/handlers.go | 41 ++++++++++++++++++++++++-- cla-backend-go/utils/auth_user.go | 3 +- cla-backend-go/v2/template/handlers.go | 10 +++++++ 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index e0fefa7c1..3cbd3f87f 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -166,6 +166,8 @@ type CompanyACLUserAddedEventData struct { // CLATemplateCreatedEventData data model type CLATemplateCreatedEventData struct { TemplateName string + OldPOC string + NewPOC string } // GitHubOrganizationAddedEventData data model @@ -585,18 +587,25 @@ func (ed *CompanyACLUserAddedEventData) GetEventDetailsString(args *LogEventArgs func (ed *CLATemplateCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := "A CLA Group template was created or updated" // nolint if args.CLAGroupName != "" { - data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + data = fmt.Sprintf("%s for the CLA Group %s", data, args.CLAGroupName) } if ed.TemplateName != "" { - data = data + fmt.Sprintf(" using template: %s", ed.TemplateName) + data = fmt.Sprintf("%s using template: %s", data, ed.TemplateName) } if args.ProjectName != "" { - data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + data = fmt.Sprintf("%s for the project %s", data, args.ProjectName) } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = fmt.Sprintf("%s by the user %s", data, args.UserName) } - data = data + "." + data = fmt.Sprintf("%s.", data) + + if ed.OldPOC != "" && ed.NewPOC != "" { + data = fmt.Sprintf("%s The point of contact email was changed from %s to %s.", data, ed.OldPOC, ed.NewPOC) + } else if ed.NewPOC != "" { + data = fmt.Sprintf("%s The point of contact email was set to %s.", data, ed.NewPOC) + } + return data, true } @@ -1462,21 +1471,8 @@ func (ed *CompanyACLUserAddedEventData) GetEventSummaryString(args *LogEventArgs // GetEventSummaryString returns the summary string for this event func (ed *CLATemplateCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := "A CLA Group template was created or updated" // nolint - if args.CLAGroupName != "" { - data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) - } - if ed.TemplateName != "" { - data = data + fmt.Sprintf(" using template: %s", ed.TemplateName) - } - if args.ProjectName != "" { - data = data + fmt.Sprintf(" for the project %s", args.ProjectName) - } - if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) - } - data = data + "." - return data, true + // Same output as the details + return ed.GetEventDetailsString(args) } // GetEventSummaryString returns the summary string for this event diff --git a/cla-backend-go/template/handlers.go b/cla-backend-go/template/handlers.go index 4bb758510..f3409386a 100644 --- a/cla-backend-go/template/handlers.go +++ b/cla-backend-go/template/handlers.go @@ -4,13 +4,18 @@ package template import ( + "context" + "fmt" + "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/models" "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/template" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime/middleware" + "github.com/sirupsen/logrus" ) // Configure API call @@ -26,17 +31,47 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventsService e }) api.TemplateCreateCLAGroupTemplateHandler = template.CreateCLAGroupTemplateHandlerFunc(func(params template.CreateCLAGroupTemplateParams, claUser *user.CLAUser) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "v2.signatures.handlers.SignaturesGetProjectSignaturesHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": params.ClaGroupID, + "templateID": params.Body.TemplateID, + } + pdfUrls, err := service.CreateCLAGroupTemplate(params.HTTPRequest.Context(), params.ClaGroupID, ¶ms.Body) if err != nil { - log.Warnf("Error generating PDFs from provided templates, error: %v", err) - return template.NewGetTemplatesBadRequest().WithPayload(errorResponse(err)) + msg := fmt.Sprintf("Error generating PDFs from provided templates, error: %v", err) + log.WithFields(f).WithError(err).Warn(msg) + return template.NewGetTemplatesBadRequest().WithPayload(utils.ToV1ErrorResponse(utils.ErrorResponseBadRequestWithError(reqID, msg, err))) + } + + // Need the template name for the event log + templateName, lookupErr := service.GetTemplateName(ctx, params.Body.TemplateID) + if lookupErr != nil || templateName == "" { + msg := fmt.Sprintf("Error looking up template name with ID: %s", params.Body.TemplateID) + log.WithFields(f).WithError(lookupErr).Warn(msg) + return template.NewGetTemplatesBadRequest().WithPayload(utils.ToV1ErrorResponse(utils.ErrorResponseBadRequestWithError(reqID, msg, lookupErr))) + } + + // Grab the new POC value from the request + newPOCValue := "" + for _, field := range params.Body.MetaFields { + if field.TemplateVariable == "CONTACT_EMAIL" { + newPOCValue = field.Value + break + } } eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLATemplateCreated, ProjectID: params.ClaGroupID, UserID: claUser.UserID, - EventData: &events.CLATemplateCreatedEventData{}, + EventData: &events.CLATemplateCreatedEventData{ + TemplateName: templateName, + NewPOC: newPOCValue, + }, }) return template.NewCreateCLAGroupTemplateOK().WithPayload(&pdfUrls) diff --git a/cla-backend-go/utils/auth_user.go b/cla-backend-go/utils/auth_user.go index 33af5fcaf..72bce1224 100644 --- a/cla-backend-go/utils/auth_user.go +++ b/cla-backend-go/utils/auth_user.go @@ -29,7 +29,6 @@ func SetAuthUserProperties(authUser *auth.User, xUserName *string, xEmail *strin tracingEnabled, conversionErr := strconv.ParseBool(os.Getenv("USER_AUTH_TRACING")) if conversionErr == nil && tracingEnabled { - log.WithFields(f).Debugf("Auth User: %+v", authUser) + log.WithFields(f).Debugf("set authuser x-username: %s and x-email: %s from Auth User model: %+v", authUser.UserName, authUser.Email, authUser) } - log.WithFields(f).Debugf("set authuser x-username:%s and x-email:%s", authUser.UserName, authUser.Email) } diff --git a/cla-backend-go/v2/template/handlers.go b/cla-backend-go/v2/template/handlers.go index db572dab0..8292e6cd7 100644 --- a/cla-backend-go/v2/template/handlers.go +++ b/cla-backend-go/v2/template/handlers.go @@ -99,6 +99,15 @@ func Configure(api *operations.EasyclaAPI, service v1Template.ServiceInterface, return template.NewGetTemplatesBadRequest().WithPayload(errorResponse(reqID, err)) } + // Grab the new POC value from the request + newPOCValue := "" + for _, field := range input.MetaFields { + if field.TemplateVariable == "CONTACT_EMAIL" { + newPOCValue = field.Value + break + } + } + eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLATemplateCreated, ProjectID: params.ClaGroupID, @@ -107,6 +116,7 @@ func Configure(api *operations.EasyclaAPI, service v1Template.ServiceInterface, LfUsername: authUser.UserName, EventData: &events.CLATemplateCreatedEventData{ TemplateName: templateName, + NewPOC: newPOCValue, }, }) From 446fe23744a20e116a907ea39c99aab3080446f5 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 28 Jun 2021 16:22:49 -0700 Subject: [PATCH 0339/1276] Cleaned Up/Optimized Signature Query (#3019) --- cla-backend-go/signatures/repository.go | 81 ++++++++++++------------- cla-backend-go/swagger/cla.v2.yaml | 9 ++- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 03af483fb..b0f9359aa 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -747,7 +747,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur // Always sort by date indexName := SignatureProjectDateIDIndex - realPageSize := int64(100) + realPageSize := int64(1000) if params.PageSize != nil && *params.PageSize > 0 { realPageSize = *params.PageSize } @@ -759,61 +759,38 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur var filter expression.ConditionBuilder var filterAdded = false - if params.ClaType != nil { - if strings.ToLower(*params.ClaType) == utils.ClaTypeICLA { + if params.ClaType != nil || params.SignatureType != nil { + switch getCLATypeFromParams(params) { + case utils.ClaTypeICLA: log.WithFields(f).Debugf("adding ICLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) - } else if strings.ToLower(*params.ClaType) == utils.ClaTypeECLA { + case utils.ClaTypeECLA: log.WithFields(f).Debugf("adding ECLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: exists", utils.SignatureTypeCLA, utils.SignatureReferenceTypeUser) filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCLA)), &filterAdded) filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeUser)), &filterAdded) filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeExists(), &filterAdded) - } else if strings.ToLower(*params.ClaType) == utils.ClaTypeCCLA { + case utils.ClaTypeCCLA: log.WithFields(f).Debugf("adding CCLA filters: signature_type: %s, signature_reference_type: %s, signature_user_ccla_company_id: not exists", utils.SignatureTypeCCLA, utils.SignatureReferenceTypeCompany) filter = addAndCondition(filter, expression.Name("signature_type").Equal(expression.Value(utils.SignatureTypeCCLA)), &filterAdded) filter = addAndCondition(filter, expression.Name("signature_reference_type").Equal(expression.Value(utils.SignatureReferenceTypeCompany)), &filterAdded) filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").AttributeNotExists(), &filterAdded) } - } else { - if params.SearchField != nil { - log.WithFields(f).Debugf("adding filters: signature_type: %s", expression.Value(params.SearchField)) - searchFieldExpression := expression.Name("signature_reference_type").Equal(expression.Value(params.SearchField)) - filter = addAndCondition(filter, searchFieldExpression, &filterAdded) - } - - if params.SignatureType != nil { - if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" && (params.FullMatch != nil && !*params.FullMatch) { - log.WithFields(f).Debugf("adding filters: signature_type: %s", strings.ToLower(utils.StringValue(params.SignatureType))) - indexName = SignatureProjectIDTypeIndex - condition = condition.And(expression.Key("signature_type").Equal(expression.Value(strings.ToLower(utils.StringValue(params.SignatureType))))) - } else { - log.WithFields(f).Debugf("adding filters: signature_type: %s", utils.StringValue(params.SignatureType)) - signatureTypeExpression := expression.Name("signature_type").Equal(expression.Value(utils.StringValue(params.SignatureType))) - filter = addAndCondition(filter, signatureTypeExpression, &filterAdded) - } - if *params.SignatureType == utils.ClaTypeCCLA { - log.WithFields(f).Debug("adding filters: signature_reference_id: exists, signature_user_ccla_company_id: not exists") - signatureReferenceIDExpression := expression.Name("signature_reference_id").AttributeExists() - signatureUserCclaCompanyIDExpression := expression.Name("signature_user_ccla_company_id").AttributeNotExists() - filter = addAndCondition(filter, signatureReferenceIDExpression, &filterAdded) - filter = addAndCondition(filter, signatureUserCclaCompanyIDExpression, &filterAdded) - } - } + } - if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { - if *params.FullMatch { - indexName = SignatureReferenceSearchIndex - log.WithFields(f).Debugf("adding filter signature_reference_name_lower: %s", strings.ToLower(utils.StringValue(params.SearchTerm))) - condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(utils.StringValue(params.SearchTerm))))) - } else { - log.WithFields(f).Debugf("adding filters signature_reference_name_lower: %s or user_email: %s", strings.ToLower(utils.StringValue(params.SearchTerm)), strings.ToLower(utils.StringValue(params.SearchTerm))) - searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(utils.StringValue(params.SearchTerm))). - Or(expression.Name("user_email").Contains(strings.ToLower(utils.StringValue(params.SearchTerm)))) - filter = addAndCondition(filter, searchTermExpression, &filterAdded) - } - } + if params.SearchTerm != nil && utils.StringValue(params.SearchTerm) != "" { + //if *params.FullMatch { + // indexName = SignatureReferenceSearchIndex + // log.WithFields(f).Debugf("adding filter signature_reference_name_lower: %s", strings.ToLower(utils.StringValue(params.SearchTerm))) + // condition = condition.And(expression.Key("signature_reference_name_lower").Equal(expression.Value(strings.ToLower(utils.StringValue(params.SearchTerm))))) + //} // else { + log.WithFields(f).Debugf("adding filters signature_reference_name_lower: %s or user_email: %s", strings.ToLower(utils.StringValue(params.SearchTerm)), strings.ToLower(utils.StringValue(params.SearchTerm))) + searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(utils.StringValue(params.SearchTerm))). + Or(expression.Name("user_email").Contains(strings.ToLower(utils.StringValue(params.SearchTerm)))) + filter = addAndCondition(filter, searchTermExpression, &filterAdded) + //} } if params.Approved != nil { @@ -821,6 +798,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur searchTermExpression := expression.Name("signature_approved").Equal(expression.Value(params.Approved)) filter = addAndCondition(filter, searchTermExpression, &filterAdded) } + if params.Signed != nil { log.WithFields(f).Debugf("adding signature_signed: %t filter", aws.BoolValue(params.Signed)) searchTermExpression := expression.Name("signature_signed").Equal(expression.Value(params.Signed)) @@ -857,6 +835,7 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur IndexName: aws.String(indexName), // Name of a secondary index to scan } f["indexName"] = indexName + log.WithFields(f).Debugf("queryInput: %+v", queryInput) if params.NextKey != nil { queryInput.ExclusiveStartKey, err = decodeNextKey(*params.NextKey) @@ -941,6 +920,24 @@ func (repo repository) GetProjectSignatures(ctx context.Context, params signatur }, nil } +// getCLATypeFromParams helper function to combine the new CLA Type parameter and the old legacy signature type parameter - returns one of the values from utils.ClaTypeICLA, utils.ClaTypeECLA, utils.ClaTypeCCLA or empty string if nothing matches +func getCLATypeFromParams(params signatures.GetProjectSignaturesParams) string { + if params.ClaType != nil { + return strings.ToLower(*params.ClaType) + } else if params.SignatureType != nil { + // ICLA -> CLAType == icla, SignatureType == cla + // ECLA -> CLAType == ecla, SignatureType == ecla + // CCLA -> CLAType == ccla, SignatureType == ccla + if strings.ToLower(*params.SignatureType) == "cla" { + return utils.ClaTypeICLA + } + + return strings.ToLower(*params.SignatureType) + } + + return "" +} + // CreateProjectSummaryReport generates a project summary report based on the specified input func (repo repository) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) { // nolint f := logrus.Fields{ diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index e370f0929..06a4ac8b5 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3817,10 +3817,15 @@ parameters: pattern: '^\w+$' signatureType: name: signatureType + description: > + CLA Type query parameter - allows the caller to specify either individual, employee or corporate signature, valid options: + * `icla` - for individual contributor signature records (individuals not associated with a corporation) + * `ecla` - for employee contributor signature records (acknowledgements from corporate contributors) + * `ccla` - for corporate contributor signature records (created by CLA Signatories and managed by CLA Managers) in: query type: string required: false - enum: [ ccla,cla ] + enum: [ccla,ecla,cla] claType: name: claType description: > @@ -3831,7 +3836,7 @@ parameters: in: query type: string required: false - enum: [ icla,ecla,ccla ] + enum: [ccla,ecla,icla] templateCLAType: name: claType in: query From ae5955d220f9672a2fcb67206a306602b0f059d2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 1 Jul 2021 12:43:55 -0700 Subject: [PATCH 0340/1276] Resolved Issues Raised by Project Service Swagger Model Updates (#3025) - project.Parent and project.EntityName was changed to optional, making the generated type to change from string to *string. Updated logic that reviews this field and dereferences the value as needed. Signed-off-by: David Deal --- cla-backend-go/events/service.go | 10 +++--- cla-backend-go/tests/project_helpers_test.go | 22 ++++++------- cla-backend-go/utils/conversion.go | 5 +++ cla-backend-go/utils/project_helpers.go | 4 +-- cla-backend-go/v2/cla_groups/handlers.go | 8 ++--- cla-backend-go/v2/cla_groups/helpers.go | 13 ++++---- cla-backend-go/v2/cla_groups/service.go | 2 +- .../v2/github_organizations/service.go | 6 ++-- cla-backend-go/v2/project-service/client.go | 32 +++++++++---------- cla-backend-go/v2/project/handlers.go | 12 +++---- cla-backend-go/v2/repositories/service.go | 6 ++-- cla-backend-go/v2/sign/service.go | 2 +- 12 files changed, 63 insertions(+), 59 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 2eb9fadf9..7a492bc98 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -255,11 +255,11 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { args.ProjectName = project.Name // Try to load and set the parent information - if project.Parent != "" { - log.WithFields(f).Debugf("loading salesforce project parent by ID: %s...", project.Parent) - parentProject, parentProjectErr := project_service.GetClient().GetProject(project.Parent) + if utils.StringValue(project.Parent) != "" { + log.WithFields(f).Debugf("loading salesforce project parent by ID: %s...", utils.StringValue(project.Parent)) + parentProject, parentProjectErr := project_service.GetClient().GetProject(utils.StringValue(project.Parent)) if parentProjectErr != nil || parentProject == nil { - log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", project.Parent) + log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", utils.StringValue(project.Parent)) return nil } var parentProjectName, parentProjectID string @@ -271,7 +271,7 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { parentProjectID = project.ID } log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s - resulting in ID: %s with name: %s", - project.Parent, parentProjectID, parentProjectName) + utils.StringValue(project.Parent), parentProjectID, parentProjectName) args.ParentProjectSFID = parentProjectID args.ParentProjectName = parentProjectName } else if project.Foundation != nil { diff --git a/cla-backend-go/tests/project_helpers_test.go b/cla-backend-go/tests/project_helpers_test.go index 4c7bfe472..eb45c05ad 100644 --- a/cla-backend-go/tests/project_helpers_test.go +++ b/cla-backend-go/tests/project_helpers_test.go @@ -20,14 +20,14 @@ const ( func TestIsProjectHasRootParentNoParent(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = "" + project.Parent = utils.StringRef("") project.Foundation = nil assert.True(t, utils.IsProjectHasRootParent(project), "Project Has Root Parent - Empty Parent") } func TestIsProjectHasRootParentLF(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -38,7 +38,7 @@ func TestIsProjectHasRootParentLF(t *testing.T) { func TestIsProjectHasRootParentLFProjectsLLC(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -49,7 +49,7 @@ func TestIsProjectHasRootParentLFProjectsLLC(t *testing.T) { func TestIsProjectHasRootParentNonLF(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -60,7 +60,7 @@ func TestIsProjectHasRootParentNonLF(t *testing.T) { func TestIsStandaloneProject(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = "" + project.Parent = utils.StringRef("") project.Foundation = nil project.Projects = []*models.ProjectOutput{} assert.True(t, utils.IsStandaloneProject(project), "Standalone Project with No Parent with No Children") @@ -68,7 +68,7 @@ func TestIsStandaloneProject(t *testing.T) { func TestLFParent(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -80,7 +80,7 @@ func TestLFParent(t *testing.T) { func TestLFProjectsLLCParent(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -92,7 +92,7 @@ func TestLFProjectsLLCParent(t *testing.T) { func TestLFParentWithChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -104,7 +104,7 @@ func TestLFParentWithChildren(t *testing.T) { func TestLFProjectsLLCParentWithChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -140,7 +140,7 @@ func TestLFProjectsLLCParentWithChildren(t *testing.T) { func TestIsProjectHaveChildrenNoChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = nil project.Projects = []*models.ProjectOutput{} assert.False(t, utils.IsProjectHaveChildren(project), "Project has no children") @@ -148,7 +148,7 @@ func TestIsProjectHaveChildrenNoChildren(t *testing.T) { func TestIsProjectHaveChildrenWithChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = testProjectParentID + project.Parent = utils.StringRef(testProjectParentID) project.Foundation = nil child := &models.ProjectOutput{ ProjectCommon: models.ProjectCommon{}, diff --git a/cla-backend-go/utils/conversion.go b/cla-backend-go/utils/conversion.go index e2a0f3d32..e51c740c1 100644 --- a/cla-backend-go/utils/conversion.go +++ b/cla-backend-go/utils/conversion.go @@ -11,6 +11,11 @@ func StringValue(input *string) string { return *input } +// StringRef function convert string to string reference +func StringRef(input string) *string { + return &input +} + // Int64Value function convert int64 pointer to string func Int64Value(input *int64) int64 { if input == nil { diff --git a/cla-backend-go/utils/project_helpers.go b/cla-backend-go/utils/project_helpers.go index 3457fc55d..6cffde169 100644 --- a/cla-backend-go/utils/project_helpers.go +++ b/cla-backend-go/utils/project_helpers.go @@ -7,13 +7,13 @@ import "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/mod // IsProjectHasRootParent determines if the a given project has a root parent. A root parent is a parent that is empty parent or the parent is TLF or LFProjects func IsProjectHasRootParent(project *models.ProjectOutputDetailed) bool { - return project.Parent == "" || (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC)) + return StringValue(project.Parent) == "" || (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC)) } // IsStandaloneProject determines if a given project is a standalone project. A standalone project is a project with no parent or the parent is TLF/LFProjects and does not have any children func IsStandaloneProject(project *models.ProjectOutputDetailed) bool { // standalone: No parent or parent is TLF/LFProjects....and no children - return (project.Parent == "" || + return (StringValue(project.Parent) == "" || (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC))) && len(project.Projects) == 0 } diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 288044ae9..a1b0110b4 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -306,15 +306,15 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } var parentProject *v2ProjectServiceModels.ProjectOutputDetailed // Handle the ONAP edge case - if project.Parent != "" { - parentProject, projectErr = psc.GetProject(project.Parent) + if utils.StringValue(project.Parent) != "" { + parentProject, projectErr = psc.GetProject(utils.StringValue(project.Parent)) if parentProject == nil || projectErr != nil { - msg := fmt.Sprintf("Failed to get parent: %s", project.Parent) + msg := fmt.Sprintf("Failed to get parent: %s", utils.StringValue(project.Parent)) log.WithFields(f).Warnf(msg) return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } } - if (project.Parent != "" && !utils.IsProjectCategory(project, parentProject)) || (utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { + if (utils.StringValue(project.Parent) != "" && !utils.IsProjectCategory(project, parentProject)) || (utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { msg := fmt.Sprintf("Unable to enroll salesforce foundation project: %s in project level cla-group.", projectSFID) return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index f426104a1..cf27d3867 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -159,7 +159,7 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) + isLFParent, err := psc.IsTheLinuxFoundation(utils.StringValue(foundationProjectDetails.Parent)) if err != nil { log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) return false, err @@ -168,7 +168,7 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat // If the foundation details in the platform project service indicates that this foundation has no parent or no // children/sub-project... (stand alone project situation) log.WithFields(f).Debug("checking to see if we have a standalone project...") - if (foundationProjectDetails.Parent == "" || isLFParent) && len(foundationProjectDetails.Projects) == 0 { + if (utils.StringValue(foundationProjectDetails.Parent) == "" || isLFParent) && len(foundationProjectDetails.Projects) == 0 { log.WithFields(f).Debug("we have a standalone project...") // Did the user actually pass in any projects? If none - add the foundation ID to the list and return to // indicate it is a "standalone project" @@ -251,7 +251,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI if projectTree != nil && projectTree.Parent != nil && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", - foundationProjectDetails.Parent, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + utils.StringValue(foundationProjectDetails.Parent), foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) } @@ -331,7 +331,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent, err := psc.IsTheLinuxFoundation(foundationProjectDetails.Parent) + isLFParent, err := psc.IsTheLinuxFoundation(utils.StringValue(foundationProjectDetails.Parent)) if err != nil { log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) return err @@ -343,13 +343,12 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } - if foundationProjectDetails.Parent != "" && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { + if utils.StringValue(foundationProjectDetails.Parent) != "" && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", - foundationProjectDetails.Parent, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + utils.StringValue(foundationProjectDetails.Parent), foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) } - } // Check to see if all the provided enrolled projects are part of this foundation diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 87d436c4f..701fe6637 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -386,7 +386,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje var parentDetails *v2ProjectServiceModels.ProjectOutputDetailed var parentDetailErr error - if sfProjectModelDetails.Parent != "" { + if utils.StringValue(sfProjectModelDetails.Parent) != "" { var parentSFID string // Use utility function that considers TLF and LF Projects, LLC parentSFID, parentDetailErr = v2ProjectService.GetClient().GetParentProject(projectOrFoundationSFID) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 4a7d9e553..28981b4af 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -102,7 +102,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) if utils.IsProjectHasRootParent(projectServiceRecord) { parentProjectSFID = projectSFID } else { - parentProjectSFID = projectServiceRecord.Parent + parentProjectSFID = utils.StringValue(projectServiceRecord.Parent) } f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") @@ -290,11 +290,11 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || (project.Foundation != nil && + if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { - parentProjectSFID = project.Parent + parentProjectSFID = utils.StringValue(project.Parent) } f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 72a6f1c12..9d25f680a 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -145,7 +145,7 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { existingModel, exists := projectServiceModels[projectSFID] if exists { log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) - return existingModel.Parent, nil + return utils.StringValue(existingModel.Parent), nil } log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModels)) @@ -161,14 +161,14 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) // Do they have a parent? - if projectModel.Parent == "" || (projectModel.Foundation != nil && + if utils.StringValue(projectModel.Parent) == "" || (projectModel.Foundation != nil && (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return projectSFID, nil } - log.WithFields(f).Debugf("returning parent projectSFID: %s", projectModel.Parent) - return projectModel.Parent, nil + log.WithFields(f).Debugf("returning parent projectSFID: %s", utils.StringValue(projectModel.Parent)) + return utils.StringValue(projectModel.Parent), nil } // GetParentProjectModel returns the parent project model if there is a parent, otherwise returns nil @@ -190,20 +190,20 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) // Parent in the cache? - existingParentModel, exists = projectServiceModels[existingModel.Parent] + existingParentModel, exists = projectServiceModels[utils.StringValue(existingModel.Parent)] if exists { return existingParentModel, nil } // Parent project not in the cache - lookup - parentProjectModel, err := pmm.GetProject(existingModel.Parent) + parentProjectModel, err := pmm.GetProject(utils.StringValue(existingModel.Parent)) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", existingModel.Parent) + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", utils.StringValue(existingModel.Parent)) return nil, err } // Update our cache for next time - projectServiceModels[existingModel.Parent] = parentProjectModel + projectServiceModels[utils.StringValue(existingModel.Parent)] = parentProjectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) return parentProjectModel, nil @@ -221,27 +221,27 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) // Do they have a parent? - if projectModel.Parent == "" || (projectModel.Foundation != nil && + if utils.StringValue(projectModel.Parent) == "" || (projectModel.Foundation != nil && (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return nil, nil } // Parent in the cache? - existingParentModel, exists = projectServiceModels[projectModel.Parent] + existingParentModel, exists = projectServiceModels[utils.StringValue(projectModel.Parent)] if exists { return existingParentModel, nil } // Parent project not in the cache - lookup - parentProjectModel, err := pmm.GetProject(projectModel.Parent) + parentProjectModel, err := pmm.GetProject(utils.StringValue(projectModel.Parent)) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", projectModel.Parent) + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", utils.StringValue(projectModel.Parent)) return nil, err } // Update our cache for next time - projectServiceModels[existingModel.Parent] = parentProjectModel + projectServiceModels[utils.StringValue(existingModel.Parent)] = parentProjectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) return parentProjectModel, nil @@ -286,13 +286,13 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) return false, err } - if projectModel.Parent == "" { + if utils.StringValue(projectModel.Parent) == "" { return false, nil } - parentProjectModel, err := pmm.GetProject(projectModel.Parent) + parentProjectModel, err := pmm.GetProject(utils.StringValue(projectModel.Parent)) if err != nil { - log.WithFields(f).Warnf("unable to lookup parent project by ID: %s error: %+v", projectModel.Parent, err) + log.WithFields(f).Warnf("unable to lookup parent project by ID: %s error: %+v", utils.StringValue(projectModel.Parent), err) return false, err } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index a02d48683..78c0455bf 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -316,10 +316,10 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service // Lookup the parent info, if it's available var parentName string - if sfProject.Parent != "" { - sfParentProject, err := psc.GetProject(sfProject.Parent) + if utils.StringValue(sfProject.Parent) != "" { + sfParentProject, err := psc.GetProject(utils.StringValue(sfProject.Parent)) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to load parant project by ID: %s", sfProject.Parent) + log.WithFields(f).WithError(err).Warnf("unable to load parant project by ID: %s", utils.StringValue(sfProject.Parent)) } if sfParentProject != nil { @@ -334,18 +334,18 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetailed, parentName string) *models.SfProjectSummary { return &models.SfProjectSummary{ - EntityName: sfProject.EntityName, + EntityName: utils.StringValue(sfProject.EntityName), EntityType: sfProject.EntityType, Funding: sfProject.Funding, ID: sfProject.ID, LfSupported: sfProject.LFSponsored, Name: sfProject.Name, - ParentID: sfProject.Parent, + ParentID: utils.StringValue(sfProject.Parent), ParentName: parentName, Slug: sfProject.Slug, Status: sfProject.Status, Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (sfProject.Parent == "" || (sfProject.Foundation != nil && + IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (utils.StringValue(sfProject.Parent) == "" || (sfProject.Foundation != nil && (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index a47796026..837cce2ca 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -90,11 +90,11 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, } var parentProjectSFID string - if project.Parent == "" || (project.Foundation != nil && + if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { parentProjectSFID = projectSFID } else { - parentProjectSFID = project.Parent + parentProjectSFID = utils.StringValue(project.Parent) } allMappings, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, aws.StringValue(input.ClaGroupID)) @@ -252,7 +252,7 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin return nil, err } f["projectName"] = projectModel.Name - if projectModel.Parent != "" { + if utils.StringValue(projectModel.Parent) != "" { f["projectParentSFID"] = projectModel.Parent } log.WithFields(f).Debug("loaded project from the project service") diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 683299365..6019dc427 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,7 +173,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if project.Parent == "" || (project.Foundation != nil && + if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(ctx, utils.StringValue(input.ProjectSfid)) From 66a0be847e26b9b430e299a1069513fbe73bf72b Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 1 Jul 2021 14:49:52 -0700 Subject: [PATCH 0341/1276] Resolved/Updated Error Handling for Missing Project to CLA Group Mapping (#3026) - Updated error handling for the project to CLA group mapping query Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/service.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 701fe6637..91520848e 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -646,7 +646,6 @@ func (s *service) appendCLAGroupsForProject(ctx context.Context, f logrus.Fields // Since this is a project and not a foundation, we'll want to set he parent foundation ID and name (which is // our parent in this case) var foundationID, foundationName string - log.WithFields(f).Debug("found 'project' in platform project service.") if sfProjectModelDetails.ProjectOutput.Foundation != nil { foundationID = sfProjectModelDetails.ProjectOutput.Foundation.ID foundationName = sfProjectModelDetails.ProjectOutput.Foundation.Name @@ -658,18 +657,17 @@ func (s *service) appendCLAGroupsForProject(ctx context.Context, f logrus.Fields log.WithFields(f).Debugf("no parent - using project as foundation ID: %s and name: %s", foundationID, foundationName) } - log.WithFields(f).Debug("locating CLA Group mapping...") + log.WithFields(f).Debugf("locating CLA Group mapping using projectOrFoundationSFID: '%s'...", projectOrFoundationSFID) projectCLAGroup, lookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, projectOrFoundationSFID) - if lookupErr != nil { - log.WithFields(f).Warnf("problem locating CLA group by project id, error: %+v", lookupErr) + if lookupErr != nil || projectCLAGroup == nil || projectCLAGroup.ClaGroupID == "" { + log.WithFields(f).WithError(lookupErr).Warnf("problem locating CLA group by project id: '%s'", projectOrFoundationSFID) return "", "", &utils.ProjectCLAGroupMappingNotFound{ProjectSFID: projectOrFoundationSFID, Err: lookupErr} } - log.WithFields(f).Debugf("loading CLA Group by ID: %s", projectCLAGroup.ClaGroupID) + log.WithFields(f).Debugf("loading CLA Group by ID: '%s' - %+v", projectCLAGroup.ClaGroupID, projectCLAGroup) v1ClaGroupsByProject, claGroupLoadErr := s.v1ProjectService.GetCLAGroupByID(ctx, projectCLAGroup.ClaGroupID) - //v1ClaGroupsByProject, prjerr := s.v1ProjectService.GetClaGroupByProjectSFID(projectOrFoundationSFID, DontLoadDetails) if claGroupLoadErr != nil { - log.WithFields(f).Warnf("problem loading CLA group by id, error: %+v", claGroupLoadErr) + log.WithFields(f).Warnf("problem loading CLA group by id: '%s', error: %+v", projectCLAGroup.ClaGroupID, claGroupLoadErr) return "", "", &utils.CLAGroupNotFound{CLAGroupID: projectCLAGroup.ClaGroupID, Err: claGroupLoadErr} } From 36b9c616a4b1be6b2a4238f260e1378bc7fafdbe Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 1 Jul 2021 16:02:56 -0700 Subject: [PATCH 0342/1276] Updated Build to Generate v1 APIs/Models to the v1 folder (#3027) --- cla-backend-go/Makefile | 15 +++++++++------ cla-backend-go/approval_list/handlers.go | 6 +++--- cla-backend-go/approval_list/helpers.go | 2 +- cla-backend-go/approval_list/repository.go | 2 +- cla-backend-go/approval_list/service.go | 2 +- cla-backend-go/cla_manager/handlers.go | 8 ++++---- cla-backend-go/cla_manager/models.go | 2 +- cla-backend-go/cla_manager/service.go | 4 ++-- .../functional_tests/cla_manager/cla_manager.go | 2 +- .../cmd/functional_tests/health/health.go | 2 +- .../cmd/functional_tests/signatures/signatures.go | 2 +- cla-backend-go/cmd/server.go | 6 +++--- cla-backend-go/company/handlers.go | 8 ++++---- cla-backend-go/company/models.go | 2 +- cla-backend-go/company/repository.go | 2 +- cla-backend-go/company/service.go | 2 +- cla-backend-go/docs/handlers.go | 4 ++-- cla-backend-go/docs/swagger.go | 2 +- cla-backend-go/events/event_data.go | 2 +- cla-backend-go/events/handlers.go | 6 +++--- cla-backend-go/events/mock.go | 4 ++-- cla-backend-go/events/mockrepo.go | 4 ++-- cla-backend-go/events/models.go | 2 +- cla-backend-go/events/repository.go | 4 ++-- cla-backend-go/events/service.go | 4 ++-- cla-backend-go/gerrits/handlers.go | 6 +++--- cla-backend-go/gerrits/models.go | 2 +- cla-backend-go/gerrits/repository.go | 2 +- cla-backend-go/gerrits/service.go | 2 +- cla-backend-go/github/handlers.go | 4 ++-- cla-backend-go/github_organizations/handlers.go | 6 +++--- cla-backend-go/github_organizations/helpers.go | 2 +- cla-backend-go/github_organizations/mock.go | 2 +- cla-backend-go/github_organizations/models.go | 2 +- cla-backend-go/github_organizations/repository.go | 2 +- cla-backend-go/github_organizations/service.go | 2 +- cla-backend-go/health/handlers.go | 6 +++--- cla-backend-go/health/service.go | 2 +- cla-backend-go/project/errors.go | 2 +- cla-backend-go/project/handlers.go | 6 +++--- cla-backend-go/project/helpers.go | 2 +- cla-backend-go/project/repository.go | 4 ++-- cla-backend-go/project/service.go | 4 ++-- cla-backend-go/repositories/handlers.go | 6 +++--- .../repositories/mock/mock_repository.go | 2 +- cla-backend-go/repositories/mock/mock_service.go | 2 +- cla-backend-go/repositories/models.go | 2 +- cla-backend-go/repositories/repository.go | 2 +- cla-backend-go/repositories/service.go | 2 +- cla-backend-go/signatures/converters.go | 2 +- cla-backend-go/signatures/handlers.go | 6 +++--- cla-backend-go/signatures/models.go | 2 +- cla-backend-go/signatures/repository.go | 4 ++-- cla-backend-go/signatures/service.go | 4 ++-- cla-backend-go/template/handlers.go | 6 +++--- cla-backend-go/template/repository.go | 2 +- cla-backend-go/template/service.go | 2 +- cla-backend-go/tests/events_test.go | 2 +- cla-backend-go/tests/project_test.go | 2 +- cla-backend-go/tests/repository_test.go | 2 +- cla-backend-go/userSubscribeLambda/main.go | 2 +- cla-backend-go/users/handlers.go | 6 +++--- cla-backend-go/users/repository.go | 2 +- cla-backend-go/users/service.go | 2 +- cla-backend-go/utils/cla_user.go | 2 +- cla-backend-go/utils/responses.go | 2 +- cla-backend-go/utils/signature_utils.go | 2 +- cla-backend-go/v2/cla_groups/service.go | 4 ++-- cla-backend-go/v2/cla_manager/emails.go | 2 +- cla-backend-go/v2/cla_manager/service.go | 2 +- cla-backend-go/v2/company/service.go | 6 +++--- cla-backend-go/v2/dynamo_events/autoenable.go | 2 +- .../v2/dynamo_events/autoenable_test.go | 2 +- .../v2/dynamo_events/projects_cla_groups.go | 4 ++-- cla-backend-go/v2/events/converters.go | 2 +- cla-backend-go/v2/events/csvResponse.go | 2 +- cla-backend-go/v2/events/handlers.go | 2 +- cla-backend-go/v2/gerrits/handlers.go | 2 +- cla-backend-go/v2/github_activity/service.go | 2 +- cla-backend-go/v2/github_activity/service_test.go | 2 +- cla-backend-go/v2/github_organizations/service.go | 2 +- cla-backend-go/v2/project/converters.go | 2 +- cla-backend-go/v2/project/handlers.go | 2 +- cla-backend-go/v2/repositories/handlers.go | 2 +- cla-backend-go/v2/repositories/service.go | 2 +- cla-backend-go/v2/sign/service.go | 2 +- cla-backend-go/v2/signatures/converters.go | 2 +- cla-backend-go/v2/signatures/handlers.go | 4 ++-- cla-backend-go/v2/signatures/service.go | 2 +- cla-backend-go/v2/template/handlers.go | 2 +- cla-backend-go/version/handlers.go | 6 +++--- 91 files changed, 145 insertions(+), 142 deletions(-) diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index a9e8110d6..406f757a2 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -29,7 +29,9 @@ GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') all: all-mac all-mac: clean swagger deps fmt build-mac build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac test lint all-linux: clean swagger deps fmt build-linux build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux test lint +lambdas-mac: build-aws-lambda-mac build-lambdas-mac: build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-metrics-report-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac +lambdas: build-lambdas-linux build-lambdas-linux: build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-metrics-report-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux generate: swagger @@ -70,13 +72,17 @@ clean: functional-tests* metrics-aws-lambda* metrics-report-lambda* \ user-subscribe-lambda* zipbuild-lambda* zipbuilder-scheduler-lambda* +swagger-clean: clean-swagger clean-swagger: @rm -rf gen/ clean-all: clean clean-swagger @rm -rf vendor/ -swagger: clean-swagger swagger-build swagger-validate +swagger: clean-swagger swagger-prep swagger-build swagger-validate +build-swagger: swagger-build +swagger-build: swagger-build-v1-services swagger-build-v2-services swagger-build-project-service swagger-build-organization-service swagger-build-user-service swagger-build-acs-service +swagger-validate: swagger-v1-validate swagger-v2-validate swagger-prep: @mkdir gen @@ -89,6 +95,8 @@ swagger-build-v1-services: -t gen \ -f swagger/cla.v1.compiled.yaml \ --copyright-file=copyright-header.txt \ + --server-package=v1/restapi \ + --model-package=v1/models \ --exclude-main \ -A cla \ -P user.CLAUser @@ -154,11 +162,6 @@ swagger-build-acs-service: -t v2/acs-service \ -f swagger/acs-service.yaml -build-swagger: swagger-build -swagger-build: clean-swagger swagger-prep swagger-build-v1-services swagger-build-v2-services swagger-build-project-service swagger-build-organization-service swagger-build-user-service swagger-build-acs-service - -swagger-validate: swagger-v1-validate swagger-v2-validate - swagger-v1-validate: @echo "" @echo "Validating EasyCLA v1 legacy API specification..." diff --git a/cla-backend-go/approval_list/handlers.go b/cla-backend-go/approval_list/handlers.go index c8df6a62d..8dfafddbc 100644 --- a/cla-backend-go/approval_list/handlers.go +++ b/cla-backend-go/approval_list/handlers.go @@ -9,9 +9,9 @@ import ( "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/company" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/company" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/signatures" "github.com/communitybridge/easycla/cla-backend-go/user" diff --git a/cla-backend-go/approval_list/helpers.go b/cla-backend-go/approval_list/helpers.go index fb53ce6d3..187c39bf7 100644 --- a/cla-backend-go/approval_list/helpers.go +++ b/cla-backend-go/approval_list/helpers.go @@ -8,7 +8,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/aws/aws-sdk-go/service/dynamodb/expression" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/approval_list/repository.go b/cla-backend-go/approval_list/repository.go index 6a025bdc7..26218948b 100644 --- a/cla-backend-go/approval_list/repository.go +++ b/cla-backend-go/approval_list/repository.go @@ -15,7 +15,7 @@ import ( "github.com/gofrs/uuid" "github.com/sirupsen/logrus" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/aws/aws-sdk-go/aws" diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index ae9ce32df..21ee64305 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -25,7 +25,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/communitybridge/easycla/cla-backend-go/users" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" ) // errors diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 4d16cd222..8bea2b3ce 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -14,18 +14,18 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/emails" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/cla_manager" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/cla_manager" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/aws/aws-sdk-go/aws" - sigAPI "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + sigAPI "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/signatures" "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/project" "github.com/communitybridge/easycla/cla-backend-go/user" diff --git a/cla-backend-go/cla_manager/models.go b/cla-backend-go/cla_manager/models.go index ff5e3a756..7d9fad8e7 100644 --- a/cla-backend-go/cla_manager/models.go +++ b/cla-backend-go/cla_manager/models.go @@ -3,7 +3,7 @@ package cla_manager -import "github.com/communitybridge/easycla/cla-backend-go/gen/models" +import "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" // CLAManagerRequests data model type CLAManagerRequests struct { diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 58ddbb212..07b18574e 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -16,8 +16,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - sigAPI "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + sigAPI "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/project" "github.com/communitybridge/easycla/cla-backend-go/signatures" diff --git a/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go b/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go index fa015d3ab..ec71d9dac 100644 --- a/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go +++ b/cla-backend-go/cmd/functional_tests/cla_manager/cla_manager.go @@ -9,7 +9,7 @@ import ( "reflect" "github.com/communitybridge/easycla/cla-backend-go/cmd/functional_tests/test_models" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/verdverm/frisby" ) diff --git a/cla-backend-go/cmd/functional_tests/health/health.go b/cla-backend-go/cmd/functional_tests/health/health.go index 8fe3d7ec7..e2937a344 100644 --- a/cla-backend-go/cmd/functional_tests/health/health.go +++ b/cla-backend-go/cmd/functional_tests/health/health.go @@ -9,7 +9,7 @@ import ( "reflect" "github.com/communitybridge/easycla/cla-backend-go/cmd/functional_tests/test_models" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/verdverm/frisby" ) diff --git a/cla-backend-go/cmd/functional_tests/signatures/signatures.go b/cla-backend-go/cmd/functional_tests/signatures/signatures.go index 340324efd..f0e423412 100644 --- a/cla-backend-go/cmd/functional_tests/signatures/signatures.go +++ b/cla-backend-go/cmd/functional_tests/signatures/signatures.go @@ -9,7 +9,7 @@ import ( "reflect" "github.com/communitybridge/easycla/cla-backend-go/cmd/functional_tests/test_models" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/verdverm/frisby" ) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 5718fe743..4cf1af8be 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -72,9 +72,9 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/auth" v1Company "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/docraptor" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" v2RestAPI "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi" v2Ops "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/github" diff --git a/cla-backend-go/company/handlers.go b/cla-backend-go/company/handlers.go index f4fc7243a..f7216dd86 100644 --- a/cla-backend-go/company/handlers.go +++ b/cla-backend-go/company/handlers.go @@ -12,14 +12,14 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/organization" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/organization" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/users" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/company" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/company" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" orgService "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" diff --git a/cla-backend-go/company/models.go b/cla-backend-go/company/models.go index 30e9a3e2a..9b4fea85a 100644 --- a/cla-backend-go/company/models.go +++ b/cla-backend-go/company/models.go @@ -6,7 +6,7 @@ package company import ( "context" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/strfmt" diff --git a/cla-backend-go/company/repository.go b/cla-backend-go/company/repository.go index d5d8ca32c..ea57f7e6c 100644 --- a/cla-backend-go/company/repository.go +++ b/cla-backend-go/company/repository.go @@ -16,7 +16,7 @@ import ( "github.com/go-openapi/strfmt" "github.com/aws/aws-sdk-go/service/dynamodb/expression" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index 2d3c69072..ab513e0d4 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -15,7 +15,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" organization_service "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" diff --git a/cla-backend-go/docs/handlers.go b/cla-backend-go/docs/handlers.go index 4bcdf5cd4..1ccae22d1 100644 --- a/cla-backend-go/docs/handlers.go +++ b/cla-backend-go/docs/handlers.go @@ -4,8 +4,8 @@ package docs import ( - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/docs" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/docs" "github.com/go-openapi/runtime/middleware" ) diff --git a/cla-backend-go/docs/swagger.go b/cla-backend-go/docs/swagger.go index d486fa1e9..999b937b8 100644 --- a/cla-backend-go/docs/swagger.go +++ b/cla-backend-go/docs/swagger.go @@ -6,7 +6,7 @@ package docs import ( "net/http" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi" "github.com/go-openapi/runtime" ) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 3cbd3f87f..70f17f5ce 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -6,7 +6,7 @@ package events import ( "fmt" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/utils" ) diff --git a/cla-backend-go/events/handlers.go b/cla-backend-go/events/handlers.go index 919f0d3a7..5703673e5 100644 --- a/cla-backend-go/events/handlers.go +++ b/cla-backend-go/events/handlers.go @@ -4,9 +4,9 @@ package events import ( - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/go-openapi/runtime/middleware" diff --git a/cla-backend-go/events/mock.go b/cla-backend-go/events/mock.go index 91494c65c..882c8358e 100644 --- a/cla-backend-go/events/mock.go +++ b/cla-backend-go/events/mock.go @@ -12,8 +12,8 @@ import ( context "context" reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" - events0 "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + events0 "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" gomock "github.com/golang/mock/gomock" ) diff --git a/cla-backend-go/events/mockrepo.go b/cla-backend-go/events/mockrepo.go index 82f9025a8..0ba7ceee1 100644 --- a/cla-backend-go/events/mockrepo.go +++ b/cla-backend-go/events/mockrepo.go @@ -12,8 +12,8 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" "github.com/go-openapi/strfmt" ) diff --git a/cla-backend-go/events/models.go b/cla-backend-go/events/models.go index de832080e..993f21e52 100644 --- a/cla-backend-go/events/models.go +++ b/cla-backend-go/events/models.go @@ -3,7 +3,7 @@ package events -import "github.com/communitybridge/easycla/cla-backend-go/gen/models" +import "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" //IndividualSignedEvent represntative of ICLA signatures const IndividualSignedEvent = "IndividualSignatureSigned" diff --git a/cla-backend-go/events/repository.go b/cla-backend-go/events/repository.go index bfce950a4..bac4ec089 100644 --- a/cla-backend-go/events/repository.go +++ b/cla-backend-go/events/repository.go @@ -27,8 +27,8 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" ) // errors diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 7a492bc98..0ad22ba99 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -18,8 +18,8 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/gerrits/handlers.go b/cla-backend-go/gerrits/handlers.go index f34cfa2a4..015c0fd8f 100644 --- a/cla-backend-go/gerrits/handlers.go +++ b/cla-backend-go/gerrits/handlers.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/gerrits" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/gerrits" "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime/middleware" diff --git a/cla-backend-go/gerrits/models.go b/cla-backend-go/gerrits/models.go index 82b309aa1..b31689693 100644 --- a/cla-backend-go/gerrits/models.go +++ b/cla-backend-go/gerrits/models.go @@ -4,7 +4,7 @@ package gerrits import ( - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/go-openapi/strfmt" ) diff --git a/cla-backend-go/gerrits/repository.go b/cla-backend-go/gerrits/repository.go index e053af72e..d9f4ae410 100644 --- a/cla-backend-go/gerrits/repository.go +++ b/cla-backend-go/gerrits/repository.go @@ -23,7 +23,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/gerrits/service.go b/cla-backend-go/gerrits/service.go index 41606da14..3ca90164a 100644 --- a/cla-backend-go/gerrits/service.go +++ b/cla-backend-go/gerrits/service.go @@ -20,7 +20,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" diff --git a/cla-backend-go/github/handlers.go b/cla-backend-go/github/handlers.go index 6c8217829..24784a1a8 100644 --- a/cla-backend-go/github/handlers.go +++ b/cla-backend-go/github/handlers.go @@ -14,8 +14,8 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/user" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - gh "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/github" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + gh "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/github" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index 85495681c..4f23a1fb6 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -11,9 +11,9 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/github_organizations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/github_organizations" "github.com/communitybridge/easycla/cla-backend-go/github" "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/communitybridge/easycla/cla-backend-go/utils" diff --git a/cla-backend-go/github_organizations/helpers.go b/cla-backend-go/github_organizations/helpers.go index 29906cce9..0157ac0bf 100644 --- a/cla-backend-go/github_organizations/helpers.go +++ b/cla-backend-go/github_organizations/helpers.go @@ -9,7 +9,7 @@ import ( netURL "net/url" "sync" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/github" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" diff --git a/cla-backend-go/github_organizations/mock.go b/cla-backend-go/github_organizations/mock.go index 29c6e5215..01c35588c 100644 --- a/cla-backend-go/github_organizations/mock.go +++ b/cla-backend-go/github_organizations/mock.go @@ -12,7 +12,7 @@ import ( context "context" reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" gomock "github.com/golang/mock/gomock" ) diff --git a/cla-backend-go/github_organizations/models.go b/cla-backend-go/github_organizations/models.go index 955ee50ee..db3755115 100644 --- a/cla-backend-go/github_organizations/models.go +++ b/cla-backend-go/github_organizations/models.go @@ -3,7 +3,7 @@ package github_organizations -import "github.com/communitybridge/easycla/cla-backend-go/gen/models" +import "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" // GithubOrganization is data model for github organizations type GithubOrganization struct { diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index 5f3ab77b1..2f6bc5a3c 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -19,7 +19,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/aws/aws-sdk-go/service/dynamodb/expression" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index a4224055b..e001d26b9 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -15,7 +15,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/repositories" ) diff --git a/cla-backend-go/health/handlers.go b/cla-backend-go/health/handlers.go index 9fe1f00b3..feba29b42 100644 --- a/cla-backend-go/health/handlers.go +++ b/cla-backend-go/health/handlers.go @@ -4,9 +4,9 @@ package health import ( - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/health" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/health" "github.com/go-openapi/runtime/middleware" ) diff --git a/cla-backend-go/health/service.go b/cla-backend-go/health/service.go index c64f97cba..61177d3a5 100644 --- a/cla-backend-go/health/service.go +++ b/cla-backend-go/health/service.go @@ -11,7 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" ini "github.com/communitybridge/easycla/cla-backend-go/init" ) diff --git a/cla-backend-go/project/errors.go b/cla-backend-go/project/errors.go index 34979b9b8..f62368699 100644 --- a/cla-backend-go/project/errors.go +++ b/cla-backend-go/project/errors.go @@ -4,7 +4,7 @@ package project import ( - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" ) // codedResponse interface diff --git a/cla-backend-go/project/handlers.go b/cla-backend-go/project/handlers.go index 7799daa9e..6c92040df 100644 --- a/cla-backend-go/project/handlers.go +++ b/cla-backend-go/project/handlers.go @@ -18,9 +18,9 @@ import ( "github.com/aws/aws-sdk-go/aws" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/project" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/project" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" diff --git a/cla-backend-go/project/helpers.go b/cla-backend-go/project/helpers.go index 1269ac8c2..e09f3f4eb 100644 --- a/cla-backend-go/project/helpers.go +++ b/cla-backend-go/project/helpers.go @@ -12,7 +12,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 79ee2d552..36f839193 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -17,7 +17,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/repositories" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/project" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/project" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/gofrs/uuid" @@ -27,7 +27,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/aws/aws-sdk-go/service/dynamodb/expression" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/project/service.go b/cla-backend-go/project/service.go index 3e98bca4d..551f5136a 100644 --- a/cla-backend-go/project/service.go +++ b/cla-backend-go/project/service.go @@ -16,8 +16,8 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/repositories" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/project" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/project" "github.com/communitybridge/easycla/cla-backend-go/gerrits" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/repositories/handlers.go b/cla-backend-go/repositories/handlers.go index a25b81ad7..547579555 100644 --- a/cla-backend-go/repositories/handlers.go +++ b/cla-backend-go/repositories/handlers.go @@ -8,9 +8,9 @@ import ( "fmt" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/github_repositories" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/github_repositories" "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime/middleware" diff --git a/cla-backend-go/repositories/mock/mock_repository.go b/cla-backend-go/repositories/mock/mock_repository.go index 9791f117e..a41593d36 100644 --- a/cla-backend-go/repositories/mock/mock_repository.go +++ b/cla-backend-go/repositories/mock/mock_repository.go @@ -12,7 +12,7 @@ import ( context "context" reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" gomock "github.com/golang/mock/gomock" ) diff --git a/cla-backend-go/repositories/mock/mock_service.go b/cla-backend-go/repositories/mock/mock_service.go index e5c1a9ce0..4d6e8067f 100644 --- a/cla-backend-go/repositories/mock/mock_service.go +++ b/cla-backend-go/repositories/mock/mock_service.go @@ -12,7 +12,7 @@ import ( context "context" reflect "reflect" - models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" gomock "github.com/golang/mock/gomock" ) diff --git a/cla-backend-go/repositories/models.go b/cla-backend-go/repositories/models.go index da8ec82ee..38398f3a4 100644 --- a/cla-backend-go/repositories/models.go +++ b/cla-backend-go/repositories/models.go @@ -3,7 +3,7 @@ package repositories -import "github.com/communitybridge/easycla/cla-backend-go/gen/models" +import "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" // RepositoryDBModel represent repositories table type RepositoryDBModel struct { diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index a769d1c1c..33212de84 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -23,7 +23,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index 4edfbcf59..054e14436 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -12,7 +12,7 @@ import ( "github.com/sirupsen/logrus" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/github" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go index 21821850e..f5a730b6f 100644 --- a/cla-backend-go/signatures/converters.go +++ b/cla-backend-go/signatures/converters.go @@ -11,7 +11,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index c1589bce2..700a20a44 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -11,9 +11,9 @@ import ( "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/github" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index e3d8c6468..7476d1b79 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -4,7 +4,7 @@ package signatures import ( - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" ) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index b0f9359aa..eefa0a026 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -23,7 +23,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -34,7 +34,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/aws/aws-sdk-go/aws" diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 3408a38a1..69d5bb47a 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -21,11 +21,11 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" githubpkg "github.com/google/go-github/v33/github" "golang.org/x/oauth2" ) diff --git a/cla-backend-go/template/handlers.go b/cla-backend-go/template/handlers.go index f3409386a..d01f67b0f 100644 --- a/cla-backend-go/template/handlers.go +++ b/cla-backend-go/template/handlers.go @@ -8,9 +8,9 @@ import ( "fmt" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/template" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/template" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/communitybridge/easycla/cla-backend-go/utils" diff --git a/cla-backend-go/template/repository.go b/cla-backend-go/template/repository.go index 76c20eaf0..9dc15efed 100644 --- a/cla-backend-go/template/repository.go +++ b/cla-backend-go/template/repository.go @@ -20,7 +20,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" ) var ( diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index db79189ca..dd2a040f3 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -20,7 +20,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/docraptor" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" diff --git a/cla-backend-go/tests/events_test.go b/cla-backend-go/tests/events_test.go index 20a8cd365..02d520a56 100644 --- a/cla-backend-go/tests/events_test.go +++ b/cla-backend-go/tests/events_test.go @@ -8,7 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/events" - eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/events" + eventOps "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" "github.com/stretchr/testify/assert" ) diff --git a/cla-backend-go/tests/project_test.go b/cla-backend-go/tests/project_test.go index 927f91e69..289c5a702 100644 --- a/cla-backend-go/tests/project_test.go +++ b/cla-backend-go/tests/project_test.go @@ -7,7 +7,7 @@ import ( "context" "testing" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/project" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/stretchr/testify/assert" diff --git a/cla-backend-go/tests/repository_test.go b/cla-backend-go/tests/repository_test.go index 2207c4e95..723fb1029 100644 --- a/cla-backend-go/tests/repository_test.go +++ b/cla-backend-go/tests/repository_test.go @@ -7,7 +7,7 @@ package tests import ( "fmt" "github.com/aws/aws-sdk-go/aws" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" ini "github.com/communitybridge/easycla/cla-backend-go/init" "github.com/google/uuid" "github.com/stretchr/testify/assert" diff --git a/cla-backend-go/userSubscribeLambda/main.go b/cla-backend-go/userSubscribeLambda/main.go index dec549704..8d3730785 100644 --- a/cla-backend-go/userSubscribeLambda/main.go +++ b/cla-backend-go/userSubscribeLambda/main.go @@ -18,7 +18,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/communitybridge/easycla/cla-backend-go/config" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/token" "github.com/communitybridge/easycla/cla-backend-go/userSubscribeLambda/cmd" diff --git a/cla-backend-go/users/handlers.go b/cla-backend-go/users/handlers.go index fb97b3bb1..ee0b84f28 100644 --- a/cla-backend-go/users/handlers.go +++ b/cla-backend-go/users/handlers.go @@ -9,9 +9,9 @@ import ( "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/users" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/users" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/user" "github.com/go-openapi/runtime/middleware" diff --git a/cla-backend-go/users/repository.go b/cla-backend-go/users/repository.go index 1412b91d4..1d633f3c7 100644 --- a/cla-backend-go/users/repository.go +++ b/cla-backend-go/users/repository.go @@ -21,7 +21,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb/expression" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/aws/aws-sdk-go/aws" diff --git a/cla-backend-go/users/service.go b/cla-backend-go/users/service.go index d1e17158f..715519965 100644 --- a/cla-backend-go/users/service.go +++ b/cla-backend-go/users/service.go @@ -7,7 +7,7 @@ import ( "errors" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/user" ) diff --git a/cla-backend-go/utils/cla_user.go b/cla-backend-go/utils/cla_user.go index 5470d1a90..7b5a462e5 100644 --- a/cla-backend-go/utils/cla_user.go +++ b/cla-backend-go/utils/cla_user.go @@ -6,7 +6,7 @@ package utils import ( "strings" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" ) // GetBestUsername gets best username of CLA User diff --git a/cla-backend-go/utils/responses.go b/cla-backend-go/utils/responses.go index c8c699a8c..2ecb9452e 100644 --- a/cla-backend-go/utils/responses.go +++ b/cla-backend-go/utils/responses.go @@ -6,7 +6,7 @@ package utils import ( "fmt" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" ) diff --git a/cla-backend-go/utils/signature_utils.go b/cla-backend-go/utils/signature_utils.go index c75da7a31..9a643f85f 100644 --- a/cla-backend-go/utils/signature_utils.go +++ b/cla-backend-go/utils/signature_utils.go @@ -5,7 +5,7 @@ package utils import ( "github.com/LF-Engineering/lfx-kit/auth" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/sirupsen/logrus" ) diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 91520848e..6afe2ed9c 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -27,8 +27,8 @@ import ( "github.com/jinzhu/copier" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" v1Project "github.com/communitybridge/easycla/cla-backend-go/project" diff --git a/cla-backend-go/v2/cla_manager/emails.go b/cla-backend-go/v2/cla_manager/emails.go index b053fc1a6..96828ac0f 100644 --- a/cla-backend-go/v2/cla_manager/emails.go +++ b/cla-backend-go/v2/cla_manager/emails.go @@ -11,7 +11,7 @@ import ( v2AcsService "github.com/communitybridge/easycla/cla-backend-go/v2/acs-service" "github.com/communitybridge/easycla/cla-backend-go/emails" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index c8e6e5a25..1cd59dbf1 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -29,7 +29,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service/client/organizations" v1ClaManager "github.com/communitybridge/easycla/cla-backend-go/cla_manager" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" v1User "github.com/communitybridge/easycla/cla-backend-go/user" easyCLAUser "github.com/communitybridge/easycla/cla-backend-go/users" diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 63b0547c3..fbd4bd7db 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -27,9 +27,9 @@ import ( "github.com/aws/aws-sdk-go/aws" v1Company "github.com/communitybridge/easycla/cla-backend-go/company" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" - v1ProjectParams "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/project" - v1SignatureParams "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + v1ProjectParams "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/project" + v1SignatureParams "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index a0d9a7dfb..06fa65955 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -14,7 +14,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/project" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" diff --git a/cla-backend-go/v2/dynamo_events/autoenable_test.go b/cla-backend-go/v2/dynamo_events/autoenable_test.go index b92d6a8f1..061408561 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable_test.go +++ b/cla-backend-go/v2/dynamo_events/autoenable_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index a7b25a612..ce536e51b 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -10,7 +10,7 @@ import ( "sync" "github.com/aws/aws-sdk-go/aws" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" organizationService "github.com/communitybridge/easycla/cla-backend-go/v2/organization-service" userService "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" @@ -18,7 +18,7 @@ import ( "github.com/aws/aws-lambda-go/events" claEvents "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" diff --git a/cla-backend-go/v2/events/converters.go b/cla-backend-go/v2/events/converters.go index 4f0528de8..868026574 100644 --- a/cla-backend-go/v2/events/converters.go +++ b/cla-backend-go/v2/events/converters.go @@ -4,7 +4,7 @@ package events import ( - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/jinzhu/copier" ) diff --git a/cla-backend-go/v2/events/csvResponse.go b/cla-backend-go/v2/events/csvResponse.go index e937e68ea..12641fab1 100644 --- a/cla-backend-go/v2/events/csvResponse.go +++ b/cla-backend-go/v2/events/csvResponse.go @@ -10,7 +10,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" diff --git a/cla-backend-go/v2/events/handlers.go b/cla-backend-go/v2/events/handlers.go index a5d88af22..da32630e0 100644 --- a/cla-backend-go/v2/events/handlers.go +++ b/cla-backend-go/v2/events/handlers.go @@ -21,7 +21,7 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" v1Company "github.com/communitybridge/easycla/cla-backend-go/company" v1Events "github.com/communitybridge/easycla/cla-backend-go/events" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/events" diff --git a/cla-backend-go/v2/gerrits/handlers.go b/cla-backend-go/v2/gerrits/handlers.go index 7118eb1a1..c0da4a705 100644 --- a/cla-backend-go/v2/gerrits/handlers.go +++ b/cla-backend-go/v2/gerrits/handlers.go @@ -16,7 +16,7 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gerrits" diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index a44c0398e..5cda76b0b 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -17,7 +17,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/v2/dynamo_events" diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go index 1c0a623cb..b667ee0eb 100644 --- a/cla-backend-go/v2/github_activity/service_test.go +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/events" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" "github.com/communitybridge/easycla/cla-backend-go/repositories/mock" "github.com/golang/mock/gomock" diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 28981b4af..9a9ea60d6 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -20,7 +20,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" v1GithubOrg "github.com/communitybridge/easycla/cla-backend-go/github_organizations" v1Repositories "github.com/communitybridge/easycla/cla-backend-go/repositories" diff --git a/cla-backend-go/v2/project/converters.go b/cla-backend-go/v2/project/converters.go index 00248dbef..781ec67f2 100644 --- a/cla-backend-go/v2/project/converters.go +++ b/cla-backend-go/v2/project/converters.go @@ -4,7 +4,7 @@ package project import ( - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/jinzhu/copier" ) diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 78c0455bf..b25d6a578 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -19,7 +19,7 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" - v1ProjectOps "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/project" + v1ProjectOps "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/project" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/project" diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 0d1800f69..bf8ba337d 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -19,7 +19,7 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/github_repositories" diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 837cce2ca..d7b2474ea 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -21,7 +21,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/github" "github.com/aws/aws-sdk-go/aws" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 6019dc427..b2ded0a47 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -28,7 +28,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/company" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/utils" ) diff --git a/cla-backend-go/v2/signatures/converters.go b/cla-backend-go/v2/signatures/converters.go index 904735a2e..50b4ecbd4 100644 --- a/cla-backend-go/v2/signatures/converters.go +++ b/cla-backend-go/v2/signatures/converters.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 2d315fb9a..71efaa668 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -21,14 +21,14 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/project" "github.com/communitybridge/easycla/cla-backend-go/company" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" - v1Signatures "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/signatures" + v1Signatures "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/signatures" "github.com/communitybridge/easycla/cla-backend-go/github" diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index fc57adb58..45aa39516 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -22,7 +22,7 @@ import ( "github.com/jinzhu/copier" "github.com/communitybridge/easycla/cla-backend-go/company" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/project" diff --git a/cla-backend-go/v2/template/handlers.go b/cla-backend-go/v2/template/handlers.go index 8292e6cd7..55c995e3b 100644 --- a/cla-backend-go/v2/template/handlers.go +++ b/cla-backend-go/v2/template/handlers.go @@ -16,7 +16,7 @@ import ( "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" v1Events "github.com/communitybridge/easycla/cla-backend-go/events" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/models" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/template" diff --git a/cla-backend-go/version/handlers.go b/cla-backend-go/version/handlers.go index 6ca46609e..fcaf6d1c2 100644 --- a/cla-backend-go/version/handlers.go +++ b/cla-backend-go/version/handlers.go @@ -5,9 +5,9 @@ package version import ( "github.com/aws/aws-sdk-go/aws" - "github.com/communitybridge/easycla/cla-backend-go/gen/models" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations" - "github.com/communitybridge/easycla/cla-backend-go/gen/restapi/operations/version" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/version" "github.com/go-openapi/runtime/middleware" ) From f6f6a30d46b705f63d957c26072f451fcadb81b9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 1 Jul 2021 17:05:20 -0700 Subject: [PATCH 0343/1276] Updated Add/Delete CLA Group Event Log Entries (#3028) Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 30 ++++++++++++++++++++---- cla-backend-go/v2/cla_groups/handlers.go | 10 ++++---- cla-backend-go/v2/project/handlers.go | 1 + 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 70f17f5ce..eec9bcd3f 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -974,7 +974,13 @@ func (ed *ClaManagerAccessRequestDeletedEventData) GetEventDetailsString(args *L // GetEventDetailsString returns the details string for this event func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Group ID: %s, Name: %s was created", args.ProjectID, args.ProjectName) + data := fmt.Sprintf("The CLA group %s was created", args.CLAGroupName) + if args.CLAGroupID != "" { + data = data + fmt.Sprintf(" with the CLA group ID %s", args.CLAGroupID) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -1009,7 +1015,13 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s // GetEventDetailsString returns the details string for this event func (ed *CLAGroupDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("CLA Group ID: %s was deleted", args.ProjectID) + data := fmt.Sprintf("The CLA group %s was deleted", args.CLAGroupName) + if args.CLAGroupID != "" { + data = data + fmt.Sprintf(" with the CLA group ID %s", args.CLAGroupID) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -1905,7 +1917,14 @@ func (ed *ClaManagerAccessRequestDeletedEventData) GetEventSummaryString(args *L // GetEventSummaryString returns the summary string for this event func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was created by the user %s.", args.ProjectName, args.UserName) + data := fmt.Sprintf("The CLA group %s was created", args.CLAGroupName) + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." return data, true } @@ -1939,7 +1958,10 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString returns the summary string for this event func (ed *CLAGroupDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The CLA Group %s was deleted", args.ProjectName) + data := fmt.Sprintf("The CLA group %s was deleted", args.CLAGroupName) + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index a1b0110b4..58eb9607d 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -72,10 +72,12 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P // Log the event eventsService.LogEvent(&events.LogEventArgs{ - EventType: events.CLAGroupCreated, - ProjectID: claGroup.ClaGroupID, - LfUsername: authUser.UserName, - EventData: &events.CLAGroupCreatedEventData{}, + EventType: events.CLAGroupCreated, + CLAGroupName: claGroup.ClaGroupName, + CLAGroupID: claGroup.ClaGroupID, + ParentProjectSFID: utils.StringValue(params.ClaGroupInput.FoundationSfid), + LfUsername: authUser.UserName, + EventData: &events.CLAGroupCreatedEventData{}, }) return cla_group.NewCreateClaGroupOK().WithXRequestID(reqID).WithPayload(claGroup) diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index b25d6a578..9ab981b5b 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -214,6 +214,7 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLAGroupDeleted, ClaGroupModel: claGroupModel, + ProjectSFID: params.ProjectSfdcID, LfUsername: authUser.UserName, EventData: &events.CLAGroupDeletedEventData{}, }) From 6a9978f7ac8c9fee9d04e9cc768266733641240a Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 2 Jul 2021 19:53:32 +0300 Subject: [PATCH 0344/1276] [#LFX-3163] Bug/ Signatory Details (#3029) --- cla-backend-go/v2/company/service.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index fbd4bd7db..34aac92a2 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1467,13 +1467,27 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 log.WithFields(f).Warnf("signature : %s have empty signature_acl", sig.SignatureID) return } - lfUsername := sig.SignatureACL[0].LfUsername - user, err := usc.GetUserByUsername(lfUsername) - if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to get user with lf username : %s", lfUsername) - return + + // get cla manager with cla signatory permission + orgClient := orgService.GetClient() + for _, lfUser := range sig.SignatureACL { + log.WithFields(f).Debugf("") + hasScope, hasScopeErr := orgClient.IsUserHaveRoleScope(ctx, utils.CLASignatoryRole, lfUser.LfUsername, v1CompanyModel.CompanyExternalID, cg.ProjectSFID) + if hasScopeErr != nil { + log.WithFields(f).WithError(hasScopeErr).Warnf("unable to check for %s permissions for user: %s ", utils.CLASignatoryRole, lfUser.LfUsername) + continue + } + if hasScope { + log.WithFields(f).Debugf("%s has %s permissions", lfUser.LfUsername, utils.CLASignatoryRole) + user, err := usc.GetUserByUsername(lfUser.LfUsername) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to get user with lf username : %s", lfUser.LfUsername) + return + } + signatoryName = user.Name + break + } } - signatoryName = user.Name }() cwg.Wait() From e5bc36780af622047fea4bd3ced3bd64156875f4 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 7 Jul 2021 21:43:30 +0300 Subject: [PATCH 0345/1276] LFX-3163 Feature/ Signatory Name - Removed cla-manager set for signatory name (active cla-list endpoint) Signed-off-by: Harold Wanyama --- cla-backend-go/v2/company/service.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 34aac92a2..31be7ed16 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1462,32 +1462,7 @@ func (s *service) fillActiveCLA(ctx context.Context, wg *sync.WaitGroup, sig *v1 signatoryName = sig.SignatoryName return } - usc := v2UserService.GetClient() - if len(sig.SignatureACL) == 0 { - log.WithFields(f).Warnf("signature : %s have empty signature_acl", sig.SignatureID) - return - } - // get cla manager with cla signatory permission - orgClient := orgService.GetClient() - for _, lfUser := range sig.SignatureACL { - log.WithFields(f).Debugf("") - hasScope, hasScopeErr := orgClient.IsUserHaveRoleScope(ctx, utils.CLASignatoryRole, lfUser.LfUsername, v1CompanyModel.CompanyExternalID, cg.ProjectSFID) - if hasScopeErr != nil { - log.WithFields(f).WithError(hasScopeErr).Warnf("unable to check for %s permissions for user: %s ", utils.CLASignatoryRole, lfUser.LfUsername) - continue - } - if hasScope { - log.WithFields(f).Debugf("%s has %s permissions", lfUser.LfUsername, utils.CLASignatoryRole) - user, err := usc.GetUserByUsername(lfUser.LfUsername) - if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to get user with lf username : %s", lfUser.LfUsername) - return - } - signatoryName = user.Name - break - } - } }() cwg.Wait() From 7a3c2625975bd7ed7a310a010144e4d63de3f466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Jul 2021 07:16:10 -0700 Subject: [PATCH 0346/1276] Bump socket.io-parser from 3.3.1 to 3.3.2 in /cla-frontend-corporate-console (#3024) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 8c8b1f913..bd7cfae77 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -5846,9 +5846,9 @@ socket.io-client@^2.3.0: to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: component-emitter "~1.3.0" debug "~3.1.0" From ade9d5ca0c29126156fc3b2bb7a13df846c75d6f Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 9 Jul 2021 02:02:38 +0300 Subject: [PATCH 0347/1276] Bug/ GH Status Update - Resolved GH status update for a company affiliation use case Signed-off-by: Harold Wanyama --- cla-backend/cla/models/github_models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 29b14e9cd..6101367b1 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -844,6 +844,7 @@ def handle_commit_from_user(project, commit_sha, author_info, signed, missing): f'user: {author_username}, ' f'email {author_email}) is on the approved list, ' 'but not affiliated with a company') + list_author_info.append(True) break missing.append((commit_sha, list_author_info)) From daf5b14c941d696c2e34d64070cf39fb908b8f7f Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 9 Jul 2021 02:09:36 +0300 Subject: [PATCH 0348/1276] Bug/GH Status Update - Resolved status update for company affiliatio use case Signed-off-by: Harold Wanyama --- cla-backend/cla/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 9c7431d34..6d2a60666 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1001,8 +1001,10 @@ def get_comment_body(repository_type, sign_url, signed, missing): ) else: if True in commit_hashes: + cla.log.info(f"{fn} filter True in commit hash listing: {commit_hashes}") + filtered_commits = [commit_hash for commit_hash in commit_hashes if commit_hash != True] committers_comment += ( - f"
  • {author} ({' ,'.join(commit_hashes[:-1])}) " + f"
  • {author} ({' ,'.join(filtered_commits)}) " + f"is authorized, but they must confirm their affiliation with their company. " + f"Start the authorization process " + f" by clicking here, click \"Corporate\"," From 020de654d1abd2233d76cc188538b0c37bdd7cc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jul 2021 09:28:52 -0700 Subject: [PATCH 0349/1276] Bump socket.io-parser from 3.3.1 to 3.3.2 in /cla-backend (#3020) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 31b8c1bf3..b2df201fa 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -2005,10 +2005,10 @@ dayjs@^1.10.3: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.3.tgz#cf3357c8e7f508432826371672ebf376cb7d619b" integrity sha512-/2fdLN987N8Ki7Id8BUN2nhuiRyxTLumQnSQf9CNncFCyqFsSKb9TNhzRYcC8K8eJSJOKvbvkImo/MKKhNi4iw== -debug@4, debug@^4.0.1, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== +debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" @@ -2026,13 +2026,6 @@ debug@^3.0.1, debug@^3.1.0, debug@^3.1.1: dependencies: ms "^2.1.1" -debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -5589,9 +5582,9 @@ socket.io-client@^2.3.0: to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: component-emitter "~1.3.0" debug "~3.1.0" From 209a5c096ad94db060b965e0408ea796464ec363 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jul 2021 20:42:30 -0700 Subject: [PATCH 0350/1276] Bump socket.io-parser from 3.3.1 to 3.3.2 in /cla-frontend-contributor-console (#3023) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 23 ++++++++-------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index d7e1b8ee0..5592afa90 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -1879,12 +1879,12 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== +debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: - ms "^2.1.1" + ms "2.1.2" debug@^3.0.1, debug@^3.1.0: version "3.2.6" @@ -1893,13 +1893,6 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -5890,9 +5883,9 @@ socket.io-client@^2.3.0: to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: component-emitter "~1.3.0" debug "~3.1.0" From 46be35f85e323eeb7a269b96017b0d15986fa31a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jul 2021 10:59:47 -0700 Subject: [PATCH 0351/1276] Bump socket.io-parser from 3.3.1 to 3.3.2 (#3021) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index d323fbb34..7259e5313 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1456,10 +1456,10 @@ dayjs@^1.9.5: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.5.tgz#fd49994ebe71639d2ce9575e97186642dfce9808" integrity sha512-WULIw7UpW/E0y6VywewpbXAMH3d5cZijEhoHLwM+OMVbk/NtchKS/W+57H/0P1rqU7gHrAArjiRLHCUhgMQl6w== -debug@4, debug@^4.0.1, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" @@ -1477,13 +1477,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.1.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -4278,9 +4271,9 @@ socket.io-client@^2.3.0: to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: component-emitter "~1.3.0" debug "~3.1.0" From 332dd4b63d630ccf17ad06a5137e5c7c97bba931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 Jul 2021 21:58:18 -0700 Subject: [PATCH 0352/1276] Bump socket.io-parser from 3.3.1 to 3.3.2 in /cla-backend-go (#3022) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index 678865e5d..69ac0d3b1 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -1588,10 +1588,10 @@ dayjs@^1.10.3: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.3.tgz#cf3357c8e7f508432826371672ebf376cb7d619b" integrity sha512-/2fdLN987N8Ki7Id8BUN2nhuiRyxTLumQnSQf9CNncFCyqFsSKb9TNhzRYcC8K8eJSJOKvbvkImo/MKKhNi4iw== -debug@4, debug@^4.0.1, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== +debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" @@ -1609,13 +1609,6 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -4504,9 +4497,9 @@ socket.io-client@^2.3.0: to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: component-emitter "~1.3.0" debug "~3.1.0" From cdcaf5a1ebc808af002644f901cbd8c26b74c1dd Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 21 Jul 2021 03:41:14 +0300 Subject: [PATCH 0353/1276] [Snyk] Security upgrade networkx from 2.2 to 2.6 (#3034) --- scripts/audits/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/audits/requirements.txt b/scripts/audits/requirements.txt index 7b4a7134b..c876de3bf 100644 --- a/scripts/audits/requirements.txt +++ b/scripts/audits/requirements.txt @@ -5,3 +5,4 @@ pylint==2.4.3 pytest==5.3.1 moto==1.3.14 +networkx>=2.6 # not directly required, pinned by Snyk to avoid a vulnerability From 7509c28a772eb18064c777a517d6cf1311e23475 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 20 Jul 2021 22:34:43 -0500 Subject: [PATCH 0354/1276] Project Service - Use Foundation ID for Parent (#3037) - Updated logic to use/leverage the project model foundation ID for the parent ID rather than using the Parent attribute. Added additional defensive logic to projects with no parents. Resolved null pointer error when no parent is present. - Added helper function for determining if a projec has a parent or not - Added helper function for getting the parent ID (safe method) Signed-off-by: David Deal --- cla-backend-go/events/service.go | 12 +-- cla-backend-go/tests/project_helpers_test.go | 16 +--- cla-backend-go/utils/project_helpers.go | 17 +++- cla-backend-go/v2/cla_groups/handlers.go | 14 +-- cla-backend-go/v2/cla_groups/helpers.go | 48 ++++++---- cla-backend-go/v2/cla_groups/service.go | 9 +- cla-backend-go/v2/company/service.go | 5 +- .../v2/github_organizations/service.go | 13 +-- cla-backend-go/v2/project-service/client.go | 87 ++++++++++--------- cla-backend-go/v2/project/handlers.go | 31 ++++--- cla-backend-go/v2/repositories/service.go | 9 +- cla-backend-go/v2/sign/service.go | 3 +- 12 files changed, 143 insertions(+), 121 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 0ad22ba99..f3e9241aa 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -255,11 +255,11 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { args.ProjectName = project.Name // Try to load and set the parent information - if utils.StringValue(project.Parent) != "" { - log.WithFields(f).Debugf("loading salesforce project parent by ID: %s...", utils.StringValue(project.Parent)) - parentProject, parentProjectErr := project_service.GetClient().GetProject(utils.StringValue(project.Parent)) + if project.Foundation != nil && project.Foundation.ID != "" { + log.WithFields(f).Debugf("loading salesforce project parent by ID: %s...", project.Foundation.ID) + parentProject, parentProjectErr := project_service.GetClient().GetProject(project.Foundation.ID) if parentProjectErr != nil || parentProject == nil { - log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", utils.StringValue(project.Parent)) + log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", project.Foundation.ID) return nil } var parentProjectName, parentProjectID string @@ -271,7 +271,7 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { parentProjectID = project.ID } log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s - resulting in ID: %s with name: %s", - utils.StringValue(project.Parent), parentProjectID, parentProjectName) + project.Foundation.ID, parentProjectID, parentProjectName) args.ParentProjectSFID = parentProjectID args.ParentProjectName = parentProjectName } else if project.Foundation != nil { @@ -293,7 +293,7 @@ func (s *service) loadLFUser(ctx context.Context, args *LogEventArgs) error { } if args == nil { - return errors.New(("unable to load lf user data - args is nil")) + return errors.New("unable to load lf user data - args is nil") } if args.LfUsername != "" { diff --git a/cla-backend-go/tests/project_helpers_test.go b/cla-backend-go/tests/project_helpers_test.go index eb45c05ad..8985bf3df 100644 --- a/cla-backend-go/tests/project_helpers_test.go +++ b/cla-backend-go/tests/project_helpers_test.go @@ -13,21 +13,18 @@ import ( ) const ( - testProjectParentID = "abc1234" - testProjectID = "def13456" - testProjectLogo = "testlogurl.com" + testProjectID = "def13456" + testProjectLogo = "testlogurl.com" ) func TestIsProjectHasRootParentNoParent(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef("") project.Foundation = nil assert.True(t, utils.IsProjectHasRootParent(project), "Project Has Root Parent - Empty Parent") } func TestIsProjectHasRootParentLF(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -38,7 +35,6 @@ func TestIsProjectHasRootParentLF(t *testing.T) { func TestIsProjectHasRootParentLFProjectsLLC(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -49,7 +45,6 @@ func TestIsProjectHasRootParentLFProjectsLLC(t *testing.T) { func TestIsProjectHasRootParentNonLF(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -60,7 +55,6 @@ func TestIsProjectHasRootParentNonLF(t *testing.T) { func TestIsStandaloneProject(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef("") project.Foundation = nil project.Projects = []*models.ProjectOutput{} assert.True(t, utils.IsStandaloneProject(project), "Standalone Project with No Parent with No Children") @@ -68,7 +62,6 @@ func TestIsStandaloneProject(t *testing.T) { func TestLFParent(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -80,7 +73,6 @@ func TestLFParent(t *testing.T) { func TestLFProjectsLLCParent(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -92,7 +84,6 @@ func TestLFProjectsLLCParent(t *testing.T) { func TestLFParentWithChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -104,7 +95,6 @@ func TestLFParentWithChildren(t *testing.T) { func TestLFProjectsLLCParentWithChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = &models.Foundation{ ID: testProjectID, LogoURL: testProjectLogo, @@ -140,7 +130,6 @@ func TestLFProjectsLLCParentWithChildren(t *testing.T) { func TestIsProjectHaveChildrenNoChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = nil project.Projects = []*models.ProjectOutput{} assert.False(t, utils.IsProjectHaveChildren(project), "Project has no children") @@ -148,7 +137,6 @@ func TestIsProjectHaveChildrenNoChildren(t *testing.T) { func TestIsProjectHaveChildrenWithChildren(t *testing.T) { project := &models.ProjectOutputDetailed{} - project.Parent = utils.StringRef(testProjectParentID) project.Foundation = nil child := &models.ProjectOutput{ ProjectCommon: models.ProjectCommon{}, diff --git a/cla-backend-go/utils/project_helpers.go b/cla-backend-go/utils/project_helpers.go index 6cffde169..066b4c622 100644 --- a/cla-backend-go/utils/project_helpers.go +++ b/cla-backend-go/utils/project_helpers.go @@ -5,15 +5,28 @@ package utils import "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/models" +// GetProjectParentSFID returns the project parent SFID if available, otherwise returns empty string +func GetProjectParentSFID(project *models.ProjectOutputDetailed) string { + if project == nil || project.Foundation == nil || project.Foundation.ID == "" { + return "" + } + return project.Foundation.ID +} + +// IsProjectHaveParent returns true if the specified project has a parent +func IsProjectHaveParent(project *models.ProjectOutputDetailed) bool { + return project != nil && project.Foundation != nil && project.Foundation.ID != "" && project.Foundation.Name != "" +} + // IsProjectHasRootParent determines if the a given project has a root parent. A root parent is a parent that is empty parent or the parent is TLF or LFProjects func IsProjectHasRootParent(project *models.ProjectOutputDetailed) bool { - return StringValue(project.Parent) == "" || (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC)) + return project.Foundation == nil || (project.Foundation != nil && project.Foundation.ID != "" && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC)) } // IsStandaloneProject determines if a given project is a standalone project. A standalone project is a project with no parent or the parent is TLF/LFProjects and does not have any children func IsStandaloneProject(project *models.ProjectOutputDetailed) bool { // standalone: No parent or parent is TLF/LFProjects....and no children - return (StringValue(project.Parent) == "" || + return (project.Foundation == nil || (project.Foundation != nil && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC))) && len(project.Projects) == 0 } diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 58eb9607d..bca28d288 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -308,15 +308,15 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } var parentProject *v2ProjectServiceModels.ProjectOutputDetailed // Handle the ONAP edge case - if utils.StringValue(project.Parent) != "" { - parentProject, projectErr = psc.GetProject(utils.StringValue(project.Parent)) + if utils.IsProjectHaveParent(project) { + parentProject, projectErr = psc.GetProject(utils.GetProjectParentSFID(project)) if parentProject == nil || projectErr != nil { - msg := fmt.Sprintf("Failed to get parent: %s", utils.StringValue(project.Parent)) + msg := fmt.Sprintf("Failed to get parent: %s", utils.GetProjectParentSFID(project)) log.WithFields(f).Warnf(msg) return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } } - if (utils.StringValue(project.Parent) != "" && !utils.IsProjectCategory(project, parentProject)) || (utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { + if (utils.IsProjectHaveParent(project) && !utils.IsProjectCategory(project, parentProject)) || (utils.IsProjectHasRootParent(project) && project.ProjectType == utils.ProjectTypeProjectGroup) { msg := fmt.Sprintf("Unable to enroll salesforce foundation project: %s in project level cla-group.", projectSFID) return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } @@ -424,9 +424,9 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P log.WithFields(f).Debug("found project - evaluating parent...") var projectSFIDs []string // Add the foundation ID, if available - if project.Foundation != nil && project.Foundation.ID != "" { - log.WithFields(f).Debugf("parent project - found %s - adding to list of project IDs...", project.Foundation.ID) - projectSFIDs = append(projectSFIDs, project.Foundation.ID) + if utils.IsProjectHaveParent(project) { + log.WithFields(f).Debugf("parent project - found %s - adding to list of project IDs...", utils.GetProjectParentSFID(project)) + projectSFIDs = append(projectSFIDs, utils.GetProjectParentSFID(project)) } log.WithFields(f).Debug("project - adding to list of project IDs...") projectSFIDs = append(projectSFIDs, project.ID) diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index cf27d3867..3fc5916d5 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -159,16 +159,19 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent, err := psc.IsTheLinuxFoundation(utils.StringValue(foundationProjectDetails.Parent)) - if err != nil { - log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) - return false, err + isLFParent := false + if utils.IsProjectHaveParent(foundationProjectDetails) { + isLFParent, err = psc.IsTheLinuxFoundation(foundationProjectDetails.Foundation.ID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup parent project by SFID: %s", foundationProjectDetails.Foundation.ID) + return false, err + } } // If the foundation details in the platform project service indicates that this foundation has no parent or no // children/sub-project... (stand alone project situation) log.WithFields(f).Debug("checking to see if we have a standalone project...") - if (utils.StringValue(foundationProjectDetails.Parent) == "" || isLFParent) && len(foundationProjectDetails.Projects) == 0 { + if isLFParent && len(foundationProjectDetails.Projects) == 0 { log.WithFields(f).Debug("we have a standalone project...") // Did the user actually pass in any projects? If none - add the foundation ID to the list and return to // indicate it is a "standalone project" @@ -218,15 +221,21 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI // fetch the foundation model details from the platform project service which includes a list of its sub projects foundationProjectDetails, err := psc.GetProject(foundationSFID) if err != nil { - log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) + log.WithFields(f).WithError(err).Warnf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) return err } + if foundationProjectDetails == nil { + return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + } foundationProjectSummary, err := psc.GetSummary(ctx, foundationSFID) if err != nil { - log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) + log.WithFields(f).WithError(err).Warnf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) return err } + if foundationProjectSummary == nil { + return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + } // build Tree that tracks parent and child projects projectTree := buildProjectNode(foundationProjectSummary) @@ -251,7 +260,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI if projectTree != nil && projectTree.Parent != nil && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", - utils.StringValue(foundationProjectDetails.Parent), foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + foundationProjectDetails.Foundation.ID, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) } @@ -316,25 +325,32 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS // fetch the foundation model details from the platform project service which includes a list of its sub projects foundationProjectDetails, err := psc.GetProject(foundationSFID) if err != nil { - log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) return err } + if foundationProjectDetails == nil { + return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + } foundationProjectSummary, err := psc.GetSummary(ctx, foundationSFID) if err != nil { - log.WithFields(f).Warnf("validation failure - problem fetching project details from project service, error: %+v", err) return err } + if foundationProjectSummary == nil { + return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + } // build Tree that tracks parent and child projects projectTree := buildProjectNode(foundationProjectSummary) // Is our parent the LF project? log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent, err := psc.IsTheLinuxFoundation(utils.StringValue(foundationProjectDetails.Parent)) - if err != nil { - log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup %s or %s project", utils.TheLinuxFoundation, utils.LFProjectsLLC) - return err + isLFParent := false + if utils.IsProjectHaveParent(foundationProjectDetails) { + isLFParent, err = psc.IsTheLinuxFoundation(foundationProjectDetails.Foundation.ID) + if err != nil { + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup parent project by SFID: %s", foundationProjectDetails.Foundation.ID) + return err + } } for _, projectSFID := range projectSFIDList { @@ -343,9 +359,9 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } - if utils.StringValue(foundationProjectDetails.Parent) != "" && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { + if !isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", - utils.StringValue(foundationProjectDetails.Parent), foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + foundationProjectDetails.Foundation.ID, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) } diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 6afe2ed9c..3ffaffd02 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -386,7 +386,8 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje var parentDetails *v2ProjectServiceModels.ProjectOutputDetailed var parentDetailErr error - if utils.StringValue(sfProjectModelDetails.Parent) != "" { + // If we have a parent... + if utils.IsProjectHaveParent(sfProjectModelDetails) { var parentSFID string // Use utility function that considers TLF and LF Projects, LLC parentSFID, parentDetailErr = v2ProjectService.GetClient().GetParentProject(projectOrFoundationSFID) @@ -646,9 +647,9 @@ func (s *service) appendCLAGroupsForProject(ctx context.Context, f logrus.Fields // Since this is a project and not a foundation, we'll want to set he parent foundation ID and name (which is // our parent in this case) var foundationID, foundationName string - if sfProjectModelDetails.ProjectOutput.Foundation != nil { - foundationID = sfProjectModelDetails.ProjectOutput.Foundation.ID - foundationName = sfProjectModelDetails.ProjectOutput.Foundation.Name + if utils.IsProjectHaveParent(sfProjectModelDetails) { + foundationID = sfProjectModelDetails.Foundation.ID + foundationName = sfProjectModelDetails.Foundation.Name log.WithFields(f).Debugf("using parent foundation ID: %s and name: %s", foundationID, foundationName) } else { // Project with no parent - must be a standalone - use our ID and Name as the foundation diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 31be7ed16..127403fba 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1144,6 +1144,9 @@ func (s *service) GetCompanyCLAGroupManagers(ctx context.Context, companyID, cla func v2ProjectToMap(projectDetails *v2ProjectServiceModels.ProjectOutputDetailed) (map[string]*v2ProjectServiceModels.ProjectOutput, error) { epmap := make(map[string]*v2ProjectServiceModels.ProjectOutput) // key project_sfid + if projectDetails == nil { + return epmap, nil + } var pr v2ProjectServiceModels.ProjectOutput err := copier.Copy(&pr, projectDetails) if err != nil { @@ -1212,7 +1215,7 @@ func (s *service) getCLAGroupsUnderProjectOrFoundation(ctx context.Context, proj log.WithFields(f).WithError(err).Warnf("unable to get project IDs for CLA Group: %s", projectMapping.ClaGroupID) return nil, err } - if len(allProjectMapping) > 1 { + if len(allProjectMapping) > 1 && projectDetails.Foundation != nil && projectDetails.Foundation.ID != "" { // reload data in projectDetails for all projects of foundation projectDetails, err = psc.GetProject(projectDetails.Foundation.ID) if err != nil { diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 9a9ea60d6..114cd8c3e 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -92,18 +92,19 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) psc := v2ProjectService.GetClient() log.WithFields(f).Debug("loading project details from the project service...") - projectServiceRecord, err := psc.GetProject(projectSFID) + project, err := psc.GetProject(projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("problem loading project details from the project service") return nil, err } var parentProjectSFID string - if utils.IsProjectHasRootParent(projectServiceRecord) { + if !utils.IsProjectHaveParent(project) || utils.IsProjectHasRootParent(project) || utils.GetProjectParentSFID(project) == "" { parentProjectSFID = projectSFID } else { - parentProjectSFID = utils.StringValue(projectServiceRecord.Parent) + parentProjectSFID = utils.GetProjectParentSFID(project) } + f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") @@ -290,12 +291,12 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } var parentProjectSFID string - if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if !utils.IsProjectHaveParent(project) || utils.IsProjectHasRootParent(project) || utils.GetProjectParentSFID(project) == "" { parentProjectSFID = projectSFID } else { - parentProjectSFID = utils.StringValue(project.Parent) + parentProjectSFID = utils.GetProjectParentSFID(project) } + f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 9d25f680a..55b054923 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -141,34 +141,18 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { "apiGWHost": apiGWHost, } - // Lookup in cache first - existingModel, exists := projectServiceModels[projectSFID] - if exists { - log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) - return utils.StringValue(existingModel.Parent), nil - } - log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModels)) - - log.WithFields(f).Debug("looking up projectModel in SF by projectSFID") - projectModel, err := pmm.GetProject(projectSFID) + // Use our helper function to lookup the parent, if it exists + parentModel, err := pmm.GetParentProjectModel(projectSFID) if err != nil { - log.WithFields(f).Warnf("unable to lookup projectModel in projectModel service by projectSFID, error: %+v", err) - return "", err + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) + return projectSFID, err } - - // Update our cache for next time - projectServiceModels[projectSFID] = projectModel - log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) - - // Do they have a parent? - if utils.StringValue(projectModel.Parent) == "" || (projectModel.Foundation != nil && - (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { - log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) - return projectSFID, nil + if parentModel == nil || parentModel.Foundation == nil || parentModel.Foundation.ID == "" { + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) + return projectSFID, err } - log.WithFields(f).Debugf("returning parent projectSFID: %s", utils.StringValue(projectModel.Parent)) - return utils.StringValue(projectModel.Parent), nil + return parentModel.Foundation.ID, nil } // GetParentProjectModel returns the parent project model if there is a parent, otherwise returns nil @@ -189,21 +173,30 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut if exists { log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) - // Parent in the cache? - existingParentModel, exists = projectServiceModels[utils.StringValue(existingModel.Parent)] + // Does this project they have a parent? projectModel.Parent is deprecated and no longer returned, use project.Foundation.ID/Name attribute instead + if existingModel.Foundation != nil && existingModel.Foundation.ID != "" && (existingModel.Foundation.Name == utils.TheLinuxFoundation || existingModel.Foundation.Name == utils.LFProjectsLLC) { + log.WithFields(f).Debugf("no parent for projectSFID %s or %s or %s is the parent...", projectSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC) + return nil, nil + } + + // Grab the parent ID once + projectParentSFID := existingModel.Foundation.ID + + // Parent SFID in the cache? + existingParentModel, exists = projectServiceModels[projectParentSFID] if exists { return existingParentModel, nil } // Parent project not in the cache - lookup - parentProjectModel, err := pmm.GetProject(utils.StringValue(existingModel.Parent)) + parentProjectModel, err := pmm.GetProject(projectParentSFID) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", utils.StringValue(existingModel.Parent)) + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel with projectSFID: '%s'", projectParentSFID) return nil, err } - // Update our cache for next time - projectServiceModels[utils.StringValue(existingModel.Parent)] = parentProjectModel + // Save/Update our cache for next time + projectServiceModels[projectParentSFID] = parentProjectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) return parentProjectModel, nil @@ -215,33 +208,43 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut log.WithFields(f).Warnf("unable to lookup projectModel in projectModel service by projectSFID, error: %+v", err) return nil, err } + if projectModel == nil { + return nil, nil + } - // Update our cache for next time + // Save/Update our cache for next time projectServiceModels[projectSFID] = projectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) - // Do they have a parent? - if utils.StringValue(projectModel.Parent) == "" || (projectModel.Foundation != nil && - (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC)) { + // No parent + if projectModel.Foundation == nil || projectModel.Foundation.ID == "" { + return nil, nil + } + + // Do they have a parent? projectModel.Parent is deprecated and no longer returned + if projectModel.Foundation != nil && projectModel.Foundation.ID != "" && (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC) { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return nil, nil } + // Grab the parent ID once + projectParentSFID := projectModel.Foundation.ID + // Parent in the cache? - existingParentModel, exists = projectServiceModels[utils.StringValue(projectModel.Parent)] + existingParentModel, exists = projectServiceModels[projectParentSFID] if exists { return existingParentModel, nil } // Parent project not in the cache - lookup - parentProjectModel, err := pmm.GetProject(utils.StringValue(projectModel.Parent)) + parentProjectModel, err := pmm.GetProject(projectParentSFID) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel projectSFID: '%s'", utils.StringValue(projectModel.Parent)) + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel with projectSFID: '%s'", projectParentSFID) return nil, err } - // Update our cache for next time - projectServiceModels[utils.StringValue(existingModel.Parent)] = parentProjectModel + // Save/Update our cache for next time + projectServiceModels[projectParentSFID] = parentProjectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) return parentProjectModel, nil @@ -286,13 +289,13 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) return false, err } - if utils.StringValue(projectModel.Parent) == "" { + if projectModel.Foundation == nil || projectModel.Foundation.ID == "" { return false, nil } - parentProjectModel, err := pmm.GetProject(utils.StringValue(projectModel.Parent)) + parentProjectModel, err := pmm.GetProject(projectModel.Foundation.ID) if err != nil { - log.WithFields(f).Warnf("unable to lookup parent project by ID: %s error: %+v", utils.StringValue(projectModel.Parent), err) + log.WithFields(f).Warnf("unable to lookup parent project by ID: %s error: %+v", projectModel.Foundation.ID, err) return false, err } diff --git a/cla-backend-go/v2/project/handlers.go b/cla-backend-go/v2/project/handlers.go index 9ab981b5b..b03df617b 100644 --- a/cla-backend-go/v2/project/handlers.go +++ b/cla-backend-go/v2/project/handlers.go @@ -317,10 +317,10 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service // Lookup the parent info, if it's available var parentName string - if utils.StringValue(sfProject.Parent) != "" { - sfParentProject, err := psc.GetProject(utils.StringValue(sfProject.Parent)) + if utils.IsProjectHaveParent(sfProject) { + sfParentProject, err := psc.GetProject(utils.GetProjectParentSFID(sfProject)) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to load parant project by ID: %s", utils.StringValue(sfProject.Parent)) + log.WithFields(f).WithError(err).Warnf("unable to load parant project by ID: %s", utils.GetProjectParentSFID(sfProject)) } if sfParentProject != nil { @@ -335,18 +335,17 @@ func Configure(api *operations.EasyclaAPI, service v1Project.Service, v2Service func buildSFProjectSummary(sfProject *v2ProjectServiceModels.ProjectOutputDetailed, parentName string) *models.SfProjectSummary { return &models.SfProjectSummary{ - EntityName: utils.StringValue(sfProject.EntityName), - EntityType: sfProject.EntityType, - Funding: sfProject.Funding, - ID: sfProject.ID, - LfSupported: sfProject.LFSponsored, - Name: sfProject.Name, - ParentID: utils.StringValue(sfProject.Parent), - ParentName: parentName, - Slug: sfProject.Slug, - Status: sfProject.Status, - Type: sfProject.Type, - IsStandalone: (sfProject.Type != utils.ProjectTypeProjectGroup) && (utils.StringValue(sfProject.Parent) == "" || (sfProject.Foundation != nil && - (sfProject.Foundation.Name == utils.TheLinuxFoundation || sfProject.Foundation.Name == utils.LFProjectsLLC))), + EntityName: utils.StringValue(sfProject.EntityName), + EntityType: sfProject.EntityType, + Funding: sfProject.Funding, + ID: sfProject.ID, + LfSupported: sfProject.LFSponsored, + Name: sfProject.Name, + ParentID: utils.GetProjectParentSFID(sfProject), + ParentName: parentName, + Slug: sfProject.Slug, + Status: sfProject.Status, + Type: sfProject.Type, + IsStandalone: utils.IsStandaloneProject(sfProject), } } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index d7b2474ea..9e0f95264 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -90,11 +90,10 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, } var parentProjectSFID string - if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if !utils.IsProjectHaveParent(project) || utils.IsProjectHasRootParent(project) || utils.GetProjectParentSFID(project) == "" { parentProjectSFID = projectSFID } else { - parentProjectSFID = utils.StringValue(project.Parent) + parentProjectSFID = utils.GetProjectParentSFID(project) } allMappings, err := s.projectsClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, aws.StringValue(input.ClaGroupID)) @@ -252,8 +251,8 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin return nil, err } f["projectName"] = projectModel.Name - if utils.StringValue(projectModel.Parent) != "" { - f["projectParentSFID"] = projectModel.Parent + if utils.IsProjectHaveParent(projectModel) { + f["projectParentSFID"] = utils.GetProjectParentSFID(projectModel) } log.WithFields(f).Debug("loaded project from the project service") enabled := true diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index b2ded0a47..7d671f107 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -173,8 +173,7 @@ func (s *service) RequestCorporateSignature(ctx context.Context, lfUsername stri } var claGroupID string - if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + if !utils.IsProjectHaveParent(project) || utils.IsProjectHasRootParent(project) || utils.GetProjectParentSFID(project) == "" { // this is root project cgmlist, perr := s.projectClaGroupsRepo.GetProjectsIdsForFoundation(ctx, utils.StringValue(input.ProjectSfid)) if perr != nil { From a1e23a5feac28a4649a1f81ae30ab8359e6ee840 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 21 Jul 2021 17:33:26 +0300 Subject: [PATCH 0355/1276] [Snyk] Security upgrade networkx from 2.2 to 2.6 (#3033) --- scripts/signature-audits/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/signature-audits/requirements.txt b/scripts/signature-audits/requirements.txt index b386d24c1..6f8f0e7fa 100644 --- a/scripts/signature-audits/requirements.txt +++ b/scripts/signature-audits/requirements.txt @@ -11,3 +11,4 @@ oauthlib==3.1.0 requests==2.22.0 requests-oauthlib==1.2.0 moto==1.3.14 +networkx>=2.6 # not directly required, pinned by Snyk to avoid a vulnerability From a34614f3fbd63f798ad3ce6865f3de627d259168 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Jul 2021 10:19:46 -0500 Subject: [PATCH 0356/1276] Resolved a number of Node Library Vulnerabilities (#3038) --- cla-backend-go/package.json | 4 +- cla-backend-go/yarn.lock | 70 +------ cla-backend/package.json | 4 +- cla-backend/yarn.lock | 70 +------ cla-frontend-contributor-console/package.json | 6 +- cla-frontend-contributor-console/yarn.lock | 182 ++---------------- cla-frontend-corporate-console/package.json | 6 +- cla-frontend-corporate-console/yarn.lock | 182 ++---------------- cla-frontend-project-console/package.json | 6 +- cla-frontend-project-console/yarn.lock | 182 ++---------------- package.json | 4 +- yarn.lock | 70 +------ 12 files changed, 90 insertions(+), 696 deletions(-) diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index e854a40bd..d58f86e95 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -28,6 +28,8 @@ }, "resolutions": { "axios": "^0.21.1", - "ini": "^1.3.7" + "ini": "^1.3.7", + "normalize-url": "^4.5.1", + "ws": "^7.4.6" } } diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index 69ac0d3b1..ffa8b1db2 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -886,11 +886,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.6.1, async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -1616,11 +1611,6 @@ debug@~3.1.0: dependencies: ms "2.0.0" -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" @@ -3522,19 +3512,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npmlog@^4.0.1: version "4.1.2" @@ -3981,15 +3962,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -4519,13 +4491,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-map-support@^0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -4605,11 +4570,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -5154,24 +5114,10 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^7.2.1, ws@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@<7.0.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== xml2js@0.4.19: version "0.4.19" diff --git a/cla-backend/package.json b/cla-backend/package.json index 30ea00235..fb13cec78 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -47,6 +47,8 @@ "resolutions": { "axios": "^0.21.1", "ini": "^1.3.7", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "normalize-url": "^4.5.1", + "ws": "^7.4.6" } } diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index b2df201fa..6a2f8a6dc 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -1183,11 +1183,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.6.1, async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -2038,11 +2033,6 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" @@ -4354,19 +4344,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npm-run-path@^4.0.0: version "4.0.1" @@ -4938,15 +4919,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -5604,13 +5576,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - sorted-array-functions@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" @@ -5695,11 +5660,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -6327,24 +6287,10 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^7.2.1, ws@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@<7.0.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== xdg-basedir@^4.0.0: version "4.0.0" diff --git a/cla-frontend-contributor-console/package.json b/cla-frontend-contributor-console/package.json index e55171d2f..e35d008de 100644 --- a/cla-frontend-contributor-console/package.json +++ b/cla-frontend-contributor-console/package.json @@ -24,6 +24,10 @@ "resolutions": { "axios": "^0.21.1", "bl": "^2.2.1", - "ini": "^1.3.7" + "braces": "^2.3.1", + "glob-parent": "^5.1.2", + "ini": "^1.3.7", + "normalize-url": "^4.5.1", + "ws": "^7.4.6" } } diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 5592afa90..3d89c7430 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -1003,11 +1003,6 @@ async-each@^1.0.0: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.0.0, async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -1223,16 +1218,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1: +braces@^1.8.2, braces@^2.3.1, braces@^3.0.1, braces@~3.0.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== @@ -1248,13 +1234,6 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -2402,13 +2381,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -2672,17 +2644,6 @@ filesize@^6.1.0: resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -2693,13 +2654,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -2973,17 +2927,10 @@ glob-base@^0.3.0: glob-parent "^2.0.0" is-glob "^2.0.0" -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +glob-parent@^2.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -3594,13 +3541,6 @@ is-natural-number@^4.0.1: resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3608,21 +3548,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - is-number@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-6.0.0.tgz#e6d15ad31fc262887cccf217ae5f9316f81b1995" integrity sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg== -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" @@ -4196,11 +4126,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4549,19 +4474,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npmlog@^4.0.1: version "4.1.2" @@ -4935,11 +4851,6 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - prettyoutput@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/prettyoutput/-/prettyoutput-1.2.0.tgz#fef93f2a79c032880cddfb84308e2137e3674b22" @@ -5139,15 +5050,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -5182,15 +5084,6 @@ ramda@^0.27.1: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - randomstring@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.1.5.tgz#6df0628f75cbd5932930d9fe3ab4e956a18518c3" @@ -5365,7 +5258,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -5921,13 +5814,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -6045,11 +5931,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-template@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" @@ -6368,13 +6249,6 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -6496,11 +6370,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6760,33 +6629,10 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^3.2.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - -ws@^7.2.1, ws@^7.3.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.1.tgz#a333be02696bd0e54cea0434e21dcc8a9ac294bb" - integrity sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@<7.0.0, ws@^3.2.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== xml2js@0.4.19: version "0.4.19" diff --git a/cla-frontend-corporate-console/package.json b/cla-frontend-corporate-console/package.json index ff305e0af..6ee28808e 100644 --- a/cla-frontend-corporate-console/package.json +++ b/cla-frontend-corporate-console/package.json @@ -24,10 +24,14 @@ "resolutions": { "axios": "^0.21.1", "bl": "^1.2.3", + "braces": "^2.3.1", "http-proxy": "^1.18.1", "ini": "^1.3.7", + "glob-parent": "^5.1.2", "kind-of": "^6.0.3", "lodash": "^4.17.19", - "minimist": "^1.2.3" + "minimist": "^1.2.3", + "normalize-url": "^4.5.1", + "ws": "^7.4.6" } } diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index bd7cfae77..31ab3dc1e 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -1003,11 +1003,6 @@ async-each@^1.0.0: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.0.0, async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -1218,16 +1213,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1: +braces@^1.8.2, braces@^2.3.1, braces@^3.0.1, braces@~3.0.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== @@ -1243,13 +1229,6 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -2389,13 +2368,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -2659,17 +2631,6 @@ filesize@^6.1.0: resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -2680,13 +2641,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -2960,17 +2914,10 @@ glob-base@^0.3.0: glob-parent "^2.0.0" is-glob "^2.0.0" -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +glob-parent@^2.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -3581,13 +3528,6 @@ is-natural-number@^4.0.1: resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3595,21 +3535,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - is-number@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-6.0.0.tgz#e6d15ad31fc262887cccf217ae5f9316f81b1995" integrity sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg== -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" @@ -4164,11 +4094,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4512,19 +4437,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npmlog@^4.0.1: version "4.1.2" @@ -4898,11 +4814,6 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - prettyoutput@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/prettyoutput/-/prettyoutput-1.2.0.tgz#fef93f2a79c032880cddfb84308e2137e3674b22" @@ -5102,15 +5013,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -5145,15 +5047,6 @@ ramda@^0.27.1: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - randomstring@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.1.5.tgz#6df0628f75cbd5932930d9fe3ab4e956a18518c3" @@ -5328,7 +5221,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -5884,13 +5777,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -6008,11 +5894,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-template@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" @@ -6331,13 +6212,6 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -6454,11 +6328,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6718,33 +6587,10 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^3.2.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - -ws@^7.2.1, ws@^7.3.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.1.tgz#a333be02696bd0e54cea0434e21dcc8a9ac294bb" - integrity sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@<7.0.0, ws@^3.2.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== xml2js@0.4.19: version "0.4.19" diff --git a/cla-frontend-project-console/package.json b/cla-frontend-project-console/package.json index f2d40b656..4668fa626 100644 --- a/cla-frontend-project-console/package.json +++ b/cla-frontend-project-console/package.json @@ -24,9 +24,13 @@ "resolutions": { "axios": "^0.21.1", "bl": "^1.2.3", + "braces": "^2.3.1", + "glob-parent": "^5.1.2", "http-proxy": "^1.18.1", "ini": "^1.3.7", "kind-of": "^6.0.3", - "minimist": "^1.2.3" + "minimist": "^1.2.3", + "normalize-url": "^4.5.1", + "ws": "^7.4.6" } } diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index e032b9b67..8eb5f6e8a 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -987,11 +987,6 @@ async-each@^1.0.0: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" - integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== - async@^2.0.0: version "2.6.2" resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" @@ -1214,16 +1209,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1: +braces@^1.8.2, braces@^2.3.1, braces@^3.0.1, braces@~3.0.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== @@ -1239,13 +1225,6 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -2352,13 +2331,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -2617,17 +2589,6 @@ filesize@^6.1.0: resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -2638,13 +2599,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -2909,17 +2863,10 @@ glob-base@^0.3.0: glob-parent "^2.0.0" is-glob "^2.0.0" -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +glob-parent@^2.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -3538,13 +3485,6 @@ is-natural-number@^4.0.1: resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3552,21 +3492,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - is-number@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-6.0.0.tgz#e6d15ad31fc262887cccf217ae5f9316f81b1995" integrity sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg== -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" @@ -4106,11 +4036,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4504,19 +4429,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npm-bundled@^1.0.1: version "1.0.6" @@ -4869,11 +4785,6 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - prettyoutput@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/prettyoutput/-/prettyoutput-1.2.0.tgz#fef93f2a79c032880cddfb84308e2137e3674b22" @@ -5049,15 +4960,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -5092,15 +4994,6 @@ ramda@^0.27.1: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - randomstring@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.1.5.tgz#6df0628f75cbd5932930d9fe3ab4e956a18518c3" @@ -5268,7 +5161,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -5819,13 +5712,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-map-resolve@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" @@ -5943,11 +5829,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-template@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" @@ -6261,13 +6142,6 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -6377,11 +6251,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6636,33 +6505,10 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^3.2.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - -ws@^7.2.1, ws@^7.3.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.1.tgz#a333be02696bd0e54cea0434e21dcc8a9ac294bb" - integrity sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@<7.0.0, ws@^3.2.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== xml2js@0.4.19: version "0.4.19" diff --git a/package.json b/package.json index 6c2600db9..b94106b1b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ }, "resolutions": { "axios": "^0.21.1", - "ini": "^1.3.7" + "ini": "^1.3.7", + "normalize-url": "^4.5.1", + "ws": "^7.4.6" } } diff --git a/yarn.lock b/yarn.lock index 7259e5313..621ce34ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -785,11 +785,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -1484,11 +1479,6 @@ debug@~3.1.0: dependencies: ms "2.0.0" -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" @@ -3344,19 +3334,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npm-run-path@^2.0.0: version "2.0.2" @@ -3806,15 +3787,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -4293,13 +4265,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-map-support@^0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -4402,11 +4367,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4935,24 +4895,10 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^7.2.1, ws@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@<7.0.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== xml2js@0.4.19: version "0.4.19" From 1418a128635a9731ad5dbcae91d220f88eb9c3f3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Jul 2021 10:28:05 -0500 Subject: [PATCH 0357/1276] More NodeJS Library Updates (#3039) --- cla-frontend-contributor-console/package.json | 5 ++++- cla-frontend-contributor-console/yarn.lock | 21 ++++++++++++------- cla-frontend-corporate-console/package.json | 4 +++- cla-frontend-corporate-console/yarn.lock | 16 +++++++------- cla-frontend-project-console/package.json | 4 +++- cla-frontend-project-console/yarn.lock | 16 +++++++------- package.json | 4 +++- yarn.lock | 13 ++++++++---- 8 files changed, 51 insertions(+), 32 deletions(-) diff --git a/cla-frontend-contributor-console/package.json b/cla-frontend-contributor-console/package.json index e35d008de..d991e9cae 100644 --- a/cla-frontend-contributor-console/package.json +++ b/cla-frontend-contributor-console/package.json @@ -27,7 +27,10 @@ "braces": "^2.3.1", "glob-parent": "^5.1.2", "ini": "^1.3.7", + "netmask": "^2.0.1", "normalize-url": "^4.5.1", - "ws": "^7.4.6" + "trim-newlines": "^3.0.1", + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.2" } } diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 3d89c7430..561c1188b 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -4418,10 +4418,10 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== -netmask@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= +netmask@^1.0.6, netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== next-tick@1, next-tick@^1.0.0: version "1.1.0" @@ -6289,6 +6289,11 @@ traverse@^0.6.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= +trim-newlines@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + trim-repeated@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" @@ -6647,10 +6652,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xregexp@2.0.0: version "2.0.0" diff --git a/cla-frontend-corporate-console/package.json b/cla-frontend-corporate-console/package.json index 6ee28808e..0c4c4f9d5 100644 --- a/cla-frontend-corporate-console/package.json +++ b/cla-frontend-corporate-console/package.json @@ -30,8 +30,10 @@ "glob-parent": "^5.1.2", "kind-of": "^6.0.3", "lodash": "^4.17.19", + "netmask": "^2.0.1", "minimist": "^1.2.3", "normalize-url": "^4.5.1", - "ws": "^7.4.6" + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.2" } } diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 31ab3dc1e..8064dde49 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -4381,10 +4381,10 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== -netmask@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= +netmask@^1.0.6, netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== next-tick@1, next-tick@^1.0.0: version "1.1.0" @@ -6605,10 +6605,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xregexp@2.0.0: version "2.0.0" diff --git a/cla-frontend-project-console/package.json b/cla-frontend-project-console/package.json index 4668fa626..63d5eca0b 100644 --- a/cla-frontend-project-console/package.json +++ b/cla-frontend-project-console/package.json @@ -30,7 +30,9 @@ "ini": "^1.3.7", "kind-of": "^6.0.3", "minimist": "^1.2.3", + "netmask": "^2.0.1", "normalize-url": "^4.5.1", - "ws": "^7.4.6" + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.2" } } diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index 8eb5f6e8a..c58c94dd5 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -4349,10 +4349,10 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== -netmask@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= +netmask@^1.0.6, netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== next-tick@1, next-tick@^1.0.0: version "1.1.0" @@ -6523,10 +6523,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xtend@^4.0.0, xtend@~4.0.0: version "4.0.2" diff --git a/package.json b/package.json index b94106b1b..fda74960c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "resolutions": { "axios": "^0.21.1", "ini": "^1.3.7", + "netmask": "^2.0.1", "normalize-url": "^4.5.1", - "ws": "^7.4.6" + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.2" } } diff --git a/yarn.lock b/yarn.lock index 621ce34ee..97d085948 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3280,6 +3280,11 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== +netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== + next-tick@1, next-tick@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" @@ -4913,10 +4918,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xtend@^4.0.0: version "4.0.2" From ccdd9a63262cfa4700ed6f596c221af610b4e8bd Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Jul 2021 12:23:58 -0500 Subject: [PATCH 0358/1276] Cleaned up Event Log Parent Project Lookup (#3040) - Cleaned up event log parent project lookup logic - Updated backend node lib vulnerability - xmlhttprequest-ssl - CVE-2021-31597 critical severity Vulnerable versions: < 1.6.1 Patched version: 1.6.1 The xmlhttprequest-ssl package before 1.6.1 for Node.js disables SSL certificate validation by default, because rejectUnauthorized (when the property exists but is undefined) is considered to be false within the https.request function of Node.js. In other words, no certificate is ever rejected. - CVE-2020-28502 high severity Vulnerable versions: < 1.6.2 Patched version: 1.6.2 This affects the package xmlhttprequest before 1.7.0; all versions of package xmlhttprequest-ssl. Provided requests are sent synchronously (async=False on xhr.open), malicious user input flowing into xhr.send could result in arbitrary code being injected and run. - updated contributor console lib vuls: - ws - glob-parent - minimist - braces - trim-newlines Signed-off-by: David Deal --- cla-backend-go/events/service.go | 17 +-- cla-backend-go/package.json | 3 +- cla-backend-go/yarn.lock | 8 +- cla-backend/package.json | 3 +- cla-backend/yarn.lock | 8 +- .../src/package.json | 27 ++-- .../src/yarn.lock | 116 ++++-------------- 7 files changed, 58 insertions(+), 124 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index f3e9241aa..78cff591f 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -255,13 +255,14 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { args.ProjectName = project.Name // Try to load and set the parent information - if project.Foundation != nil && project.Foundation.ID != "" { - log.WithFields(f).Debugf("loading salesforce project parent by ID: %s...", project.Foundation.ID) + if utils.IsProjectHaveParent(project) { + log.WithFields(f).Debugf("loading project parent by ID: %s...", project.Foundation.ID) parentProject, parentProjectErr := project_service.GetClient().GetProject(project.Foundation.ID) if parentProjectErr != nil || parentProject == nil { - log.WithFields(f).Warnf("failed to load salesforce project parent by ID: %s", project.Foundation.ID) + log.WithFields(f).Warnf("failed to load project parent by ID: %s", project.Foundation.ID) return nil } + var parentProjectName, parentProjectID string if !utils.IsProjectHasRootParent(project) { parentProjectName = parentProject.Name @@ -270,14 +271,14 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { parentProjectName = project.Name parentProjectID = project.ID } - log.WithFields(f).Debugf("loaded salesforce project by parent ID: %s - resulting in ID: %s with name: %s", + log.WithFields(f).Debugf("loaded project by parent ID: %s - resulting in ID: %s with name: %s", project.Foundation.ID, parentProjectID, parentProjectName) args.ParentProjectSFID = parentProjectID args.ParentProjectName = parentProjectName - } else if project.Foundation != nil { - // if there's no parent set the foundation as parent project - args.ParentProjectSFID = project.Foundation.ID - args.ParentProjectName = project.Foundation.Name + } else { + // No parent, just use the current project as the parent + args.ParentProjectSFID = project.ID + args.ParentProjectName = project.Name } } else { log.WithFields(f).Warnf("project sfid %s was not set properly can't set parent project fields in event", args.ProjectSFID) diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index d58f86e95..53989b91e 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -30,6 +30,7 @@ "axios": "^0.21.1", "ini": "^1.3.7", "normalize-url": "^4.5.1", - "ws": "^7.4.6" + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.2" } } diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index ffa8b1db2..d9354dd2e 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -5132,10 +5132,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xtend@^4.0.0: version "4.0.2" diff --git a/cla-backend/package.json b/cla-backend/package.json index fb13cec78..2a7ca59bf 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -49,6 +49,7 @@ "ini": "^1.3.7", "node-fetch": "^2.6.1", "normalize-url": "^4.5.1", - "ws": "^7.4.6" + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.2" } } diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 6a2f8a6dc..19bc7ae7d 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -6310,10 +6310,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xtend@^4.0.0: version "4.0.2" diff --git a/cla-frontend-contributor-console/src/package.json b/cla-frontend-contributor-console/src/package.json index a51c8c7d1..b8d825f9f 100644 --- a/cla-frontend-contributor-console/src/package.json +++ b/cla-frontend-contributor-console/src/package.json @@ -1,5 +1,6 @@ { "name": "cla-frontend-contributor-console", + "description": "cla-contributor-console", "license": "MIT", "author": "The Linux Foundation", "private": true, @@ -58,16 +59,6 @@ "@ionic/app-scripts": "3.2.0", "typescript": "3.0.1" }, - "resolutions": { - "cryptiles": "^4.1.2", - "hoek": "^4.2.1", - "ini": "^1.3.7", - "kind-of": "^6.0.3", - "mem": "^4.0.0", - "node-sass": "4.13.1", - "tunnel-agent": "^0.6.0", - "yargs-parser": "13.1.2" - }, "cordovaPlugins": [ "cordova-plugin-whitelist", "cordova-plugin-statusbar", @@ -87,5 +78,19 @@ "config": { "ionic_src_dir": "ionic" }, - "description": "cla-contributor-console" + "resolutions": { + "braces": "^2.3.1", + "cryptiles": "^4.1.2", + "hoek": "^4.2.1", + "glob-parent": "^5.1.2", + "ini": "^1.3.7", + "kind-of": "^6.0.3", + "mem": "^4.0.0", + "minimist": "^0.2.1", + "node-sass": "4.13.1", + "trim-newlines": "^3.0.1", + "tunnel-agent": "^0.6.0", + "ws": "^5.2.3", + "yargs-parser": "13.1.2" + } } diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 1d91d9e19..f40734a66 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -516,15 +516,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.1, braces@^2.3.2: +braces@^1.8.2, braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: @@ -1378,12 +1370,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - dependencies: - fill-range "^2.1.0" - express@^4.16.3: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -1504,16 +1490,6 @@ filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -1682,18 +1658,12 @@ glob-base@^0.3.0: glob-parent "^2.0.0" is-glob "^2.0.0" -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" +glob-parent@^2.0.0, glob-parent@^3.1.0, glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" + is-glob "^4.0.1" glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: version "7.1.4" @@ -2033,7 +2003,7 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2059,15 +2029,10 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: +is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" @@ -2075,22 +2040,12 @@ is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - dependencies: - kind-of "^3.0.2" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" dependencies: kind-of "^3.0.2" -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -2381,10 +2336,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -math-random@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -2514,15 +2465,10 @@ minimatch@^3.0.4, minimatch@~3.0.2: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.3, minimist@^1.2.0: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@0.0.8, minimist@^0.2.1, minimist@^1.1.3, minimist@^1.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.1.tgz#827ba4e7593464e7c221e8c5bed930904ee2c455" + integrity sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg== minipass@^2.2.1, minipass@^2.3.5: version "2.3.5" @@ -2938,10 +2884,6 @@ path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -3032,10 +2974,6 @@ postcss@^6.0.17, postcss@^6.0.21: source-map "^0.6.1" supports-color "^5.4.0" -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -3139,14 +3077,6 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== -randomatic@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -3934,9 +3864,10 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" +trim-newlines@^1.0.0, trim-newlines@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== "true-case-path@^1.0.2": version "1.0.3" @@ -4056,10 +3987,6 @@ uglifyjs-webpack-plugin@^0.4.6: uglify-js "^2.8.29" webpack-sources "^1.0.1" -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - unfetch@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db" @@ -4298,13 +4225,12 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -ws@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.2.tgz#96c1d08b3fefda1d5c1e33700d3bfaa9be2d5608" +ws@3.3.2, ws@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" + integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" xml2js@0.4.19, xml2js@^0.4.19: version "0.4.19" From 2cf493fdc991ac10f3289f8f21e4a83c81f1304f Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Jul 2021 16:32:43 -0500 Subject: [PATCH 0359/1276] Resolved Project Service Parent Nil Pointer Issue (#3043) --- cla-backend-go/v2/project-service/client.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 55b054923..b968d05ec 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -173,8 +173,12 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut if exists { log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) + if !utils.IsProjectHaveParent(existingModel) { + return nil, nil + } + // Does this project they have a parent? projectModel.Parent is deprecated and no longer returned, use project.Foundation.ID/Name attribute instead - if existingModel.Foundation != nil && existingModel.Foundation.ID != "" && (existingModel.Foundation.Name == utils.TheLinuxFoundation || existingModel.Foundation.Name == utils.LFProjectsLLC) { + if existingModel.Foundation.Name == utils.TheLinuxFoundation || existingModel.Foundation.Name == utils.LFProjectsLLC { log.WithFields(f).Debugf("no parent for projectSFID %s or %s or %s is the parent...", projectSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC) return nil, nil } @@ -217,12 +221,12 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) // No parent - if projectModel.Foundation == nil || projectModel.Foundation.ID == "" { + if !utils.IsProjectHaveParent(existingModel) { return nil, nil } // Do they have a parent? projectModel.Parent is deprecated and no longer returned - if projectModel.Foundation != nil && projectModel.Foundation.ID != "" && (projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC) { + if projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return nil, nil } From 87d9a00df3d42eb5aa34cb4776bc08e58de68384 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Jul 2021 17:24:15 -0500 Subject: [PATCH 0360/1276] Cleaned up Enroll/Unenroll Issue (#3044) Signed-off-by: David Deal --- cla-backend-go/events/service.go | 12 ++++++------ cla-backend-go/v2/cla_groups/handlers.go | 14 +++++++------- cla-backend-go/v2/cla_groups/helpers.go | 6 +++--- cla-backend-go/v2/cla_groups/service.go | 4 ++-- cla-backend-go/v2/project-service/client.go | 6 +++--- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 78cff591f..eb0f2e974 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -256,17 +256,17 @@ func (s *service) loadSFProject(ctx context.Context, args *LogEventArgs) error { // Try to load and set the parent information if utils.IsProjectHaveParent(project) { - log.WithFields(f).Debugf("loading project parent by ID: %s...", project.Foundation.ID) - parentProject, parentProjectErr := project_service.GetClient().GetProject(project.Foundation.ID) - if parentProjectErr != nil || parentProject == nil { - log.WithFields(f).Warnf("failed to load project parent by ID: %s", project.Foundation.ID) + log.WithFields(f).Debugf("loading project parent by ID: %s...", utils.GetProjectParentSFID(project)) + parentProjectModel, parentProjectErr := project_service.GetClient().GetParentProjectModel(project.ID) + if parentProjectErr != nil || parentProjectModel == nil { + log.WithFields(f).Warnf("failed to load project parent by ID: %s", utils.GetProjectParentSFID(project)) return nil } var parentProjectName, parentProjectID string if !utils.IsProjectHasRootParent(project) { - parentProjectName = parentProject.Name - parentProjectID = parentProject.ID + parentProjectName = parentProjectModel.Name + parentProjectID = parentProjectModel.ID } else { parentProjectName = project.Name parentProjectID = project.ID diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index bca28d288..ec2b124a0 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -270,7 +270,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P "projectSFIDList": strings.Join(params.ProjectSFIDList, ","), } - cg, getCLAGroupErr := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, getCLAGroupErr := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) if getCLAGroupErr != nil { if _, ok := getCLAGroupErr.(*utils.CLAGroupNotFound); ok { return cla_group.NewEnrollProjectsNotFound().WithXRequestID(reqID).WithPayload( @@ -285,14 +285,14 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, cg.FoundationSFID, []string{cg.ProjectExternalID}, projectClaGroupsRepo) { - msg := fmt.Sprintf("user %s does not have access to enroll projects with project scope of: %s", authUser.UserName, cg.FoundationSFID) + if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, []string{claGroupModel.ProjectExternalID}, projectClaGroupsRepo) { + msg := fmt.Sprintf("user %s does not have access to enroll projects with project scope of: %s", authUser.UserName, claGroupModel.FoundationSFID) log.WithFields(f).Warn(msg) return cla_group.NewEnrollProjectsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - if !cg.FoundationLevelCLA { - log.WithFields(f).Debug("locating project by sfid...") + if !claGroupModel.FoundationLevelCLA { + log.WithFields(f).Debug("cla group is not a foundation level CLA group - locating project by sfid...") psc := v2ProjectService.GetClient() for _, projectSFID := range params.ProjectSFIDList { project, projectErr := psc.GetProject(projectSFID) @@ -321,16 +321,16 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } } - } // Enroll the project(s) into the CLA Group enrollCLAGroupErr := service.EnrollProjectsInClaGroup(ctx, &EnrollProjectsModel{ AuthUser: authUser, CLAGroupID: params.ClaGroupID, - FoundationSFID: cg.FoundationSFID, + FoundationSFID: claGroupModel.FoundationSFID, ProjectSFIDList: params.ProjectSFIDList, }) + if enrollCLAGroupErr != nil { if strings.Contains(enrollCLAGroupErr.Error(), "bad request") { return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index 3fc5916d5..a18dfdf2d 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -161,9 +161,9 @@ func (s *service) validateClaGroupInput(ctx context.Context, input *models.Creat log.WithFields(f).Debugf("looking up LF parent project record...") isLFParent := false if utils.IsProjectHaveParent(foundationProjectDetails) { - isLFParent, err = psc.IsTheLinuxFoundation(foundationProjectDetails.Foundation.ID) + isLFParent, err = psc.IsTheLinuxFoundation(utils.GetProjectParentSFID(foundationProjectDetails)) if err != nil { - log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup parent project by SFID: %s", foundationProjectDetails.Foundation.ID) + log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup parent project by SFID: %s", utils.GetProjectParentSFID(foundationProjectDetails)) return false, err } } @@ -361,7 +361,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS if !isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup) { msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", - foundationProjectDetails.Foundation.ID, foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) + utils.GetProjectParentSFID(foundationProjectDetails), foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) log.WithFields(f).Warnf(msg) return fmt.Errorf(msg) } diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 3ffaffd02..d0617159b 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -1027,7 +1027,7 @@ func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollP return nil } -//func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, claGroupID string, foundationSFID string, projectSFIDList []string) error { +// UnenrollProjectsInClaGroup un-enrolls the specified projects from the CLA group func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *UnenrollProjectsModel) error { f := logrus.Fields{ "functionName": "v2.cla_groups.service.UnenrollProjectsInClaGroup", @@ -1051,7 +1051,7 @@ func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *Unenr var wg sync.WaitGroup wg.Add(2) - // Separate go routine for unenrolling projects + // Separate go routine for un-enrolling projects go func(c context.Context, authUser *auth.User, claGroupID string, foundationSFID string, projSFIDList []string) { defer wg.Done() log.WithFields(f).Debug("unenrolling projects in CLA Group") diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index b968d05ec..662ac4798 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -147,12 +147,12 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) return projectSFID, err } - if parentModel == nil || parentModel.Foundation == nil || parentModel.Foundation.ID == "" { + if parentModel == nil { log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) return projectSFID, err } - return parentModel.Foundation.ID, nil + return parentModel.ID, nil } // GetParentProjectModel returns the parent project model if there is a parent, otherwise returns nil @@ -293,7 +293,7 @@ func (pmm *Client) IsParentTheLinuxFoundation(projectSFID string) (bool, error) return false, err } - if projectModel.Foundation == nil || projectModel.Foundation.ID == "" { + if !utils.IsProjectHaveParent(projectModel) { return false, nil } From b0912b3edb976404d67f39cf569622386fb4fa4e Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 21 Jul 2021 22:31:16 -0500 Subject: [PATCH 0361/1276] Cleaned up Enroll/Unenroll Issue (#3045) Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/handlers.go | 8 +++--- cla-backend-go/v2/cla_groups/helpers.go | 31 ++++----------------- cla-backend-go/v2/project-service/client.go | 23 ++++++++++++--- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index ec2b124a0..a2a2d7e2f 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -356,7 +356,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P "projectSFIDList": strings.Join(params.ProjectSFIDList, ","), } - cg, err := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) + claGroupModel, err := v1ProjectService.GetCLAGroupByID(ctx, params.ClaGroupID) if err != nil { if err, ok := err.(*utils.CLAGroupNotFound); ok { return cla_group.NewUnenrollProjectsNotFound().WithXRequestID(reqID).WithPayload( @@ -371,8 +371,8 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P } // Check permissions - if !isUserHaveAccessToCLAProject(ctx, authUser, cg.FoundationSFID, []string{cg.ProjectExternalID}, projectClaGroupsRepo) { - msg := fmt.Sprintf("user %s does not have access to unenroll projects with project scope of: %s", authUser.UserName, cg.FoundationSFID) + if !isUserHaveAccessToCLAProject(ctx, authUser, claGroupModel.FoundationSFID, []string{claGroupModel.ProjectExternalID}, projectClaGroupsRepo) { + msg := fmt.Sprintf("user %s does not have access to unenroll projects with project scope of: %s", authUser.UserName, claGroupModel.FoundationSFID) log.WithFields(f).Warn(msg) return cla_group.NewUnenrollProjectsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -380,7 +380,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P err = service.UnenrollProjectsInClaGroup(ctx, &UnenrollProjectsModel{ AuthUser: authUser, CLAGroupID: params.ClaGroupID, - FoundationSFID: cg.FoundationSFID, + FoundationSFID: claGroupModel.FoundationSFID, ProjectSFIDList: params.ProjectSFIDList, }) if err != nil { diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index a18dfdf2d..b62e62536 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -315,12 +315,6 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS log.WithFields(f).Warn("validation failure - there should be at least one subproject associated...") return fmt.Errorf("bad request: there should be at least one subproject associated") } - // Comment out the below as we want to support project-level projects - /* log.WithFields(f).Debug("checking to see if foundation is in project list...") - if !isFoundationIDInList(foundationSFID, projectSFIDList) { - log.WithFields(f).Warn("validation failure - unable to unenroll Project Group from CLA Group") - return fmt.Errorf("bad request: unable to unenroll Project Group from CLA Group") - } */ // fetch the foundation model details from the platform project service which includes a list of its sub projects foundationProjectDetails, err := psc.GetProject(foundationSFID) @@ -339,32 +333,19 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) } + // Combine all the projectSFID values and check to see if any are the project root - shouldn't be in the list + if psc.IsAnyProjectTheRootParent(append(projectSFIDList, foundationSFID)) { + return errors.New("validation failure - one of the input projects is the root Linux Foundation project") + } + // build Tree that tracks parent and child projects projectTree := buildProjectNode(foundationProjectSummary) - // Is our parent the LF project? - log.WithFields(f).Debugf("looking up LF parent project record...") - isLFParent := false - if utils.IsProjectHaveParent(foundationProjectDetails) { - isLFParent, err = psc.IsTheLinuxFoundation(foundationProjectDetails.Foundation.ID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("validation failure - unable to lookup parent project by SFID: %s", foundationProjectDetails.Foundation.ID) - return err - } - } - for _, projectSFID := range projectSFIDList { - projectDetails, projErr := psc.GetProject(projectSFID) + _, projErr := psc.GetProject(projectSFID) if projErr != nil { return err } - - if !isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup) { - msg := fmt.Sprintf("input validation failure - foundationSFID: %s , foundationType: %s , projectSFID: %s , projectType: %s ", - utils.GetProjectParentSFID(foundationProjectDetails), foundationProjectDetails.ProjectType, projectSFID, projectDetails.ProjectType) - log.WithFields(f).Warnf(msg) - return fmt.Errorf(msg) - } } // Check to see if all the provided enrolled projects are part of this foundation diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 662ac4798..ee3c5d353 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -184,7 +184,7 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut } // Grab the parent ID once - projectParentSFID := existingModel.Foundation.ID + projectParentSFID := utils.GetProjectParentSFID(existingModel) // Parent SFID in the cache? existingParentModel, exists = projectServiceModels[projectParentSFID] @@ -221,7 +221,7 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) // No parent - if !utils.IsProjectHaveParent(existingModel) { + if !utils.IsProjectHaveParent(projectModel) { return nil, nil } @@ -232,7 +232,7 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut } // Grab the parent ID once - projectParentSFID := projectModel.Foundation.ID + projectParentSFID := utils.GetProjectParentSFID(projectModel) // Parent in the cache? existingParentModel, exists = projectServiceModels[projectParentSFID] @@ -262,7 +262,6 @@ func (pmm *Client) IsTheLinuxFoundation(projectSFID string) (bool, error) { "apiGWHost": apiGWHost, } - log.WithFields(f).Debug("querying project...") projectModel, err := pmm.GetProject(projectSFID) if err != nil { log.WithFields(f).Warnf("unable to lookup project by ID: %s error: %+v", projectSFID, err) @@ -465,3 +464,19 @@ func (pmm *Client) GetSummary(ctx context.Context, projectSFID string) ([]*model return result.Payload.Data, nil } + +// IsAnyProjectTheRootParent returns true if one or more of the project ID's in the list is one of the root parents, returns false otherwise +func (pmm *Client) IsAnyProjectTheRootParent(sliceProjectSFID []string) bool { + var retVal bool + + // Check each project to see if it is one of the root parents + for _, projectSFID := range sliceProjectSFID { + // If so, return true, we're done + if isTLF, err := pmm.IsTheLinuxFoundation(projectSFID); isTLF && err == nil { + retVal = isTLF + break + } + } + + return retVal +} From 8854480b7808eecc200ad031b383fb7310164a1e Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 22 Jul 2021 13:59:39 -0500 Subject: [PATCH 0362/1276] Added Logic to Prevent Unenroll of All Projects Under a CLA Group (#3046) Signed-off-by: David Deal --- .circleci/config.yml | 16 ++--------- cla-backend-go/events/service.go | 7 ----- cla-backend-go/v2/cla_groups/helpers.go | 38 ++++++++++++++++++------- cla-backend-go/v2/cla_groups/service.go | 1 - 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 93a29ae55..3ffb4c24c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -242,22 +242,10 @@ jobs: name: Build command: | cd cla-backend-go - echo "Building AWS Lambda - API..." - make build-aws-lambda-linux - echo "Building AWS Metrics Lambda..." - make build-metrics-lambda-linux - echo "Building AWS Metrics Report Lambda..." - make build-metrics-report-lambda - echo "Building AWS Lambda - DynamoDB Events Handler..." - make build-dynamo-events-lambda-linux - echo "Building AWS Lambda - Zip Builder Scheduler..." - make build-zipbuilder-scheduler-lambda-linux - echo "Building AWS Lambda - Zip Builder Handler..." - make build-zipbuilder-lambda-linux + echo "Building Lambdas..." + make build-lambdas-linux echo "Building Functional Tests..." make build-functional-tests-linux - echo "Building User Subscribe..." - make build-user-subscribe-lambda-linux - run: name: Test command: | diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index eb0f2e974..7274fe8e0 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -372,45 +372,38 @@ func (s *service) loadDetails(ctx context.Context, args *LogEventArgs) error { f := logrus.Fields{ "functionName": "v1.events.service.loadDetails", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "args": fmt.Sprintf("%+v", args), } - log.WithFields(f).Debug("loading company details...") err := s.loadCompany(ctx, args) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load company details...") return err } - log.WithFields(f).Debug("loading SF project details...") err = s.loadSFProject(ctx, args) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load SF project details...") return err } - log.WithFields(f).Debug("loading CLA Group details...") err = s.loadCLAGroup(ctx, args) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load CLA Group details...") return err } - log.WithFields(f).Debug("loading LF user details ...") err = s.loadLFUser(ctx, args) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load LF User details...") return err } - log.WithFields(f).Debug("loading user details...") err = s.loadUser(ctx, args) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load user details...") return err } - log.WithFields(f).Debug("loading LF user details...") err = s.loadLFUser(ctx, args) if err != nil { log.WithFields(f).WithError(err).Warn("unable to load LF user details...") diff --git a/cla-backend-go/v2/cla_groups/helpers.go b/cla-backend-go/v2/cla_groups/helpers.go index b62e62536..fb17b75b8 100644 --- a/cla-backend-go/v2/cla_groups/helpers.go +++ b/cla-backend-go/v2/cla_groups/helpers.go @@ -214,8 +214,7 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI psc := v2ProjectService.GetClient() if len(projectSFIDList) == 0 { - log.WithFields(f).Warn("validation failure - there should be at least one subproject associated...") - return fmt.Errorf("bad request: there should be at least one subproject associated") + return errors.New("validation failure - there should be at least one project provided for the enroll request") } // fetch the foundation model details from the platform project service which includes a list of its sub projects @@ -225,18 +224,23 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI return err } if foundationProjectDetails == nil { - return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + return fmt.Errorf("validation failure - problem fetching project details for project: %s", foundationSFID) } foundationProjectSummary, err := psc.GetSummary(ctx, foundationSFID) if err != nil { - log.WithFields(f).WithError(err).Warnf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + log.WithFields(f).WithError(err).Warnf("validation failure - problem fetching project details for project: %s", foundationSFID) return err } if foundationProjectSummary == nil { return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) } + // Combine all the projectSFID values and check to see if any are the project root - shouldn't be in the list + if psc.IsAnyProjectTheRootParent(append(projectSFIDList, foundationSFID)) { + return errors.New("validation failure - one of the input projects is the root Linux Foundation project") + } + // build Tree that tracks parent and child projects projectTree := buildProjectNode(foundationProjectSummary) @@ -252,10 +256,11 @@ func (s *service) validateEnrollProjectsInput(ctx context.Context, foundationSFI } } + // Make sure each project exists in the project service for _, projectSFID := range projectSFIDList { projectDetails, projErr := psc.GetProject(projectSFID) if projErr != nil { - return err + return fmt.Errorf("validation failure - unable to lookup project by ID %s due to the error: %+v", projectSFID, err) } if projectTree != nil && projectTree.Parent != nil && (!isLFParent && (foundationProjectDetails.ProjectType == utils.ProjectTypeProjectGroup && projectDetails.ProjectType != utils.ProjectTypeProjectGroup)) { @@ -312,8 +317,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS psc := v2ProjectService.GetClient() if len(projectSFIDList) == 0 { - log.WithFields(f).Warn("validation failure - there should be at least one subproject associated...") - return fmt.Errorf("bad request: there should be at least one subproject associated") + return errors.New("validation failure - there should be at least one project provided for the unenroll request") } // fetch the foundation model details from the platform project service which includes a list of its sub projects @@ -322,7 +326,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } if foundationProjectDetails == nil { - return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + return fmt.Errorf("validation failure - problem fetching project details for project: %s", foundationSFID) } foundationProjectSummary, err := psc.GetSummary(ctx, foundationSFID) @@ -330,7 +334,7 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return err } if foundationProjectSummary == nil { - return fmt.Errorf("validation failure - problem fetching project details from project service for project: %s", foundationSFID) + return fmt.Errorf("validation failure - problem fetching project details for project: %s", foundationSFID) } // Combine all the projectSFID values and check to see if any are the project root - shouldn't be in the list @@ -338,13 +342,25 @@ func (s *service) validateUnenrollProjectsInput(ctx context.Context, foundationS return errors.New("validation failure - one of the input projects is the root Linux Foundation project") } + // Grab the existing list of project CLA Groups associated with this foundation + existingProjectCLAGroupModels, err := s.projectsClaGroupsRepo.GetProjectsIdsForFoundation(ctx, foundationSFID) + if err != nil { + return err + } + log.WithFields(f).Debugf("before unenroll, we have %d projects associated with the CLA Group - we will be removing %d and will have %d remaining.", len(existingProjectCLAGroupModels), len(projectSFIDList), len(existingProjectCLAGroupModels)-len(projectSFIDList)) + + if len(existingProjectCLAGroupModels)-len(projectSFIDList) <= 1 { + return fmt.Errorf("validation failure - must have at least one project enrolled in the CLA group under parent: %s with ID: %s", foundationProjectDetails.Name, foundationSFID) + } + // build Tree that tracks parent and child projects projectTree := buildProjectNode(foundationProjectSummary) + // Make sure each project exists in the project service for _, projectSFID := range projectSFIDList { _, projErr := psc.GetProject(projectSFID) if projErr != nil { - return err + return fmt.Errorf("validation failure - unable to lookup project by ID %s due to the error: %+v", projectSFID, err) } } @@ -567,7 +583,6 @@ func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, cl "claGroupID": claGroupID, } - log.WithFields(f).Debug("disabling CLA service in platform project service") // Run this in parallel... var errorList []error var wg sync.WaitGroup @@ -578,6 +593,7 @@ func (s *service) DisableCLAService(ctx context.Context, authUser *auth.User, cl // Execute as a go routine go func(psClient *v2ProjectService.Client, claGroupID, projectSFID string) { defer wg.Done() + log.WithFields(f).Debugf("disabling CLA service for project: %s", projectSFID) disableProjectErr := psClient.DisableCLA(ctx, projectSFID) if disableProjectErr != nil { log.WithFields(f).WithError(disableProjectErr). diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index d0617159b..53bbb9557 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -1070,7 +1070,6 @@ func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *Unenr // Separate go routine for disabling the CLA Service in the project service go func(c context.Context, claGroupID string, projSFIDList []string) { defer wg.Done() - log.WithFields(f).Debug("disabling CLA service in platform project service") // Note: log entry will be created by disable CLA Service call errDisableCLA := s.DisableCLAService(c, request.AuthUser, claGroupID, projSFIDList) if errDisableCLA != nil { From 21bed8f49605c8cc290ca7cfc00320c983914113 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 22 Jul 2021 15:39:27 -0500 Subject: [PATCH 0363/1276] Added GitLab Approval List Placeholders (#3047) --- cla-backend-go/signatures/converters.go | 8 +-- cla-backend-go/signatures/dbmodels.go | 10 ++-- cla-backend-go/signatures/handlers.go | 6 +- cla-backend-go/signatures/models.go | 8 ++- cla-backend-go/signatures/projections.go | 2 + cla-backend-go/signatures/repository.go | 55 ++++++++---------- cla-backend-go/signatures/service.go | 48 +++++++-------- cla-backend-go/v2/signatures/handlers.go | 74 ++---------------------- 8 files changed, 75 insertions(+), 136 deletions(-) diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go index f5a730b6f..e7f2d979b 100644 --- a/cla-backend-go/signatures/converters.go +++ b/cla-backend-go/signatures/converters.go @@ -81,10 +81,10 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results ProjectID: dbSignature.SignatureProjectID, Created: dbSignature.DateCreated, Modified: dbSignature.DateModified, - EmailApprovalList: dbSignature.EmailWhitelist, - DomainApprovalList: dbSignature.DomainWhitelist, - GithubUsernameApprovalList: dbSignature.GitHubWhitelist, - GithubOrgApprovalList: dbSignature.GitHubOrgWhitelist, + EmailApprovalList: dbSignature.EmailApprovalList, + DomainApprovalList: dbSignature.EmailDomainApprovalList, + GithubUsernameApprovalList: dbSignature.GitHubUsernameApprovalList, + GithubOrgApprovalList: dbSignature.GitHubOrgApprovalList, UserName: dbSignature.UserName, UserLFID: dbSignature.UserLFUsername, UserGHID: dbSignature.UserGithubUsername, diff --git a/cla-backend-go/signatures/dbmodels.go b/cla-backend-go/signatures/dbmodels.go index 021fa80c9..00056661d 100644 --- a/cla-backend-go/signatures/dbmodels.go +++ b/cla-backend-go/signatures/dbmodels.go @@ -19,10 +19,12 @@ type ItemSignature struct { SignatureReferenceType string `json:"signature_reference_type"` SignatureType string `json:"signature_type"` SignatureUserCompanyID string `json:"signature_user_ccla_company_id"` - EmailWhitelist []string `json:"email_whitelist"` - DomainWhitelist []string `json:"domain_whitelist"` - GitHubWhitelist []string `json:"github_whitelist"` - GitHubOrgWhitelist []string `json:"github_org_whitelist"` + EmailApprovalList []string `json:"email_whitelist"` + EmailDomainApprovalList []string `json:"domain_whitelist"` + GitHubUsernameApprovalList []string `json:"github_whitelist"` + GitHubOrgApprovalList []string `json:"github_org_whitelist"` + GitLabUsernameApprovalList []string `json:"gitlab_username_approval_list"` + GitLabGroupApprovalList []string `json:"gitlab_group_approval_list"` SignatureACL []string `json:"signature_acl"` UserGithubUsername string `json:"user_github_username"` UserLFUsername string `json:"user_lf_username"` diff --git a/cla-backend-go/signatures/handlers.go b/cla-backend-go/signatures/handlers.go index 700a20a44..748f7eb76 100644 --- a/cla-backend-go/signatures/handlers.go +++ b/cla-backend-go/signatures/handlers.go @@ -177,7 +177,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d githubAccessToken = "" } - ghApprovalList, err := service.GetGithubOrganizationsFromWhitelist(ctx, params.SignatureID, githubAccessToken) + ghApprovalList, err := service.GetGithubOrganizationsFromApprovalList(ctx, params.SignatureID, githubAccessToken) if err != nil { log.Warnf("error fetching github organization approval list entries v using signature_id: %s, error: %+v", params.SignatureID, err) @@ -203,7 +203,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d githubAccessToken = "" } - ghApprovalList, err := service.AddGithubOrganizationToWhitelist(ctx, params.SignatureID, params.Body, githubAccessToken) + ghApprovalList, err := service.AddGithubOrganizationToApprovalList(ctx, params.SignatureID, params.Body, githubAccessToken) if err != nil { log.Warnf("error adding github organization %s using signature_id: %s to the whitelist, error: %+v", *params.Body.OrganizationID, params.SignatureID, err) @@ -253,7 +253,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d githubAccessToken = "" } - ghApprovalList, err := service.DeleteGithubOrganizationFromWhitelist(ctx, params.SignatureID, params.Body, githubAccessToken) + ghApprovalList, err := service.DeleteGithubOrganizationFromApprovalList(ctx, params.SignatureID, params.Body, githubAccessToken) if err != nil { log.Warnf("error deleting github organization %s using signature_id: %s from the whitelist, error: %+v", *params.Body.OrganizationID, params.SignatureID, err) diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index 7476d1b79..db5a76043 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -22,7 +22,7 @@ type ApprovalCriteria struct { GitHubUsername string } -//ApprovalList ... +//ApprovalList data model type ApprovalList struct { Criteria string ApprovalList []string @@ -31,11 +31,13 @@ type ApprovalList struct { ClaGroupName string CompanyID string Version string + EmailApprovals []string DomainApprovals []string - GHOrgApprovals []string GitHubUsernameApprovals []string - EmailApprovals []string GHUsernames []string + GHOrgApprovals []string + GitLabUsernameApprovals []string + GitLabGroupApprovals []string GerritICLAECLAs []string ICLAs []*models.IclaSignature ECLAs []*models.Signature diff --git a/cla-backend-go/signatures/projections.go b/cla-backend-go/signatures/projections.go index e846ae7f1..6bd143612 100644 --- a/cla-backend-go/signatures/projections.go +++ b/cla-backend-go/signatures/projections.go @@ -28,6 +28,8 @@ func buildProjection() expression.ProjectionBuilder { expression.Name("domain_whitelist"), expression.Name("github_whitelist"), expression.Name("github_org_whitelist"), + expression.Name("gitlab_username_approval_list"), // added for GitLab support + expression.Name("gitlab_project_approval_list"), // added for GitLab support expression.Name("user_github_username"), expression.Name("user_lf_username"), expression.Name("user_name"), diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index eefa0a026..d7dafd59f 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -60,11 +60,11 @@ const ( BigPageSize = 200 ) -// SignatureRepository interface defines the functions for the github whitelist service +// SignatureRepository interface defines the functions for the the signature repository type SignatureRepository interface { - GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string) ([]models.GithubOrg, error) - AddGithubOrganizationToWhitelist(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) - DeleteGithubOrganizationFromWhitelist(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) + GetGithubOrganizationsFromApprovalList(ctx context.Context, signatureID string) ([]models.GithubOrg, error) + AddGithubOrganizationToApprovalList(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) + DeleteGithubOrganizationFromApprovalList(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) InvalidateProjectRecord(ctx context.Context, signatureID, note string) error GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) @@ -113,7 +113,7 @@ type repository struct { signatureTableName string } -// NewRepository creates a new instance of the whitelist service +// NewRepository creates a new instance of the signature repository service func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.RepositoryInterface, gerritService gerrits.Service) SignatureRepository { return repository{ stage: stage, @@ -128,10 +128,10 @@ func NewRepository(awsSession *session.Session, stage string, companyRepo compan } } -// GetGithubOrganizationsFromWhitelist returns a list of GH organizations stored in the whitelist -func (repo repository) GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string) ([]models.GithubOrg, error) { +// GetGithubOrganizationsFromApprovalList returns a list of GH organizations stored in the approval list +func (repo repository) GetGithubOrganizationsFromApprovalList(ctx context.Context, signatureID string) ([]models.GithubOrg, error) { f := logrus.Fields{ - "functionName": "v1.signatures.repository.GetGitHubOrganizationsFromWhitelist", + "functionName": "v1.signatures.repository.GetGithubOrganizationsFromApprovalList", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, } @@ -146,7 +146,7 @@ func (repo repository) GetGithubOrganizationsFromWhitelist(ctx context.Context, }) if err != nil { - log.WithFields(f).Warnf("Error retrieving GH organization whitelist for signatureID: %s, error: %v", signatureID, err) + log.WithFields(f).Warnf("Error retrieving GH organization approval list for signatureID: %s, error: %v", signatureID, err) return nil, err } @@ -172,16 +172,16 @@ func (repo repository) GetGithubOrganizationsFromWhitelist(ctx context.Context, return orgs, nil } -// AddGithubOrganizationToWhitelist adds the specified GH organization to the whitelist -func (repo repository) AddGithubOrganizationToWhitelist(ctx context.Context, signatureID, GitHubOrganizationID string) ([]models.GithubOrg, error) { +// AddGithubOrganizationToApprovalList adds the specified GH organization to the approval list +func (repo repository) AddGithubOrganizationToApprovalList(ctx context.Context, signatureID, GitHubOrganizationID string) ([]models.GithubOrg, error) { f := logrus.Fields{ - "functionName": "v1.signatures.repository.AddGitHubOrganizationToWhitelist", + "functionName": "v1.signatures.repository.AddGithubOrganizationToApprovalList", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "GitHubOrganizationID": GitHubOrganizationID, } // get item from dynamoDB table - log.WithFields(f).Debugf("querying database for GitHub organization whitelist using signatureID: %s", signatureID) + log.WithFields(f).Debugf("querying database for GitHub organization approval list using signatureID: %s", signatureID) result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ TableName: aws.String(repo.signatureTableName), @@ -193,7 +193,7 @@ func (repo repository) AddGithubOrganizationToWhitelist(ctx context.Context, sig }) if err != nil { - log.WithFields(f).Warnf("Error retrieving GitHub organization whitelist for signatureID: %s and GH Org: %s, error: %v", + log.WithFields(f).Warnf("Error retrieving GitHub organization approval list for signatureID: %s and GH Org: %s, error: %v", signatureID, GitHubOrganizationID, err) return nil, err } @@ -255,7 +255,7 @@ func (repo repository) AddGithubOrganizationToWhitelist(ctx context.Context, sig updatedItemFromMap, ok := updatedValues.Attributes["github_org_whitelist"] if !ok { - msg := fmt.Sprintf("unable to fetch updated whitelist organization values for "+ + msg := fmt.Sprintf("unable to fetch updated github organization approval list values for "+ "organization id: %s for signature: %s - list is empty - returning empty list", GitHubOrganizationID, signatureID) log.WithFields(f).Debugf(msg) @@ -265,10 +265,10 @@ func (repo repository) AddGithubOrganizationToWhitelist(ctx context.Context, sig return buildResponse(updatedItemFromMap.L), nil } -// DeleteGithubOrganizationFromWhitelist removes the specified GH organization from the whitelist -func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context, signatureID, GitHubOrganizationID string) ([]models.GithubOrg, error) { +// DeleteGithubOrganizationFromApprovalList removes the specified GH organization from the approval list +func (repo repository) DeleteGithubOrganizationFromApprovalList(ctx context.Context, signatureID, GitHubOrganizationID string) ([]models.GithubOrg, error) { f := logrus.Fields{ - "functionName": "v1.signatures.repository.DeleteGitHubOrganizationFromWhitelist", + "functionName": "v1.signatures.repository.DeleteGithubOrganizationFromApprovalList", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": signatureID, "GitHubOrganizationID": GitHubOrganizationID, @@ -284,14 +284,14 @@ func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context }) if err != nil { - log.WithFields(f).Warnf("error retrieving GH organization whitelist for signatureID: %s and GH Org: %s, error: %v", + log.WithFields(f).Warnf("error retrieving GH organization approval list for signatureID: %s and GH Org: %s, error: %v", signatureID, GitHubOrganizationID, err) return nil, err } itemFromMap, ok := result.Item["github_org_whitelist"] if !ok { - log.WithFields(f).Warnf("unable to remove whitelist organization: %s for signature: %s - list is empty", + log.WithFields(f).Warnf("unable to remove github organization approval list entry: %s for signature: %s - list is empty", GitHubOrganizationID, signatureID) return nil, errors.New("no github_org_whitelist column") } @@ -311,7 +311,7 @@ func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context // ValidationException: ExpressionAttributeValues contains invalid value: Supplied AttributeValue // is empty, must contain exactly one of the supported data types for the key) - log.WithFields(f).Debugf("clearing out github org whitelist for organization: %s for signature: %s - list is empty", + log.WithFields(f).Debugf("clearing out github org approval list for organization: %s for signature: %s - list is empty", GitHubOrganizationID, signatureID) nullFlag := true @@ -336,7 +336,7 @@ func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context _, err = repo.dynamoDBClient.UpdateItem(input) if err != nil { - log.WithFields(f).Warnf("error updating github org whitelist to NULL value, error: %v", err) + log.WithFields(f).Warnf("error updating github org approva list to NULL value, error: %v", err) return nil, err } @@ -369,13 +369,13 @@ func (repo repository) DeleteGithubOrganizationFromWhitelist(ctx context.Context updatedValues, err := repo.dynamoDBClient.UpdateItem(input) if err != nil { - log.WithFields(f).Warnf("Error updating github org whitelist, error: %v", err) + log.WithFields(f).Warnf("Error updating github org approva list, error: %v", err) return nil, err } updatedItemFromMap, ok := updatedValues.Attributes["github_org_whitelist"] if !ok { - msg := fmt.Sprintf("unable to fetch updated whitelist organization values for "+ + msg := fmt.Sprintf("unable to fetch updated approva list organization values for "+ "organization id: %s for signature: %s - list is empty - returning empty list", GitHubOrganizationID, signatureID) log.WithFields(f).Debugf(msg) @@ -2802,12 +2802,7 @@ func (repo repository) removeColumn(ctx context.Context, signatureID, columnName ExpressionAttributeNames: map[string]*string{ "#" + columnName: aws.String(columnName), }, - //ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ - // ":a": { - // S: aws.String("bar"), - // }, - //}, - UpdateExpression: aws.String("REMOVE #" + columnName), //aws.String("REMOVE github_org_whitelist"), + UpdateExpression: aws.String("REMOVE #" + columnName), ReturnValues: aws.String(dynamodb.ReturnValueNone), } diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 69d5bb47a..9bf4e84c5 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -45,9 +45,9 @@ type SignatureService interface { GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams) (*models.Signatures, error) InvalidateProjectRecords(ctx context.Context, projectID, note string) (int, error) - GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string, githubAccessToken string) ([]models.GithubOrg, error) - AddGithubOrganizationToWhitelist(ctx context.Context, signatureID string, whiteListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) - DeleteGithubOrganizationFromWhitelist(ctx context.Context, signatureID string, whiteListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) + GetGithubOrganizationsFromApprovalList(ctx context.Context, signatureID string, githubAccessToken string) ([]models.GithubOrg, error) + AddGithubOrganizationToApprovalList(ctx context.Context, signatureID string, approvalListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) + DeleteGithubOrganizationFromApprovalList(ctx context.Context, signatureID string, approvalListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) UpdateApprovalList(ctx context.Context, authUser *auth.User, claGroupModel *models.ClaGroup, companyModel *models.Company, claGroupID string, params *models.ApprovalList) (*models.Signature, error) AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error) @@ -66,7 +66,7 @@ type service struct { githubOrgValidation bool } -// NewService creates a new whitelist service +// NewService creates a new signature service func NewService(repo SignatureRepository, companyService company.IService, usersService users.Service, eventsService events.Service, githubOrgValidation bool) SignatureService { return service{ repo, @@ -196,18 +196,18 @@ func (s service) GetUserSignatures(ctx context.Context, params signatures.GetUse return userSignatures, nil } -// GetGithubOrganizationsFromWhitelist retrieves the organization from the whitelist -func (s service) GetGithubOrganizationsFromWhitelist(ctx context.Context, signatureID string, githubAccessToken string) ([]models.GithubOrg, error) { +// GetGithubOrganizationsFromApprovalList retrieves the organization from the approval list +func (s service) GetGithubOrganizationsFromApprovalList(ctx context.Context, signatureID string, githubAccessToken string) ([]models.GithubOrg, error) { if signatureID == "" { - msg := "unable to get GitHub organizations whitelist - signature ID is nil" + msg := "unable to get GitHub organizations approval list - signature ID is nil" log.Warn(msg) return nil, errors.New(msg) } - orgIds, err := s.repo.GetGithubOrganizationsFromWhitelist(ctx, signatureID) + orgIds, err := s.repo.GetGithubOrganizationsFromApprovalList(ctx, signatureID) if err != nil { - log.Warnf("error loading github organization from whitelist using signatureID: %s, error: %v", + log.Warnf("error loading github organization from approval list using signatureID: %s, error: %v", signatureID, err) return nil, err } @@ -249,18 +249,18 @@ func (s service) GetGithubOrganizationsFromWhitelist(ctx context.Context, signat return orgIds, nil } -// AddGithubOrganizationToWhitelist adds the GH organization to the whitelist -func (s service) AddGithubOrganizationToWhitelist(ctx context.Context, signatureID string, whiteListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) { - organizationID := whiteListParams.OrganizationID +// AddGithubOrganizationToApprovalList adds the GH organization to the approval list +func (s service) AddGithubOrganizationToApprovalList(ctx context.Context, signatureID string, approvalListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) { + organizationID := approvalListParams.OrganizationID if signatureID == "" { - msg := "unable to add GitHub organization from whitelist - signature ID is nil" + msg := "unable to add GitHub organization from approval list - signature ID is nil" log.Warn(msg) return nil, errors.New(msg) } if organizationID == nil { - msg := "unable to add GitHub organization from whitelist - organization ID is nil" + msg := "unable to add GitHub organization from approval list - organization ID is nil" log.Warn(msg) return nil, errors.New(msg) } @@ -309,30 +309,30 @@ func (s service) AddGithubOrganizationToWhitelist(ctx context.Context, signature } } - gitHubWhiteList, err := s.repo.AddGithubOrganizationToWhitelist(ctx, signatureID, *organizationID) + gitHubOrgApprovalList, err := s.repo.AddGithubOrganizationToApprovalList(ctx, signatureID, *organizationID) if err != nil { - log.Warnf("issue adding github organization to white list using signatureID: %s, gh org id: %s, error: %v", + log.Warnf("issue adding github organization to approval list using signatureID: %s, gh org id: %s, error: %v", signatureID, *organizationID, err) return nil, err } - return gitHubWhiteList, nil + return gitHubOrgApprovalList, nil } -// DeleteGithubOrganizationFromWhitelist deletes the specified GH organization from the whitelist -func (s service) DeleteGithubOrganizationFromWhitelist(ctx context.Context, signatureID string, whiteListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) { +// DeleteGithubOrganizationFromApprovalList deletes the specified GH organization from the approval list +func (s service) DeleteGithubOrganizationFromApprovalList(ctx context.Context, signatureID string, approvalListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error) { // Extract the payload values - organizationID := whiteListParams.OrganizationID + organizationID := approvalListParams.OrganizationID if signatureID == "" { - msg := "unable to delete GitHub organization from whitelist - signature ID is nil" + msg := "unable to delete GitHub organization from approval list - signature ID is nil" log.Warn(msg) return nil, errors.New(msg) } if organizationID == nil { - msg := "unable to delete GitHub organization from whitelist - organization ID is nil" + msg := "unable to delete GitHub organization from approval list - organization ID is nil" log.Warn(msg) return nil, errors.New(msg) } @@ -381,12 +381,12 @@ func (s service) DeleteGithubOrganizationFromWhitelist(ctx context.Context, sign } } - gitHubWhiteList, err := s.repo.DeleteGithubOrganizationFromWhitelist(ctx, signatureID, *organizationID) + gitHubOrgApprovalList, err := s.repo.DeleteGithubOrganizationFromApprovalList(ctx, signatureID, *organizationID) if err != nil { return nil, err } - return gitHubWhiteList, nil + return gitHubOrgApprovalList, nil } // UpdateApprovalList service method diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 71efaa668..fc9791eff 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -52,7 +52,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "v2.signatures.handlers.SignaturesGetGitHubOrgWhitelistHandler", + "functionName": "v2.signatures.handlers.SignaturesGetSignatureHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "signatureID": params.SignatureID, } @@ -208,15 +208,15 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj githubAccessToken = "" } - ghWhiteList, err := v1SignatureService.GetGithubOrganizationsFromWhitelist(ctx, params.SignatureID, githubAccessToken) + ghOrgApprovalList, err := v1SignatureService.GetGithubOrganizationsFromApprovalList(ctx, params.SignatureID, githubAccessToken) if err != nil { - log.WithFields(f).Warnf("error fetching github organization whitelist entries v using signature_id: %s, error: %+v", + log.WithFields(f).Warnf("error fetching github organization approval list entries using signature_id: %s, error: %+v", params.SignatureID, err) return signatures.NewGetGitHubOrgWhitelistBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } var response []models.GithubOrg - err = copier.Copy(&response, ghWhiteList) + err = copier.Copy(&response, ghOrgApprovalList) if err != nil { return signatures.NewGetGitHubOrgWhitelistBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } @@ -254,7 +254,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj return signatures.NewAddGitHubOrgWhitelistBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } - ghApprovalList, err := v1SignatureService.AddGithubOrganizationToWhitelist(ctx, params.SignatureID, input, githubAccessToken) + ghApprovalList, err := v1SignatureService.AddGithubOrganizationToApprovalList(ctx, params.SignatureID, input, githubAccessToken) if err != nil { log.WithFields(f).Warnf("error adding github organization %s using signature_id: %s to the approval list, error: %+v", *params.Body.OrganizationID, params.SignatureID, err) @@ -322,7 +322,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj return signatures.NewDeleteGitHubOrgWhitelistBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(reqID, err)) } - ghApprovalList, err := v1SignatureService.DeleteGithubOrganizationFromWhitelist(ctx, params.SignatureID, input, githubAccessToken) + ghApprovalList, err := v1SignatureService.DeleteGithubOrganizationFromApprovalList(ctx, params.SignatureID, input, githubAccessToken) if err != nil { log.WithFields(f).Warnf("error deleting github organization %s using signature_id: %s from the approval list, error: %+v", *params.Body.OrganizationID, params.SignatureID, err) @@ -1487,68 +1487,6 @@ func isUserHaveAccessToCLAGroupProjects(ctx context.Context, authUser *auth.User return false } -// isUserHaveAccessToCLAProject is a helper function to determine if the user has access to the specified project -func isUserHaveAccessToCLAProject(ctx context.Context, authUser *auth.User, projectSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { // nolint - f := logrus.Fields{ - "functionName": "v2.signatures.handlers.isUserHaveAccessToCLAProject", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "userName": authUser.UserName, - "userEmail": authUser.Email, - } - - log.WithFields(f).Debugf("testing if user %s/%s has access to project SFID: %s...", authUser.UserName, authUser.Email, projectSFID) - if utils.IsUserAuthorizedForProject(ctx, authUser, projectSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to project SFID: %s...", authUser.UserName, authUser.Email, projectSFID) - return true - } - log.WithFields(f).Debugf("user %s/%s doesn't have direct access to the project SFID: %s - loading CLA Group from project id...", authUser.UserName, authUser.Email, projectSFID) - - log.WithFields(f).Debug("loading CLA Group from project id...") - projectCLAGroupModel, err := projectClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - returning false") - return false - } - if projectCLAGroupModel == nil { - log.WithFields(f).WithError(err).Warnf("problem loading project -> cla group mapping - no mapping found - returning false") - return false - } - - f["foundationSFID"] = projectCLAGroupModel.FoundationSFID - log.WithFields(f).Debug("testing if user has access to parent foundation...") - if utils.IsUserAuthorizedForProjectTree(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to parent foundation tree...") - return true - } - if utils.IsUserAuthorizedForProject(ctx, authUser, projectCLAGroupModel.FoundationSFID, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debug("user has access to parent foundation...") - return true - } - log.WithFields(f).Debug("user does not have access to parent foundation...") - - // Lookup the other project IDs for the CLA Group - log.WithFields(f).Debug("looking up other projects associated with the CLA Group...") - projectCLAGroupModels, err := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, projectCLAGroupModel.ClaGroupID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem loading project cla group mappings by CLA Group ID - returning false") - return false - } - - projectSFIDs := getProjectIDsFromModels(f, projectCLAGroupModel.FoundationSFID, projectCLAGroupModels) - projectSFIDsCSV := strings.Join(projectSFIDs, ",") // Create a project SFID CSV for printout - f["projectIDs"] = projectSFIDsCSV - - log.WithFields(f).Debugf("testing if user %s/%s has access to any cla group projects: %s", authUser.UserName, authUser.Email, projectSFIDsCSV) - if utils.IsUserAuthorizedForAnyProjects(ctx, authUser, projectSFIDs, utils.ALLOW_ADMIN_SCOPE) { - log.WithFields(f).Debugf("user %s/%s has access to at least of of the projects: %s...", authUser.UserName, authUser.Email, projectSFIDsCSV) - return true - } - - log.WithFields(f).Debug("exhausted project checks - user does not have access to project") - return false -} - // isUserHaveAccessToCLAProjectOrganization is a helper function to determine if the user has access to the specified project and organization func isUserHaveAccessToCLAProjectOrganization(ctx context.Context, authUser *auth.User, projectSFID, organizationSFID string, projectClaGroupsRepo projects_cla_groups.Repository) bool { f := logrus.Fields{ From d3ad0a06d760ad4a170d027f3e1b00314ad18f6b Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 23 Jul 2021 13:01:35 -0500 Subject: [PATCH 0364/1276] Updated Enroll/Unenroll Response Code (#3048) - Changed 500 internal server error to a 400 bad request - Added custom enroll/unenroll error structure/object to allow us to determine the type of error Signed-off-by: David Deal --- cla-backend-go/utils/errors.go | 40 ++++++++++++++++++++++++ cla-backend-go/v2/cla_groups/handlers.go | 16 ++++++++++ cla-backend-go/v2/cla_groups/service.go | 28 ++++++++++++----- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index a54444e1d..62ff6d6dc 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -366,3 +366,43 @@ func (e *InvalidCLAType) Error() string { func (e *InvalidCLAType) Unwrap() error { return e.Err } + +// EnrollError is an error model for representing enroll/un-enroll errors +type EnrollError struct { + Type string + Message string + Err error +} + +// Error is an error string function for enroll/un-enroll error +func (e *EnrollError) Error() string { + if e.Err == nil { + return fmt.Sprintf("%s validation error: %s", e.Type, e.Message) + } + return fmt.Sprintf("%s validation error: %s due to error: %+v", e.Type, e.Message, e.Err) +} + +// Unwrap method returns its contained error +func (e *EnrollError) Unwrap() error { + return e.Err +} + +// EnrollValidationError is an error model for representing enroll/un-enroll validation errors +type EnrollValidationError struct { + Type string + Message string + Err error +} + +// Error is an error string function for enroll/un-enroll validation error +func (e *EnrollValidationError) Error() string { + if e.Err == nil { + return fmt.Sprintf("%s validation error: %s", e.Type, e.Message) + } + return fmt.Sprintf("%s validation error: %s due to error: %+v", e.Type, e.Message, e.Err) +} + +// Unwrap method returns its contained error +func (e *EnrollValidationError) Unwrap() error { + return e.Err +} diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index a2a2d7e2f..401194f83 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -332,6 +332,14 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P }) if enrollCLAGroupErr != nil { + if _, ok := enrollCLAGroupErr.(*utils.EnrollValidationError); ok { + return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, "unable to enroll projects in CLA Group", enrollCLAGroupErr)) + } + if _, ok := enrollCLAGroupErr.(*utils.EnrollError); ok { + return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, "unable to enroll projects in CLA Group", enrollCLAGroupErr)) + } if strings.Contains(enrollCLAGroupErr.Error(), "bad request") { return cla_group.NewEnrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( utils.ErrorResponseBadRequestWithError(reqID, "unable to enroll projects in CLA Group", enrollCLAGroupErr)) @@ -384,6 +392,14 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P ProjectSFIDList: params.ProjectSFIDList, }) if err != nil { + if _, ok := err.(*utils.EnrollValidationError); ok { + return cla_group.NewUnenrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, "unable to enroll projects in CLA Group", err)) + } + if _, ok := err.(*utils.EnrollError); ok { + return cla_group.NewUnenrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, "unable to enroll projects in CLA Group", err)) + } if strings.Contains(err.Error(), "bad request") { return cla_group.NewUnenrollProjectsBadRequest().WithXRequestID(reqID).WithPayload( utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to unenroll projects for CLA Group ID: %s", params.ClaGroupID), err)) diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 53bbb9557..10ec2b9ca 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -980,8 +980,11 @@ func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollP log.WithFields(f).Debug("validating enroll project input") err := s.validateEnrollProjectsInput(ctx, request.FoundationSFID, request.ProjectSFIDList) if err != nil { - log.WithFields(f).Warnf("validating enroll project input failed. error = %s", err) - return err + return &utils.EnrollValidationError{ + Type: "enroll", + Message: "invalid project ID value", + Err: err, + } } // Setup a wait group to enroll and enable CLA service - we'll want to work quickly here @@ -1020,8 +1023,11 @@ func (s *service) EnrollProjectsInClaGroup(ctx context.Context, request *EnrollP // Wait until all go routines are done wg.Wait() if len(errorList) > 0 { - log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when enrolling and enabling CLA service for %d projects", len(errorList), len(request.ProjectSFIDList)) - return errorList[0] + return &utils.EnrollError{ + Type: "enroll", + Message: fmt.Sprintf("encountered %d errors when enrolling and disabling CLA service for %d projects", len(errorList), len(request.ProjectSFIDList)), + Err: errorList[0], + } } return nil @@ -1042,8 +1048,11 @@ func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *Unenr log.WithFields(f).Debug("validating unenroll project input") err := s.validateUnenrollProjectsInput(ctx, request.FoundationSFID, request.ProjectSFIDList) if err != nil { - log.WithFields(f).Warnf("validating unenroll project input failed. error = %s", err) - return err + return &utils.EnrollValidationError{ + Type: "unenroll", + Message: "invalid project ID value", + Err: err, + } } // Setup a wait group to enroll and enable CLA service - we'll want to work quickly here @@ -1081,8 +1090,11 @@ func (s *service) UnenrollProjectsInClaGroup(ctx context.Context, request *Unenr // Wait until all go routines are done wg.Wait() if len(errorList) > 0 { - log.WithFields(f).WithError(errorList[0]).Warnf("encountered %d errors when unenrolling and disabling CLA service for %d projects", len(errorList), len(request.ProjectSFIDList)) - return errorList[0] + return &utils.EnrollError{ + Type: "unenroll", + Message: fmt.Sprintf("encountered %d errors when unenrolling and disabling CLA service for %d projects", len(errorList), len(request.ProjectSFIDList)), + Err: errorList[0], + } } return nil From 537b4fbf997d424b9694c7017f870508d533daa6 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 23 Jul 2021 15:57:14 -0500 Subject: [PATCH 0365/1276] Resolved GitHub Org Update Issue (#3049) - Removed trailing comma in syntax of query - Normalized API function calls for Github -> GitHub - Updated event log message when updating to include branch protection value Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 27 +++---- .../github_organizations/handlers.go | 8 +-- cla-backend-go/github_organizations/mock.go | 60 ++++++++-------- .../github_organizations/repository.go | 70 +++++++++---------- .../github_organizations/service.go | 50 ++++++------- .../repositories/mock/mock_service.go | 22 +++--- cla-backend-go/repositories/service.go | 8 +-- cla-backend-go/signatures/repository.go | 2 +- cla-backend-go/v2/dynamo_events/autoenable.go | 2 +- .../v2/dynamo_events/github_repository.go | 4 +- cla-backend-go/v2/github_activity/service.go | 4 +- .../v2/github_organizations/handlers.go | 6 +- .../v2/github_organizations/service.go | 8 +-- cla-backend-go/v2/repositories/service.go | 14 ++-- 14 files changed, 145 insertions(+), 140 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index eec9bcd3f..f9f8a2e11 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -185,9 +185,10 @@ type GitHubOrganizationDeletedEventData struct { // GitHubOrganizationUpdatedEventData data model type GitHubOrganizationUpdatedEventData struct { - GitHubOrganizationName string - AutoEnabled bool - AutoEnabledClaGroupID string + GitHubOrganizationName string + AutoEnabled bool + AutoEnabledClaGroupID string + BranchProtectionEnabled bool } // CCLAApprovalListRequestCreatedEventData data model @@ -635,10 +636,14 @@ func (ed *GitHubOrganizationDeletedEventData) GetEventDetailsString(args *LogEve // GetEventDetailsString returns the details string for this event func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization:%s was updated with auto-enabled: %t", - ed.GitHubOrganizationName, ed.AutoEnabled) + data := fmt.Sprintf("The GitHub Organization '%s' was updated", ed.GitHubOrganizationName) + data = data + fmt.Sprintf(" with auto-enabled set to %t", ed.AutoEnabled) + data = data + fmt.Sprintf(" with branch protection set to %t", ed.BranchProtectionEnabled) if ed.AutoEnabledClaGroupID != "" { - data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + data = data + fmt.Sprintf(" with auto-enabled-cla-group ID value of %s", ed.AutoEnabledClaGroupID) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for project %s", args.ProjectName) } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) @@ -1525,13 +1530,11 @@ func (ed *GitHubOrganizationDeletedEventData) GetEventSummaryString(args *LogEve // GetEventSummaryString returns the summary string for this event func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization: %s was updated with auto-enabled: %t", - ed.GitHubOrganizationName, ed.AutoEnabled) + data := fmt.Sprintf("The GitHub Organization '%s' was updated", ed.GitHubOrganizationName) + data = data + fmt.Sprintf(" with auto-enabled set to %t", ed.AutoEnabled) + data = data + fmt.Sprintf(" with branch protection set to %t", ed.BranchProtectionEnabled) if ed.AutoEnabledClaGroupID != "" { - data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) - } - if args.CLAGroupName != "" { - data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) + data = data + fmt.Sprintf(" with auto-enabled-cla-group ID value of %s", ed.AutoEnabledClaGroupID) } if args.ProjectName != "" { data = data + fmt.Sprintf(" for project %s", args.ProjectName) diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index 4f23a1fb6..c09ed5ffb 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -28,7 +28,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint - result, err := service.GetGithubOrganizations(ctx, params.ProjectSFID) + result, err := service.GetGitHubOrganizations(ctx, params.ProjectSFID) if err != nil { if _, ok := err.(*v2ProjectServiceClient.GetProjectNotFound); ok { return github_organizations.NewGetProjectGithubOrganizationsNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ @@ -65,7 +65,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev return github_organizations.NewAddProjectGithubOrganizationNotFound().WithPayload(errorResponse(err)) } - result, err := service.AddGithubOrganization(ctx, params.ProjectSFID, params.Body) + result, err := service.AddGitHubOrganization(ctx, params.ProjectSFID, params.Body) if err != nil { if _, ok := err.(*v2ProjectServiceClient.GetProjectNotFound); ok { return github_organizations.NewAddProjectGithubOrganizationNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ @@ -114,7 +114,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev return github_organizations.NewDeleteProjectGithubOrganizationNotFound().WithPayload(errorResponse(err)) } - err = service.DeleteGithubOrganization(ctx, params.ProjectSFID, params.OrgName) + err = service.DeleteGitHubOrganization(ctx, params.ProjectSFID, params.OrgName) if err != nil { if _, ok := err.(*v2ProjectServiceClient.GetProjectNotFound); ok { return github_organizations.NewDeleteProjectGithubOrganizationNotFound().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ @@ -155,7 +155,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev }) } - err := service.UpdateGithubOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + err := service.UpdateGitHubOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) if err != nil { if errors.Is(err, projects_cla_groups.ErrCLAGroupDoesNotExist) { return github_organizations.NewUpdateProjectGithubOrganizationConfigNotFound().WithPayload(errorResponse(err)) diff --git a/cla-backend-go/github_organizations/mock.go b/cla-backend-go/github_organizations/mock.go index 01c35588c..12e1014e9 100644 --- a/cla-backend-go/github_organizations/mock.go +++ b/cla-backend-go/github_organizations/mock.go @@ -39,10 +39,10 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { return m.recorder } -// AddGithubOrganization mocks base method -func (m *MockRepository) AddGithubOrganization(arg0 context.Context, arg1, arg2 string, arg3 *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +// AddGitHubOrganization mocks base method +func (m *MockRepository) AddGitHubOrganization(arg0 context.Context, arg1, arg2 string, arg3 *models.CreateGithubOrganization) (*models.GithubOrganization, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddGithubOrganization", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "AddGitHubOrganization", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*models.GithubOrganization) ret1, _ := ret[1].(error) return ret0, ret1 @@ -51,13 +51,13 @@ func (m *MockRepository) AddGithubOrganization(arg0 context.Context, arg1, arg2 // AddGithubOrganization indicates an expected call of AddGithubOrganization func (mr *MockRepositoryMockRecorder) AddGithubOrganization(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubOrganization", reflect.TypeOf((*MockRepository)(nil).AddGithubOrganization), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGitHubOrganization", reflect.TypeOf((*MockRepository)(nil).AddGitHubOrganization), arg0, arg1, arg2, arg3) } -// DeleteGithubOrganization mocks base method -func (m *MockRepository) DeleteGithubOrganization(arg0 context.Context, arg1, arg2 string) error { +// DeleteGitHubOrganization mocks base method +func (m *MockRepository) DeleteGitHubOrganization(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteGithubOrganization", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "DeleteGitHubOrganization", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } @@ -65,13 +65,13 @@ func (m *MockRepository) DeleteGithubOrganization(arg0 context.Context, arg1, ar // DeleteGithubOrganization indicates an expected call of DeleteGithubOrganization func (mr *MockRepositoryMockRecorder) DeleteGithubOrganization(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGithubOrganization", reflect.TypeOf((*MockRepository)(nil).DeleteGithubOrganization), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGitHubOrganization", reflect.TypeOf((*MockRepository)(nil).DeleteGitHubOrganization), arg0, arg1, arg2) } // DeleteGithubOrganizationByParent mocks base method -func (m *MockRepository) DeleteGithubOrganizationByParent(arg0 context.Context, arg1, arg2 string) error { +func (m *MockRepository) DeleteGitHubOrganizationByParent(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteGithubOrganizationByParent", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "DeleteGitHubOrganizationByParent", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } @@ -79,13 +79,13 @@ func (m *MockRepository) DeleteGithubOrganizationByParent(arg0 context.Context, // DeleteGithubOrganizationByParent indicates an expected call of DeleteGithubOrganizationByParent func (mr *MockRepositoryMockRecorder) DeleteGithubOrganizationByParent(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGithubOrganizationByParent", reflect.TypeOf((*MockRepository)(nil).DeleteGithubOrganizationByParent), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGitHubOrganizationByParent", reflect.TypeOf((*MockRepository)(nil).DeleteGitHubOrganizationByParent), arg0, arg1, arg2) } // GetGithubOrganization mocks base method -func (m *MockRepository) GetGithubOrganization(arg0 context.Context, arg1 string) (*models.GithubOrganization, error) { +func (m *MockRepository) GetGitHubOrganization(arg0 context.Context, arg1 string) (*models.GithubOrganization, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganization", arg0, arg1) + ret := m.ctrl.Call(m, "GetGitHubOrganization", arg0, arg1) ret0, _ := ret[0].(*models.GithubOrganization) ret1, _ := ret[1].(error) return ret0, ret1 @@ -94,13 +94,13 @@ func (m *MockRepository) GetGithubOrganization(arg0 context.Context, arg1 string // GetGithubOrganization indicates an expected call of GetGithubOrganization func (mr *MockRepositoryMockRecorder) GetGithubOrganization(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganization", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganization), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganization", reflect.TypeOf((*MockRepository)(nil).GetGitHubOrganization), arg0, arg1) } -// GetGithubOrganizationByName mocks base method -func (m *MockRepository) GetGithubOrganizationByName(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizationByName mocks base method +func (m *MockRepository) GetGitHubOrganizationByName(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizationByName", arg0, arg1) + ret := m.ctrl.Call(m, "GetGitHubOrganizationByName", arg0, arg1) ret0, _ := ret[0].(*models.GithubOrganizations) ret1, _ := ret[1].(error) return ret0, ret1 @@ -109,13 +109,13 @@ func (m *MockRepository) GetGithubOrganizationByName(arg0 context.Context, arg1 // GetGithubOrganizationByName indicates an expected call of GetGithubOrganizationByName func (mr *MockRepositoryMockRecorder) GetGithubOrganizationByName(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationByName", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizationByName), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganizationByName", reflect.TypeOf((*MockRepository)(nil).GetGitHubOrganizationByName), arg0, arg1) } -// GetGithubOrganizations mocks base method -func (m *MockRepository) GetGithubOrganizations(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizations mocks base method +func (m *MockRepository) GetGitHubOrganizations(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizations", arg0, arg1) + ret := m.ctrl.Call(m, "GetGitHubOrganizations", arg0, arg1) ret0, _ := ret[0].(*models.GithubOrganizations) ret1, _ := ret[1].(error) return ret0, ret1 @@ -124,13 +124,13 @@ func (m *MockRepository) GetGithubOrganizations(arg0 context.Context, arg1 strin // GetGithubOrganizations indicates an expected call of GetGithubOrganizations func (mr *MockRepositoryMockRecorder) GetGithubOrganizations(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizations", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizations), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganizations", reflect.TypeOf((*MockRepository)(nil).GetGitHubOrganizations), arg0, arg1) } -// GetGithubOrganizationsByParent mocks base method -func (m *MockRepository) GetGithubOrganizationsByParent(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizationsByParent mocks base method +func (m *MockRepository) GetGitHubOrganizationsByParent(arg0 context.Context, arg1 string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizationsByParent", arg0, arg1) + ret := m.ctrl.Call(m, "GetGitHubOrganizationsByParent", arg0, arg1) ret0, _ := ret[0].(*models.GithubOrganizations) ret1, _ := ret[1].(error) return ret0, ret1 @@ -139,13 +139,13 @@ func (m *MockRepository) GetGithubOrganizationsByParent(arg0 context.Context, ar // GetGithubOrganizationsByParent indicates an expected call of GetGithubOrganizationsByParent func (mr *MockRepositoryMockRecorder) GetGithubOrganizationsByParent(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationsByParent", reflect.TypeOf((*MockRepository)(nil).GetGithubOrganizationsByParent), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganizationsByParent", reflect.TypeOf((*MockRepository)(nil).GetGitHubOrganizationsByParent), arg0, arg1) } -// UpdateGithubOrganization mocks base method -func (m *MockRepository) UpdateGithubOrganization(arg0 context.Context, arg1, arg2 string, arg3 bool, arg4 string, arg5 bool, arg6 *bool) error { +// UpdateGitHubOrganization mocks base method +func (m *MockRepository) UpdateGitHubOrganization(arg0 context.Context, arg1, arg2 string, arg3 bool, arg4 string, arg5 bool, arg6 *bool) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateGithubOrganization", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret := m.ctrl.Call(m, "UpdateGitHubOrganization", arg0, arg1, arg2, arg3, arg4, arg5, arg6) ret0, _ := ret[0].(error) return ret0 } @@ -153,5 +153,5 @@ func (m *MockRepository) UpdateGithubOrganization(arg0 context.Context, arg1, ar // UpdateGithubOrganization indicates an expected call of UpdateGithubOrganization func (mr *MockRepositoryMockRecorder) UpdateGithubOrganization(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubOrganization", reflect.TypeOf((*MockRepository)(nil).UpdateGithubOrganization), arg0, arg1, arg2, arg3, arg4, arg5, arg6) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGitHubOrganization", reflect.TypeOf((*MockRepository)(nil).UpdateGitHubOrganization), arg0, arg1, arg2, arg3, arg4, arg5, arg6) } diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index 2f6bc5a3c..35c104c50 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -30,21 +30,21 @@ const ( ProjectSFIDOrganizationNameIndex = "project-sfid-organization-name-index" ) -// errors var ( + // ErrOrganizationDoesNotExist organization does not exist error ErrOrganizationDoesNotExist = errors.New("github organization does not exist in cla") ) // RepositoryInterface interface defines the functions for the github organizations data model type RepositoryInterface interface { - AddGithubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) - GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) - GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) - GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) - GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) - UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error - DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error - DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error + AddGitHubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) + GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) + GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) + GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) + GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) + UpdateGitHubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error + DeleteGitHubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error + DeleteGitHubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error } // Repository object/struct @@ -63,8 +63,8 @@ func NewRepository(awsSession *session.Session, stage string) Repository { } } -// AddGithubOrganization add github organization logic -func (repo Repository) AddGithubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +// AddGitHubOrganization add github organization logic +func (repo Repository) AddGitHubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -76,7 +76,7 @@ func (repo Repository) AddGithubOrganization(ctx context.Context, parentProjectS } // First, let's check to see if we have an existing github organization with the same name - existingRecord, getErr := repo.GetGithubOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) + existingRecord, getErr := repo.GetGitHubOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) if getErr != nil { log.WithFields(f).WithError(getErr).Debug("unable to locate existing github organization by name") } @@ -111,7 +111,7 @@ func (repo Repository) AddGithubOrganization(ctx context.Context, parentProjectS // Attempt to simply update the existing record - we should only have one // activate GH org by updating the enabled flag enabled := true - updateErr := repo.UpdateGithubOrganization(ctx, + updateErr := repo.UpdateGitHubOrganization(ctx, projectSFID, utils.StringValue(input.OrganizationName), autoEnabled, @@ -126,7 +126,7 @@ func (repo Repository) AddGithubOrganization(ctx context.Context, parentProjectS // we could simply update the record we initially loaded or simply query the updated record again... // we're using a key lookup, so it should be fast... - existingUpdatedRecord, getUpdatedRecordErr := repo.GetGithubOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) + existingUpdatedRecord, getUpdatedRecordErr := repo.GetGitHubOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) if getUpdatedRecordErr != nil { log.WithFields(f).WithError(getUpdatedRecordErr).Warn("unable to locate existing github organization by name") return nil, getUpdatedRecordErr @@ -189,8 +189,8 @@ func (repo Repository) AddGithubOrganization(ctx context.Context, parentProjectS return ToModel(githubOrg), nil } -// GetGithubOrganizations get github organizations based on the project SFID -func (repo Repository) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizations get github organizations based on the project SFID +func (repo Repository) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -245,10 +245,10 @@ func (repo Repository) GetGithubOrganizations(ctx context.Context, projectSFID s return &models.GithubOrganizations{List: ghOrgList}, nil } -// GetGithubOrganizationsByParent returns a list of github organizations by parent project SFID -func (repo Repository) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizationsByParent returns a list of github organizations by parent project SFID +func (repo Repository) GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ - "functionName": "v1.github_organizations.repository.GetGithubOrganizationsByParent", + "functionName": "v1.github_organizations.repository.GetGitHubOrganizationsByParent", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, } @@ -297,8 +297,8 @@ func (repo Repository) GetGithubOrganizationsByParent(ctx context.Context, paren return &models.GithubOrganizations{List: ghOrgList}, nil } -// GetGithubOrganizationByName get github organization by name -func (repo Repository) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizationByName get github organization by name +func (repo Repository) GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -346,8 +346,8 @@ func (repo Repository) GetGithubOrganizationByName(ctx context.Context, githubOr return &models.GithubOrganizations{List: ghOrgList}, nil } -// GetGithubOrganization by organization name -func (repo Repository) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { +// GetGitHubOrganization by organization name +func (repo Repository) GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -380,8 +380,8 @@ func (repo Repository) GetGithubOrganization(ctx context.Context, githubOrganiza return ToModel(&org), nil } -// UpdateGithubOrganization updates the specified GitHub organization based on the update model provided -func (repo Repository) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { +// UpdateGitHubOrganization updates the specified GitHub organization based on the update model provided +func (repo Repository) UpdateGitHubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.UpdateGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -394,7 +394,7 @@ func (repo Repository) UpdateGithubOrganization(ctx context.Context, projectSFID } _, currentTime := utils.CurrentTime() - githubOrg, lookupErr := repo.GetGithubOrganization(ctx, organizationName) + githubOrg, lookupErr := repo.GetGitHubOrganization(ctx, organizationName) if lookupErr != nil { log.WithFields(f).Warnf("error looking up GitHub organization by name, error: %+v", lookupErr) return lookupErr @@ -425,14 +425,14 @@ func (repo Repository) UpdateGithubOrganization(ctx context.Context, projectSFID S: aws.String(currentTime), }, } - updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m," + updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m" if enabled != nil { expressionAttributeNames["#E"] = aws.String("enabled") expressionAttributeValues[":e"] = &dynamodb.AttributeValue{ BOOL: aws.Bool(*enabled), } - updateExpression = updateExpression + " #E = :e " + updateExpression = updateExpression + ", #E = :e " } input := &dynamodb.UpdateItemInput{ @@ -447,7 +447,7 @@ func (repo Repository) UpdateGithubOrganization(ctx context.Context, projectSFID TableName: aws.String(repo.githubOrgTableName), } - log.WithFields(f).Debug("updating github organization record...") + log.WithFields(f).Debugf("updating github organization record: %+v", input) _, updateErr := repo.dynamoDBClient.UpdateItem(input) if updateErr != nil { log.WithFields(f).Warnf("unable to update GitHub organization record, error: %+v", updateErr) @@ -457,8 +457,8 @@ func (repo Repository) UpdateGithubOrganization(ctx context.Context, projectSFID return nil } -// DeleteGithubOrganization deletes the github organization by project SFID -func (repo Repository) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { +// DeleteGitHubOrganization deletes the github organization by project SFID +func (repo Repository) DeleteGitHubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -467,7 +467,7 @@ func (repo Repository) DeleteGithubOrganization(ctx context.Context, projectSFID } var githubOrganizationName string - orgs, orgErr := repo.GetGithubOrganizations(ctx, projectSFID) + orgs, orgErr := repo.GetGitHubOrganizations(ctx, projectSFID) if orgErr != nil { errMsg := fmt.Sprintf("github organization is not found using projectSFID: %s, error: %+v", projectSFID, orgErr) log.WithFields(f).Warn(errMsg) @@ -520,8 +520,8 @@ func (repo Repository) DeleteGithubOrganization(ctx context.Context, projectSFID return nil } -// DeleteGithubOrganizationByParent deletes the github organization by parent SFID -func (repo Repository) DeleteGithubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error { +// DeleteGitHubOrganizationByParent deletes the github organization by parent SFID +func (repo Repository) DeleteGitHubOrganizationByParent(ctx context.Context, parentProjectSFID string, githubOrgName string) error { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -530,7 +530,7 @@ func (repo Repository) DeleteGithubOrganizationByParent(ctx context.Context, par } var githubOrganizationName string - orgs, orgErr := repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) + orgs, orgErr := repo.GetGitHubOrganizationsByParent(ctx, parentProjectSFID) if orgErr != nil { errMsg := fmt.Sprintf("github organization is not found using parentProjectSFID %s, error: - %+v", parentProjectSFID, orgErr) log.WithFields(f).Warn(errMsg) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index e001d26b9..bb865139e 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -21,12 +21,12 @@ import ( // ServiceInterface contains functions of GithubOrganizations service type ServiceInterface interface { - AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) - GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) - GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) - GetGithubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) - UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error - DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error + AddGitHubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) + GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) + GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) + GetGitHubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) + UpdateGitHubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error + DeleteGitHubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error RemoveDuplicates(input []*models.GithubOrganization) []*models.GithubOrganization } @@ -46,8 +46,8 @@ func NewService(repo RepositoryInterface, ghRepository repositories.Repository, } } -// AddGithubOrganization adds the github organization for the specified project -func (s Service) AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +// AddGitHubOrganization adds the github organization for the specified project +func (s Service) AddGitHubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -70,11 +70,11 @@ func (s Service) AddGithubOrganization(ctx context.Context, projectSFID string, } } - return s.repo.AddGithubOrganization(ctx, parentProjectSFID, projectSFID, input) + return s.repo.AddGitHubOrganization(ctx, parentProjectSFID, projectSFID, input) } -// GetGithubOrganizations returns the github organization for the specified project -func (s Service) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizations returns the github organization for the specified project +func (s Service) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "GetGitHubOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -85,7 +85,7 @@ func (s Service) GetGithubOrganizations(ctx context.Context, projectSFID string) var gitHubOrgModels = models.GithubOrganizations{} var githubOrgs = make([]*models.GithubOrganization, 0) - projectGithubModels, err := s.repo.GetGithubOrganizations(ctx, projectSFID) + projectGithubModels, err := s.repo.GetGitHubOrganizations(ctx, projectSFID) if err != nil { log.WithFields(f).Warnf("problem fetching github organizations by projectSFID, error: %+v", err) return nil, err @@ -113,7 +113,7 @@ func (s Service) GetGithubOrganizations(ctx context.Context, projectSFID string) if parentProjectSFID != projectSFID && (projectDetails != nil && !utils.IsProjectHasRootParent(projectDetails)) { log.WithFields(f).Debugf("found parent of projectSFID: %s to be %s. Searching github organization by parent SFID: %s...", projectSFID, parentProjectSFID, parentProjectSFID) - parentGithubModels, parentErr := s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) + parentGithubModels, parentErr := s.repo.GetGitHubOrganizationsByParent(ctx, parentProjectSFID) if parentErr != nil { log.WithFields(f).Warnf("problem fetching github organizations by paarent projectSFID: %s , error: %+v", parentProjectSFID, err) return nil, parentErr @@ -133,20 +133,20 @@ func (s Service) GetGithubOrganizations(ctx context.Context, projectSFID string) return &gitHubOrgModels, err } -// GetGithubOrganizationsByParent returns the github organizations for the specified parent project SFID -func (s Service) GetGithubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { - return s.repo.GetGithubOrganizationsByParent(ctx, parentProjectSFID) +// GetGitHubOrganizationsByParent returns the github organizations for the specified parent project SFID +func (s Service) GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { + return s.repo.GetGitHubOrganizationsByParent(ctx, parentProjectSFID) } -// GetGithubOrganizationByName returns the github organizations for the specified github organization name -func (s Service) GetGithubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) { +// GetGitHubOrganizationByName returns the github organizations for the specified github organization name +func (s Service) GetGitHubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "GetGitHubOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "githubOrgName": githubOrgName, } - gitHubOrgs, err := s.repo.GetGithubOrganizationByName(ctx, githubOrgName) + gitHubOrgs, err := s.repo.GetGitHubOrganizationByName(ctx, githubOrgName) if err != nil { log.WithFields(f).Warnf("problem fetching github organizations by name, error: %+v", err) return nil, err @@ -163,19 +163,19 @@ func (s Service) GetGithubOrganizationByName(ctx context.Context, githubOrgName return gitHubOrgs.List[0], err } -// UpdateGithubOrganization updates the specified github organization based on the project SFID, organization name provided values -func (s Service) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { +// UpdateGitHubOrganization updates the specified github organization based on the project SFID, organization name provided values +func (s Service) UpdateGitHubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { // check if valid cla group id is passed if autoEnabledClaGroupID != "" { if _, err := s.claRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { return err } } - return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) + return s.repo.UpdateGitHubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) } -// DeleteGithubOrganization removes the specified github organization under the projectSFID -func (s Service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { +// DeleteGitHubOrganization removes the specified github organization under the projectSFID +func (s Service) DeleteGitHubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { f := logrus.Fields{ "functionName": "DeleteGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -196,7 +196,7 @@ func (s Service) DeleteGithubOrganization(ctx context.Context, projectSFID strin return err } - return s.repo.DeleteGithubOrganization(ctx, projectSFID, githubOrgName) + return s.repo.DeleteGitHubOrganization(ctx, projectSFID, githubOrgName) } // RemoveDuplicates removes any duplicates from the specified list diff --git a/cla-backend-go/repositories/mock/mock_service.go b/cla-backend-go/repositories/mock/mock_service.go index 4d6e8067f..5363e52a2 100644 --- a/cla-backend-go/repositories/mock/mock_service.go +++ b/cla-backend-go/repositories/mock/mock_service.go @@ -238,10 +238,10 @@ func (m *MockGithubOrgRepo) EXPECT() *MockGithubOrgRepoMockRecorder { return m.recorder } -// GetGithubOrganizationByName mocks base method -func (m *MockGithubOrgRepo) GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizationByName mocks base method +func (m *MockGithubOrgRepo) GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizationByName", ctx, githubOrganizationName) + ret := m.ctrl.Call(m, "GetGitHubOrganizationByName", ctx, githubOrganizationName) ret0, _ := ret[0].(*models.GithubOrganizations) ret1, _ := ret[1].(error) return ret0, ret1 @@ -250,13 +250,13 @@ func (m *MockGithubOrgRepo) GetGithubOrganizationByName(ctx context.Context, git // GetGithubOrganizationByName indicates an expected call of GetGithubOrganizationByName func (mr *MockGithubOrgRepoMockRecorder) GetGithubOrganizationByName(ctx, githubOrganizationName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizationByName", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGithubOrganizationByName), ctx, githubOrganizationName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganizationByName", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGitHubOrganizationByName), ctx, githubOrganizationName) } // GetGithubOrganization mocks base method -func (m *MockGithubOrgRepo) GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { +func (m *MockGithubOrgRepo) GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganization", ctx, githubOrganizationName) + ret := m.ctrl.Call(m, "GetGitHubOrganization", ctx, githubOrganizationName) ret0, _ := ret[0].(*models.GithubOrganization) ret1, _ := ret[1].(error) return ret0, ret1 @@ -265,13 +265,13 @@ func (m *MockGithubOrgRepo) GetGithubOrganization(ctx context.Context, githubOrg // GetGithubOrganization indicates an expected call of GetGithubOrganization func (mr *MockGithubOrgRepoMockRecorder) GetGithubOrganization(ctx, githubOrganizationName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganization", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGithubOrganization), ctx, githubOrganizationName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganization", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGitHubOrganization), ctx, githubOrganizationName) } -// GetGithubOrganizations mocks base method -func (m *MockGithubOrgRepo) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { +// GetGitHubOrganizations mocks base method +func (m *MockGithubOrgRepo) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetGithubOrganizations", ctx, projectSFID) + ret := m.ctrl.Call(m, "GetGitHubOrganizations", ctx, projectSFID) ret0, _ := ret[0].(*models.GithubOrganizations) ret1, _ := ret[1].(error) return ret0, ret1 @@ -280,5 +280,5 @@ func (m *MockGithubOrgRepo) GetGithubOrganizations(ctx context.Context, projectS // GetGithubOrganizations indicates an expected call of GetGithubOrganizations func (mr *MockGithubOrgRepoMockRecorder) GetGithubOrganizations(ctx, projectSFID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGithubOrganizations", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGithubOrganizations), ctx, projectSFID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGitHubOrganizations", reflect.TypeOf((*MockGithubOrgRepo)(nil).GetGitHubOrganizations), ctx, projectSFID) } diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index 054e14436..ba971d2f9 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -38,9 +38,9 @@ type Service interface { // GithubOrgRepo provide method to get github organization by name type GithubOrgRepo interface { - GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) - GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) - GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) + GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*models.GithubOrganizations, error) + GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) + GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) } type service struct { @@ -88,7 +88,7 @@ func (s *service) AddGithubRepository(ctx context.Context, externalProjectID str return nil, projectErr } - org, err := s.ghOrgRepo.GetGithubOrganizationByName(ctx, utils.StringValue(input.RepositoryOrganizationName)) + org, err := s.ghOrgRepo.GetGitHubOrganizationByName(ctx, utils.StringValue(input.RepositoryOrganizationName)) if err != nil { log.WithFields(f).WithError(err).Warnf("problem loading github organization by name: %s", utils.StringValue(input.RepositoryOrganizationName)) return nil, err diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index d7dafd59f..f937e432d 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -2466,7 +2466,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } for _, ghOrgRepo := range ghOrgRepositories { - ghOrg, getGHOrgErr := repo.ghOrgRepo.GetGithubOrganization(ctx, ghOrgRepo.RepositoryOrganizationName) + ghOrg, getGHOrgErr := repo.ghOrgRepo.GetGitHubOrganization(ctx, ghOrgRepo.RepositoryOrganizationName) if getGHOrgErr != nil { msg := fmt.Sprintf("unable to get gh org by name: %s ", ghOrgRepo.RepositoryOrganizationName) log.WithFields(f).WithError(getGHOrgErr).Warn(msg) diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index 06fa65955..6eb4a1cf4 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -75,7 +75,7 @@ func (a *autoEnableServiceProvider) CreateAutoEnabledRepository(repo *github.Rep } organizationName := strings.Split(repositoryFullName, "/")[0] - orgModel, err := a.githubOrgRepo.GetGithubOrganization(ctx, organizationName) + orgModel, err := a.githubOrgRepo.GetGitHubOrganization(ctx, organizationName) if err != nil { log.Warnf("fetching github org failed : %v", err) return nil, err diff --git a/cla-backend-go/v2/dynamo_events/github_repository.go b/cla-backend-go/v2/dynamo_events/github_repository.go index 068e93284..3044b8504 100644 --- a/cla-backend-go/v2/dynamo_events/github_repository.go +++ b/cla-backend-go/v2/dynamo_events/github_repository.go @@ -97,7 +97,7 @@ func (s *service) EnableBranchProtectionServiceHandler(event events.DynamoDBEven parentOrgName := newRepoModel.RepositoryOrganizationName log.WithFields(f).Warnf("problem locating github organization by name: %s, error: %+v", parentOrgName, err) - gitHubOrg, err := s.githubOrgService.GetGithubOrganizationByName(context.Background(), parentOrgName) + gitHubOrg, err := s.githubOrgService.GetGitHubOrganizationByName(context.Background(), parentOrgName) if err != nil { log.WithFields(f).Warnf("problem locating github organization by name: %s, error: %+v", parentOrgName, err) return nil @@ -150,7 +150,7 @@ func (s *service) DisableBranchProtectionServiceHandler(event events.DynamoDBEve // Branch protection only available for GitHub if oldRepoModel.RepositoryType == utils.GitHubType { parentOrgName := oldRepoModel.RepositoryOrganizationName - gitHubOrg, err := s.githubOrgService.GetGithubOrganizationByName(context.Background(), parentOrgName) + gitHubOrg, err := s.githubOrgService.GetGitHubOrganizationByName(context.Background(), parentOrgName) if err != nil { log.WithFields(f).Warnf("problem locating github organization by name: %s, error: %+v", parentOrgName, err) return nil diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index 5cda76b0b..3c79a5fa5 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -336,12 +336,12 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont } // fetch the old and the new github orgs from the db - oldGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, oldOrganizationName) + oldGithubOrg, err := s.githubOrgRepo.GetGitHubOrganization(ctx, oldOrganizationName) if err != nil { return fmt.Errorf("fetching the old organization name : %s failed : %v", oldOrganizationName, err) } - newGithubOrg, err := s.githubOrgRepo.GetGithubOrganization(ctx, newOrganizationName) + newGithubOrg, err := s.githubOrgRepo.GetGitHubOrganization(ctx, newOrganizationName) if err != nil { disabledErr := s.disableFailedTransferRepo(ctx, sender, f, repoModel, oldGithubOrg, newGithubOrg) if disabledErr != nil { diff --git a/cla-backend-go/v2/github_organizations/handlers.go b/cla-backend-go/v2/github_organizations/handlers.go index f7a776a2b..a08053bd7 100644 --- a/cla-backend-go/v2/github_organizations/handlers.go +++ b/cla-backend-go/v2/github_organizations/handlers.go @@ -232,8 +232,10 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. EventType: events.GitHubOrganizationUpdated, ProjectSFID: params.ProjectSFID, EventData: &events.GitHubOrganizationUpdatedEventData{ - GitHubOrganizationName: params.OrgName, - AutoEnabled: utils.BoolValue(params.Body.AutoEnabled), + GitHubOrganizationName: params.OrgName, + AutoEnabled: utils.BoolValue(params.Body.AutoEnabled), + AutoEnabledClaGroupID: params.Body.AutoEnabledClaGroupID, + BranchProtectionEnabled: params.Body.BranchProtectionEnabled, }, }) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 114cd8c3e..3655538ba 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -82,7 +82,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) // Load the GitHub Organization and Repository details - result will be missing CLA Group info and ProjectSFID details log.WithFields(f).Debugf("loading GitHub organizations for projectSFID: %s", projectSFID) - orgs, err := s.ghService.GetGithubOrganizations(ctx, projectSFID) + orgs, err := s.ghService.GetGitHubOrganizations(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("problem loading github organizations from the project service") return nil, err @@ -301,7 +301,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, log.WithFields(f).Debug("located parentProjectID...") log.WithFields(f).Debug("adding github organization...") - resp, err := s.repo.AddGithubOrganization(ctx, parentProjectSFID, projectSFID, &in) + resp, err := s.repo.AddGitHubOrganization(ctx, parentProjectSFID, projectSFID, &in) if err != nil { log.WithFields(f).WithError(err).Warn("problem adding github organization for project") return nil, err @@ -311,7 +311,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, } func (s service) UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { - return s.repo.UpdateGithubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) + return s.repo.UpdateGitHubOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) } func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error { @@ -338,5 +338,5 @@ func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID strin } log.WithFields(f).Debug("deleting github github organization...") - return s.repo.DeleteGithubOrganization(ctx, projectSFID, githubOrgName) + return s.repo.DeleteGitHubOrganization(ctx, projectSFID, githubOrgName) } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 9e0f95264..c83126b5a 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -44,9 +44,9 @@ type Service interface { // GithubOrgRepo provide method to get github organization by name type GithubOrgRepo interface { - GetGithubOrganizationByName(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganizations, error) - GetGithubOrganization(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganization, error) - GetGithubOrganizations(ctx context.Context, projectSFID string) (*v1Models.GithubOrganizations, error) + GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganizations, error) + GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganization, error) + GetGitHubOrganizations(ctx context.Context, projectSFID string) (*v1Models.GithubOrganizations, error) } type service struct { @@ -112,7 +112,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, return nil, fmt.Errorf("provided cla group id %s is not linked to project sfid %s", utils.StringValue(input.ClaGroupID), projectSFID) } - org, err := s.ghOrgRepo.GetGithubOrganizationByName(ctx, utils.StringValue(input.GithubOrganizationName)) + org, err := s.ghOrgRepo.GetGitHubOrganizationByName(ctx, utils.StringValue(input.GithubOrganizationName)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to get organization by name") return nil, err @@ -261,13 +261,13 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin //// Lookup orgs via projectSFID //log.WithFields(f).Debug("querying EasyCLA for organizations by project id...") //var githubOrgList *v1Models.GithubOrganizations - //githubOrgList, err = s.ghOrgRepo.GetGithubOrganizations(ctx, projectSFID) + //githubOrgList, err = s.ghOrgRepo.GetGitHubOrganizations(ctx, projectSFID) //if err != nil { // log.WithFields(f).WithError(err).Warn("unable to lookup project by id in the github organization table") // if projectModel.Parent != "" { // log.WithFields(f).Debugf("querying for organizations by parent project id: %s...", projectModel.Parent) // var ghOrgErr error - // githubOrgList, ghOrgErr = s.ghOrgRepo.GetGithubOrganizations(ctx, projectModel.Parent) + // githubOrgList, ghOrgErr = s.ghOrgRepo.GetGitHubOrganizations(ctx, projectModel.Parent) // if ghOrgErr != nil { // log.WithFields(f).WithError(ghOrgErr).Warn("unable to lookup project by parent id in the github organization table") // return nil, ghOrgErr @@ -516,7 +516,7 @@ func (s *service) getBranchProtectionRepositoryForOrgName(ctx context.Context, g "githubOrgName": githubOrgName, } - githubOrg, err := s.ghOrgRepo.GetGithubOrganization(ctx, githubOrgName) + githubOrg, err := s.ghOrgRepo.GetGitHubOrganization(ctx, githubOrgName) if err != nil { log.WithFields(f).Warnf("fetching githubOrg %s failed, error: %v", githubOrgName, err) return nil, err From 44d0850b09895442782292420e68c33ce9c3688a Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 23 Jul 2021 17:07:16 -0500 Subject: [PATCH 0366/1276] Activity Log Cleanup for Create CLA Group (#3050) Signed-off-by: David Deal --- cla-backend-go/v2/cla_groups/handlers.go | 23 ++++++++++++++--------- cla-backend-go/v2/cla_groups/service.go | 12 ++++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/cla_groups/handlers.go b/cla-backend-go/v2/cla_groups/handlers.go index 401194f83..bd2ffbdd1 100644 --- a/cla-backend-go/v2/cla_groups/handlers.go +++ b/cla-backend-go/v2/cla_groups/handlers.go @@ -60,9 +60,8 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P return cla_group.NewCreateClaGroupForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - claGroup, err := service.CreateCLAGroup(ctx, authUser, params.ClaGroupInput, utils.StringValue(params.XUSERNAME)) + claGroupSummary, err := service.CreateCLAGroup(ctx, authUser, params.ClaGroupInput, utils.StringValue(params.XUSERNAME)) if err != nil { - log.WithFields(f).WithError(err).Warn("unable to create the CLA Group") return cla_group.NewCreateClaGroupBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ Code: "400", Message: fmt.Sprintf("EasyCLA - 400 Bad Request - %s", err.Error()), @@ -70,17 +69,23 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P }) } + claGroupModel, err := service.GetCLAGroup(ctx, claGroupSummary.ClaGroupID) + if err != nil { + return cla_group.NewCreateClaGroupBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, "problem loading newly created CLA Group", err)) + } + // Log the event eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLAGroupCreated, - CLAGroupName: claGroup.ClaGroupName, - CLAGroupID: claGroup.ClaGroupID, - ParentProjectSFID: utils.StringValue(params.ClaGroupInput.FoundationSfid), + CLAGroupName: claGroupSummary.ClaGroupName, + CLAGroupID: claGroupSummary.ClaGroupID, + ClaGroupModel: claGroupModel, + ParentProjectSFID: claGroupSummary.FoundationSfid, LfUsername: authUser.UserName, EventData: &events.CLAGroupCreatedEventData{}, }) - return cla_group.NewCreateClaGroupOK().WithXRequestID(reqID).WithPayload(claGroup) + return cla_group.NewCreateClaGroupOK().WithXRequestID(reqID).WithPayload(claGroupSummary) }) api.ClaGroupUpdateClaGroupHandler = cla_group.UpdateClaGroupHandlerFunc(func(params cla_group.UpdateClaGroupParams, authUser *auth.User) middleware.Responder { @@ -164,7 +169,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P oldCLAGroupName = claGroupModel.ProjectName oldCLAGroupDescription = claGroupModel.ProjectDescription - claGroup, err := service.UpdateCLAGroup(ctx, authUser, claGroupModel, params.Body, utils.StringValue(params.XUSERNAME)) + claGroupSummary, err := service.UpdateCLAGroup(ctx, authUser, claGroupModel, params.Body) if err != nil { // Return a 409 conflict if we have a duplicate name if _, ok := err.(*utils.CLAGroupNameConflict); ok { @@ -180,7 +185,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P eventsService.LogEvent(&events.LogEventArgs{ EventType: events.CLAGroupUpdated, ClaGroupModel: claGroupModel, - ProjectID: claGroup.ClaGroupID, + ProjectID: claGroupSummary.ClaGroupID, ProjectSFID: projectCLAGroupModels[0].ProjectSFID, ParentProjectSFID: projectCLAGroupModels[0].FoundationSFID, LfUsername: authUser.UserName, @@ -192,7 +197,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1ProjectService v1P }, }) - return cla_group.NewUpdateClaGroupOK().WithXRequestID(reqID).WithPayload(claGroup) + return cla_group.NewUpdateClaGroupOK().WithXRequestID(reqID).WithPayload(claGroupSummary) }) api.ClaGroupDeleteClaGroupHandler = cla_group.DeleteClaGroupHandlerFunc(func(params cla_group.DeleteClaGroupParams, authUser *auth.User) middleware.Responder { diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 10ec2b9ca..e0c8aaeeb 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -54,7 +54,8 @@ type service struct { // Service interface type Service interface { CreateCLAGroup(ctx context.Context, authUser *auth.User, input *models.CreateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) - UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) + GetCLAGroup(ctx context.Context, claGroupID string) (*v1Models.ClaGroup, error) + UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput) (*models.ClaGroupSummary, error) ListClaGroupsForFoundationOrProject(ctx context.Context, foundationSFID string) (*models.ClaGroupListSummary, error) ListAllFoundationClaGroups(ctx context.Context, foundationID *string) (*models.FoundationMappingList, error) DeleteCLAGroup(ctx context.Context, claGroupModel *v1Models.ClaGroup, authUser *auth.User) error @@ -82,6 +83,7 @@ func NewService(projectService v1Project.Service, templateService v1Template.Ser } } +// CreateCLAGroup creates a new CLA group func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input *models.CreateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { // Validate the input log.WithField("input", input).Debugf("validating create cla group input") @@ -231,7 +233,13 @@ func (s *service) CreateCLAGroup(ctx context.Context, authUser *auth.User, input }, nil } -func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput, projectManagerLFID string) (*models.ClaGroupSummary, error) { +// GetCLAGroup returns the CLA group associated with the specified ID +func (s *service) GetCLAGroup(ctx context.Context, claGroupID string) (*v1Models.ClaGroup, error) { + return s.v1ProjectService.GetCLAGroupByID(ctx, claGroupID) +} + +// UpdateCLAGroup updates the specified CLA group with the input details +func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGroupModel *v1Models.ClaGroup, input *models.UpdateClaGroupInput) (*models.ClaGroupSummary, error) { // Validate the input f := logrus.Fields{ "functionName": "v2.cla_groups.service.UpdateCLAGroup", From af395ec12839515a5e2e6c14525d95ed89d2c8d7 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 23 Jul 2021 17:36:13 -0500 Subject: [PATCH 0367/1276] Resolved CLA Group Name/Description Update Issue (#3051) Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 43 +++++++++++++++++-------- cla-backend-go/project/repository.go | 17 +++++----- cla-backend-go/project/service.go | 2 +- cla-backend-go/v2/cla_groups/service.go | 1 + 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index f9f8a2e11..22f52c5fd 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -995,26 +995,38 @@ func (ed *CLAGroupCreatedEventData) GetEventDetailsString(args *LogEventArgs) (s // GetEventDetailsString returns the details string for this event func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - var nameUpdated bool + var nameUpdated, descriptionUpdated bool - data := fmt.Sprintf("CLA Group ID: %s was updated", args.ProjectID) + data := "The CLA Group" // nolint if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName { - data = fmt.Sprintf("%s with Name from : %s to : %s", data, ed.OldClaGroupName, ed.NewClaGroupName) + data = fmt.Sprintf("%s name was updated to '%s'", data, ed.NewClaGroupName) nameUpdated = true } + if args.CLAGroupID != "" { + data = data + fmt.Sprintf(" with the CLA group ID %s", args.CLAGroupID) + } + if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription { + descriptionUpdated = true if nameUpdated { - data = data + "," + data = fmt.Sprintf("%s and the description was updated to '%s'", data, ed.NewClaGroupDescription) } else { - data = data + " with" + data = fmt.Sprintf("%s description was updated to '%s'", data, ed.NewClaGroupDescription) } - data = fmt.Sprintf("%s Description from : %s to : %s", data, ed.OldClaGroupDescription, ed.NewClaGroupDescription) + } + + //shouldn't happen + if !nameUpdated && !descriptionUpdated { + data = data + " was updated" + } + + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) } if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } - data = data + "." return data, true } @@ -1935,27 +1947,32 @@ func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (s func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { var nameUpdated, descriptionUpdated bool - message := "The CLA Group" + data := "The CLA Group" // nolint if ed.NewClaGroupName != "" && ed.OldClaGroupName != ed.NewClaGroupName { - message = message + " name was updated to : " + ed.NewClaGroupName + data = fmt.Sprintf("%s name was updated to '%s'", data, ed.NewClaGroupName) nameUpdated = true } if ed.NewClaGroupDescription != "" && ed.OldClaGroupDescription != ed.NewClaGroupDescription { descriptionUpdated = true if nameUpdated { - message = message + " and the description was updated to : " + ed.NewClaGroupDescription + data = fmt.Sprintf("%s and the description was updated to '%s'", data, ed.NewClaGroupDescription) } else { - message = message + " description was updated to : " + ed.NewClaGroupDescription + data = fmt.Sprintf("%s description was updated to '%s'", data, ed.NewClaGroupDescription) } } //shouldn't happen if !nameUpdated && !descriptionUpdated { - message = message + " was updated" + data = data + " was updated" } - data := fmt.Sprintf("%s by the user %s.", message, args.UserName) + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } return data, true } diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 36f839193..1250821ab 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -665,18 +665,11 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG return nil, ErrProjectDoesNotExist } - // We don't allow CLA Group templates to be changed - this requires a legal review - if existingCLAGroup.ProjectTemplateID != claGroupModel.ProjectTemplateID { - msg := fmt.Sprintf("problem updating CLA Group - changing CLA templates is not allowed - project: %s with ID: %s - previous template: %s updated template: %s", - claGroupModel.ProjectName, claGroupModel.ProjectID, claGroupModel.ProjectTemplateID, claGroupModel.ProjectTemplateID) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) - } - expressionAttributeNames := map[string]*string{} expressionAttributeValues := map[string]*dynamodb.AttributeValue{} updateExpression := "SET " + // An update to the CLA Group name... if claGroupModel.ProjectName != "" && existingCLAGroup.ProjectName != claGroupModel.ProjectName { log.WithFields(f).Debugf("adding project_name: %s", claGroupModel.ProjectName) expressionAttributeNames["#N"] = aws.String("project_name") @@ -688,6 +681,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #LOW = :low, " } + // An update to the CLA Group description... if existingCLAGroup.ProjectDescription != claGroupModel.ProjectDescription { log.WithFields(f).Debugf("adding project_description: %s", claGroupModel.ProjectDescription) expressionAttributeNames["#DESC"] = aws.String("project_description") @@ -695,6 +689,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #DESC = :desc, " } + // An update to the project ACL if claGroupModel.ProjectACL != nil && len(claGroupModel.ProjectACL) > 0 { log.WithFields(f).Debugf("adding project_acl: %s", claGroupModel.ProjectACL) expressionAttributeNames["#A"] = aws.String("project_acl") @@ -702,6 +697,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #A = :a, " } + // An update to the ICLA enabled flag if claGroupModel.ProjectICLAEnabled != existingCLAGroup.ProjectICLAEnabled { log.WithFields(f).Debugf("adding project_icla_enabled: %t", claGroupModel.ProjectICLAEnabled) expressionAttributeNames["#I"] = aws.String("project_icla_enabled") @@ -709,6 +705,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #I = :i, " } + // An update to the CCLA enabled flag if claGroupModel.ProjectCCLAEnabled != existingCLAGroup.ProjectCCLAEnabled { log.WithFields(f).Debugf("adding project_ccla_enabled: %t", claGroupModel.ProjectCCLAEnabled) expressionAttributeNames["#C"] = aws.String("project_ccla_enabled") @@ -716,6 +713,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #C = :c, " } + // An update to the CCLA requires ICLA flag if claGroupModel.ProjectCCLARequiresICLA != existingCLAGroup.ProjectCCLARequiresICLA { log.WithFields(f).Debugf("adding project_ccla_requires_icla_signature: %t", claGroupModel.ProjectCCLARequiresICLA) expressionAttributeNames["#CI"] = aws.String("project_ccla_requires_icla_signature") @@ -723,6 +721,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #CI = :ci, " } + // An update to the project live flag if claGroupModel.ProjectLive != existingCLAGroup.ProjectLive { log.WithFields(f).Debugf("adding project_live: %t", claGroupModel.ProjectLive) expressionAttributeNames["#PL"] = aws.String("project_live") @@ -730,6 +729,7 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG updateExpression = updateExpression + " #PL = :pl, " } + // We'll update the date modified time _, currentTimeString := utils.CurrentTime() log.WithFields(f).Debugf("adding date_modified: %s", currentTimeString) expressionAttributeNames["#M"] = aws.String("date_modified") @@ -748,7 +748,6 @@ func (repo *repo) UpdateCLAGroup(ctx context.Context, claGroupModel *models.ClaG UpdateExpression: &updateExpression, TableName: aws.String(repo.claGroupTable), } - //log.Debugf("Update input: %+V", updateInput.GoString()) // Make the DynamoDB Update API call _, updateErr := repo.dynamoDBClient.UpdateItem(updateInput) diff --git a/cla-backend-go/project/service.go b/cla-backend-go/project/service.go index 551f5136a..f998eec85 100644 --- a/cla-backend-go/project/service.go +++ b/cla-backend-go/project/service.go @@ -323,7 +323,7 @@ func (s service) GetClaGroupsByFoundationSFID(ctx context.Context, foundationSFI return s.repo.GetClaGroupsByFoundationSFID(ctx, foundationSFID, loadRepoDetails) } -// GetClaGroupByProjectSFID( service method +// GetClaGroupByProjectSFID service method func (s service) GetClaGroupByProjectSFID(ctx context.Context, projectSFID string, loadRepoDetails bool) (*models.ClaGroup, error) { return s.repo.GetClaGroupByProjectSFID(ctx, projectSFID, loadRepoDetails) } diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index e0c8aaeeb..51601dd53 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -286,6 +286,7 @@ func (s *service) UpdateCLAGroup(ctx context.Context, authUser *auth.User, claGr ProjectACL: claGroupModel.ProjectACL, ProjectICLAEnabled: claGroupModel.ProjectICLAEnabled, ProjectCCLAEnabled: claGroupModel.ProjectCCLAEnabled, + ProjectTemplateID: claGroupModel.ProjectTemplateID, ProjectCCLARequiresICLA: claGroupModel.ProjectCCLARequiresICLA, ProjectIndividualDocuments: claGroupModel.ProjectIndividualDocuments, ProjectCorporateDocuments: claGroupModel.ProjectCorporateDocuments, From 7ee0904639833b94ffa5bb55004bc9b05a44ae0d Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 23 Jul 2021 19:13:09 -0500 Subject: [PATCH 0368/1276] Resolved Event Log Unit Test Issues (#3052) Signed-off-by: David Deal --- cla-backend-go/events/event_data.go | 7 +++---- cla-backend-go/events/event_data_test.go | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 22f52c5fd..7a2a7247c 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -1027,7 +1027,7 @@ func (ed *CLAGroupUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (s if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } - return data, true + return data + ".", true } // GetEventDetailsString returns the details string for this event @@ -1939,8 +1939,7 @@ func (ed *CLAGroupCreatedEventData) GetEventSummaryString(args *LogEventArgs) (s if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } - data = data + "." - return data, true + return data + ".", true } // GetEventSummaryString returns the summary string for this event @@ -1973,7 +1972,7 @@ func (ed *CLAGroupUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (s if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } - return data, true + return data + ".", true } // GetEventSummaryString returns the summary string for this event diff --git a/cla-backend-go/events/event_data_test.go b/cla-backend-go/events/event_data_test.go index e81d3d27c..4f6bd1440 100644 --- a/cla-backend-go/events/event_data_test.go +++ b/cla-backend-go/events/event_data_test.go @@ -30,7 +30,7 @@ func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { eventData: &CLAGroupUpdatedEventData{ NewClaGroupName: "updatedNameValue", }, - summaryStr: "The CLA Group name was updated to : updatedNameValue by the user john.", + summaryStr: "The CLA Group name was updated to 'updatedNameValue' by the user john.", }, { name: "only name updated but old description still passed", @@ -39,14 +39,14 @@ func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { NewClaGroupDescription: "oldDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - summaryStr: "The CLA Group name was updated to : updatedNameValue by the user john.", + summaryStr: "The CLA Group name was updated to 'updatedNameValue' by the user john.", }, { name: "only description updated", eventData: &CLAGroupUpdatedEventData{ NewClaGroupDescription: "updatedDescriptionValue", }, - summaryStr: "The CLA Group description was updated to : updatedDescriptionValue by the user john.", + summaryStr: "The CLA Group description was updated to 'updatedDescriptionValue' by the user john.", }, { name: "only description updated but old name still passed", @@ -55,7 +55,7 @@ func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { NewClaGroupName: "oldNameValue", OldClaGroupName: "oldNameValue", }, - summaryStr: "The CLA Group description was updated to : updatedDescriptionValue by the user john.", + summaryStr: "The CLA Group description was updated to 'updatedDescriptionValue' by the user john.", }, { name: "name and description updated", @@ -63,7 +63,7 @@ func TestCLAGroupUpdatedEventData_GetEventSummaryString(t *testing.T) { NewClaGroupName: "updatedNameValue", NewClaGroupDescription: "updatedDescriptionValue", }, - summaryStr: "The CLA Group name was updated to : updatedNameValue and the description was updated to : updatedDescriptionValue by the user john.", + summaryStr: "The CLA Group name was updated to 'updatedNameValue' and the description was updated to 'updatedDescriptionValue' by the user john.", }, } @@ -86,7 +86,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { { name: "empty", eventData: &CLAGroupUpdatedEventData{}, - detailStr: "CLA Group ID: projectIDValue was updated by the user john.", + detailStr: "The CLA Group was updated by the user john.", }, { name: "only name updated", @@ -94,7 +94,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupName: "updatedNameValue", OldClaGroupName: "oldNameValue", }, - detailStr: "CLA Group ID: projectIDValue was updated with Name from : oldNameValue to : updatedNameValue by the user john.", + detailStr: "The CLA Group name was updated to 'updatedNameValue' by the user john.", }, { name: "only name updated but old description still passed", @@ -104,7 +104,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupDescription: "oldDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - detailStr: "CLA Group ID: projectIDValue was updated with Name from : oldNameValue to : updatedNameValue by the user john.", + detailStr: "The CLA Group name was updated to 'updatedNameValue' by the user john.", }, { name: "only description updated", @@ -112,7 +112,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupDescription: "updatedDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - detailStr: "CLA Group ID: projectIDValue was updated with Description from : oldDescriptionValue to : updatedDescriptionValue by the user john.", + detailStr: "The CLA Group description was updated to 'updatedDescriptionValue' by the user john.", }, { name: "only description updated but old name still passed", @@ -122,7 +122,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupName: "oldNameValue", OldClaGroupName: "oldNameValue", }, - detailStr: "CLA Group ID: projectIDValue was updated with Description from : oldDescriptionValue to : updatedDescriptionValue by the user john.", + detailStr: "The CLA Group description was updated to 'updatedDescriptionValue' by the user john.", }, { name: "name and description updated", @@ -132,7 +132,7 @@ func TestCLAGroupUpdatedEventData_GetEventDetailsString(t *testing.T) { NewClaGroupDescription: "updatedDescriptionValue", OldClaGroupDescription: "oldDescriptionValue", }, - detailStr: "CLA Group ID: projectIDValue was updated with Name from : oldNameValue to : updatedNameValue, Description from : oldDescriptionValue to : updatedDescriptionValue by the user john.", + detailStr: "The CLA Group name was updated to 'updatedNameValue' and the description was updated to 'updatedDescriptionValue' by the user john.", }, } From 2f9e7bfc2c630f5a8cdef5d5f8c16b1ffa3f7497 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 26 Jul 2021 21:37:38 +0300 Subject: [PATCH 0369/1276] Feature/gitlab integration poc (#3053) --- cla-backend-go/auth/authorizer.go | 2 + .../cmd/dynamo_events_lambda/main.go | 4 + cla-backend-go/cmd/gitlab/api/main.go | 117 ++++++ cla-backend-go/cmd/gitlab/auth/main.go | 312 ++++++++++++++++ cla-backend-go/cmd/gitlab/webhook/main.go | 88 +++++ cla-backend-go/cmd/server.go | 10 + cla-backend-go/config/config.go | 12 + cla-backend-go/config/ssm.go | 17 + cla-backend-go/events/event_data.go | 114 ++++++ cla-backend-go/events/event_types.go | 4 + cla-backend-go/gitlab/auth.go | 33 ++ cla-backend-go/gitlab/client.go | 141 +++++++ cla-backend-go/gitlab/client_test.go | 57 +++ cla-backend-go/gitlab/init.go | 15 + cla-backend-go/gitlab/organization.go | 26 ++ cla-backend-go/go.mod | 19 +- cla-backend-go/go.sum | 107 ++++-- cla-backend-go/serverless.yml | 4 + cla-backend-go/swagger/cla.v2.yaml | 175 ++++++++- .../common/create-github-organization.yaml | 4 +- .../swagger/common/gitlab-organization.yaml | 82 ++++ .../swagger/common/gitlab-organizations.yaml | 9 + cla-backend-go/utils/const.go | 15 + .../utils/utils_user_auth_standalone.go | 2 + .../v2/github_organizations/service.go | 27 +- .../v2/gitlab_organizations/handlers.go | 206 ++++++++++ .../v2/gitlab_organizations/models.go | 50 +++ .../v2/gitlab_organizations/repository.go | 352 ++++++++++++++++++ .../v2/gitlab_organizations/service.go | 266 +++++++++++++ 29 files changed, 2208 insertions(+), 62 deletions(-) create mode 100644 cla-backend-go/cmd/gitlab/api/main.go create mode 100644 cla-backend-go/cmd/gitlab/auth/main.go create mode 100644 cla-backend-go/cmd/gitlab/webhook/main.go create mode 100644 cla-backend-go/gitlab/auth.go create mode 100644 cla-backend-go/gitlab/client.go create mode 100644 cla-backend-go/gitlab/client_test.go create mode 100644 cla-backend-go/gitlab/init.go create mode 100644 cla-backend-go/gitlab/organization.go create mode 100644 cla-backend-go/swagger/common/gitlab-organization.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-organizations.yaml create mode 100644 cla-backend-go/utils/const.go create mode 100644 cla-backend-go/v2/gitlab_organizations/handlers.go create mode 100644 cla-backend-go/v2/gitlab_organizations/models.go create mode 100644 cla-backend-go/v2/gitlab_organizations/repository.go create mode 100644 cla-backend-go/v2/gitlab_organizations/service.go diff --git a/cla-backend-go/auth/authorizer.go b/cla-backend-go/auth/authorizer.go index 49569d2b3..76532c094 100644 --- a/cla-backend-go/auth/authorizer.go +++ b/cla-backend-go/auth/authorizer.go @@ -125,6 +125,7 @@ func (a Authorizer) SecurityAuth(token string, scopes []string) (*user.CLAUser, } return nil, err } + //log.WithFields(f).Debugf("user loaded : %+v with scopes : %+v", lfuser, scopes) for _, scope := range scopes { switch Scope(scope) { @@ -151,5 +152,6 @@ func (a Authorizer) SecurityAuth(token string, scopes []string) (*user.CLAUser, } } + //log.WithFields(f).Debugf("returning user from auth : %+v", lfuser) return &lfuser, nil } diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 8dbf6d764..72edafe5f 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -8,6 +8,8 @@ import ( "encoding/json" "os" + "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/communitybridge/easycla/cla-backend-go/github_organizations" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -91,6 +93,8 @@ func init() { token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) + // initialize gitlab + gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) user_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) project_service.InitClient(configFile.APIGatewayURL) diff --git a/cla-backend-go/cmd/gitlab/api/main.go b/cla-backend-go/cmd/gitlab/api/main.go new file mode 100644 index 000000000..dec0f60aa --- /dev/null +++ b/cla-backend-go/cmd/gitlab/api/main.go @@ -0,0 +1,117 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "flag" + "fmt" + "os" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/xanzy/go-gitlab" +) + +const ( + ProjectsURL = "https://gitlab.com/api/v4/projects" +) + +var state = flag.String("state", "failed", "the state of the MR to set") + +func main() { + flag.Parse() + + access_token := os.Getenv("GITLAB_ACCESS_TOKEN") + if access_token == "" { + log.Fatal("GITLAB_ACCESS_TOKEN is required") + } + + log.Infof("The gitlab access token is : %s", access_token) + + gitlabClient, err := gitlab.NewOAuthClient(access_token) + if err != nil { + log.Fatalf("creating client failed : %v", err) + } + + user, _, err := gitlabClient.Users.CurrentUser() + if err != nil { + log.Fatalf("fetching current user failed : %v", err) + } + + log.Infof("fetched current user : %s", user.Name) + + projects, _, err := gitlabClient.Projects.ListUserProjects(user.ID, &gitlab.ListProjectsOptions{}) + if err != nil { + log.Fatalf("listing projects failed : %v", err) + } + log.Printf("we fetched : %d projects for the account", len(projects)) + for _, p := range projects { + log.Println("Name : ", p.Name) + log.Println("ID: ", p.ID) + } + + projectID := 28118160 + commitSha := "f7036ab67a4e464e83e16af0b02d447c53fffa74" + + statuses, _, err := gitlabClient.Commits.GetCommitStatuses(projectID, commitSha, + &gitlab.GetCommitStatusesOptions{}) + if err != nil { + log.Fatalf("fetching commit statuses failed : %v", err) + } + + if len(statuses) == 0 { + log.Infof("no statuses found for commit sha") + setState := gitlab.Failed + if *state != string(gitlab.Failed) { + setState = gitlab.Success + } + + _, _, err = gitlabClient.Commits.SetCommitStatus(projectID, commitSha, &gitlab.SetCommitStatusOptions{ + State: setState, + Name: gitlab.String("easyCLA Bot"), + Description: gitlab.String(getDescription(setState)), + TargetURL: gitlab.String(getTargetURL("deniskurov@gmail.com")), + }) + if err != nil { + log.Fatalf("setting commit status for the sha failed : %v", err) + } + + statuses, _, err = gitlabClient.Commits.GetCommitStatuses(projectID, commitSha, + &gitlab.GetCommitStatusesOptions{}) + if err != nil { + log.Fatalf("fetching commit statuses failed : %v", err) + } + + } + + for _, status := range statuses { + log.Println("Status : ", status.Status) + if status.Status != *state { + log.Infof("setting state of commit sha to %s", *state) + _, _, err = gitlabClient.Commits.SetCommitStatus(projectID, commitSha, &gitlab.SetCommitStatusOptions{ + State: gitlab.BuildStateValue(*state), + Name: gitlab.String("easyCLA Bot"), + Description: gitlab.String(getDescription(gitlab.BuildStateValue(*state))), + TargetURL: gitlab.String(getTargetURL("deniskurov@gmail.com")), + }) + if err != nil { + log.Fatalf("setting commit status for the sha failed : %v", err) + } + } + log.Println("Status Name : ", status.Name) + log.Println("Status Description : ", status.Description) + log.Println("Status Author : ", status.Author.Name) + log.Println("Status Author Email : ", status.Author.Email) + } +} + +func getDescription(status gitlab.BuildStateValue) string { + if status == gitlab.Failed { + return "User hasn't signed CLA" + } + return "User signed CLA" +} + +func getTargetURL(email string) string { + return fmt.Sprintf("http://localhost:8080/gitlab/sign/%s", email) +} diff --git a/cla-backend-go/cmd/gitlab/auth/main.go b/cla-backend-go/cmd/gitlab/auth/main.go new file mode 100644 index 000000000..798a07ef6 --- /dev/null +++ b/cla-backend-go/cmd/gitlab/auth/main.go @@ -0,0 +1,312 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "strconv" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/gin-gonic/gin" + "github.com/go-resty/resty/v2" + "github.com/xanzy/go-gitlab" +) + +const ( + REDIRECT_URI = "http://localhost:8080/gitlab/oauth/callback" + APPLICATION_ID = "18718b478096e6a257eda51414d0d446ad28866c15187aa765f602fe906d0b17" + APPLICATION_SECRET = "8dd14ace0eb0e4674b849b6fed4ce51bbcc456fc62d9149aff15353c1dda6327" +) + +const ( + hookURL = "https://4c1ba3f4f3c1.ngrok.io/gitlab/events" +) + +type OauthSuccessResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + CreatedAt int `json:"created_at"` +} + +var passingUsers = map[string]bool{ + "deniskurov@gmail.com": true, +} + +func main() { + r := gin.Default() + r.GET("/gitlab/sign", func(c *gin.Context) { + email := c.Query("email") + if email == "" { + c.JSON(400, gin.H{ + "message": "email is required parameter", + }) + return + } + + projectID := c.Query("project_id") + if projectID == "" { + c.JSON(400, gin.H{ + "message": "projectID is required parameter", + }) + return + } + + lastCommitSha := c.Query("sha") + if lastCommitSha == "" { + c.JSON(400, gin.H{ + "message": "sha is required parameter", + }) + return + } + + projectIDInt, err := strconv.Atoi(projectID) + if err != nil { + log.Error("project id conversion failed ", err) + c.JSON(400, gin.H{ + "message": "project id conversion", + }) + return + } + + if err := setCommitStatus(projectIDInt, lastCommitSha, email, string(gitlab.Success)); err != nil { + log.Error("setting commit status failed", err) + c.JSON(500, gin.H{ + "message": "setting commit status failed", + }) + return + } + + log.Infof("email to sign is : %s", email) + log.Infof("project id : %s, sha : %s", projectID, lastCommitSha) + + c.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("user : %s, signed for project : %s", email, projectID), + }) + + }) + + r.POST("/gitlab/events", func(c *gin.Context) { + jsonData, err := ioutil.ReadAll(c.Request.Body) + event, err := gitlab.ParseWebhook(gitlab.EventTypeMergeRequest, jsonData) + if err != nil { + log.Error("parsing json body failed", err) + c.JSON(400, gin.H{ + "message": "code is required parameter", + }) + return + } + + mergeEvent, ok := event.(*gitlab.MergeEvent) + if !ok { + c.JSON(400, gin.H{ + "message": "type cast failed", + }) + return + } + + if mergeEvent.ObjectAttributes.State != "opened" { + c.JSON(200, gin.H{ + "message": "only interested in opened events", + }) + return + } + + projectName := mergeEvent.Project.Name + projectID := mergeEvent.Project.ID + + mergeID := mergeEvent.ObjectAttributes.IID + lastCommitSha := mergeEvent.ObjectAttributes.LastCommit.ID + lastCommitMessage := mergeEvent.ObjectAttributes.LastCommit.Message + + authorName := mergeEvent.ObjectAttributes.LastCommit.Author.Name + authorEmail := mergeEvent.ObjectAttributes.LastCommit.Author.Email + + log.Printf("Received MR (%d) for Project %s:%d", mergeID, projectName, projectID) + log.Printf("last commit : %s : %s", lastCommitSha, lastCommitMessage) + log.Printf("author name : %s, author email : %s", authorName, authorEmail) + + if err := setCommitStatus(projectID, lastCommitSha, authorEmail, ""); err != nil { + log.Error("setting commit status failed", err) + c.JSON(500, gin.H{ + "message": "setting commit status failed", + }) + return + } + + //empJSON, err := json.MarshalIndent(mergeEvent, "", " ") + //if err != nil { + // log.Fatalf(err.Error()) + //} + //fmt.Printf("MarshalIndent funnction output %s\n", string(empJSON)) + c.JSON(http.StatusOK, gin.H{}) + + }) + r.GET("/gitlab/oauth/callback", func(c *gin.Context) { + code := c.Query("code") + if code == "" { + c.JSON(400, gin.H{ + "message": "code is required parameter", + }) + return + } + + state := c.Query("state") + if state == "" { + c.JSON(400, gin.H{ + "message": "state is required parameter", + }) + return + } + log.Printf("received code : %s, STATE: %s", code, state) + + client := resty.New() + params := map[string]string{ + "client_id": APPLICATION_ID, + "client_secret": APPLICATION_SECRET, + "code": code, + "grant_type": "authorization_code", + "redirect_uri": REDIRECT_URI, + } + + resp, err := client.R(). + SetQueryParams(params). + SetResult(&OauthSuccessResponse{}). + Post("https://gitlab.com/oauth/token") + + if err != nil { + c.JSON(500, gin.H{ + "message": fmt.Sprintf("getting the token failed : %v", err), + }) + return + } + + result := resp.Result().(*OauthSuccessResponse) + accessToken := result.AccessToken + + err = registerWebHooksForUserProjects(accessToken) + if err != nil { + log.Error("register webhook ", err) + } + + respData := gin.H{ + "message": "OK", + "data": result, + } + + if err != nil { + respData["error"] = err + } + + c.JSON(200, respData) + }) + r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") +} + +func registerWebHooksForUserProjects(accessToken string) error { + gitlabClient, err := gitlab.NewOAuthClient(accessToken) + if err != nil { + return fmt.Errorf("creating client failed : %v", err) + } + + user, _, err := gitlabClient.Users.CurrentUser() + if err != nil { + return fmt.Errorf("fetching current user failed : %v", err) + } + + log.Infof("fetched current user : %s", user.Name) + + projects, _, err := gitlabClient.Projects.ListUserProjects(user.ID, &gitlab.ListProjectsOptions{}) + if err != nil { + return fmt.Errorf("listing projects failed : %v", err) + } + + log.Printf("we fetched : %d projects for the account", len(projects)) + + for _, p := range projects { + log.Println("**********************") + log.Println("Name : ", p.Name) + log.Println("ID: ", p.ID) + log.Infof("adding webhook to the project : %s (%d)", p.Name, p.ID) + if err := addCLAHookToProject(gitlabClient, p.ID); err != nil { + return fmt.Errorf("adding hook to the project : %s (%d) failed : %v", p.Name, p.ID, err) + } + } + + return nil +} + +func addCLAHookToProject(gitlabClient *gitlab.Client, projectID int) error { + _, _, err := gitlabClient.Projects.AddProjectHook(projectID, &gitlab.AddProjectHookOptions{ + URL: gitlab.String(hookURL), + MergeRequestsEvents: gitlab.Bool(true), + EnableSSLVerification: gitlab.Bool(false), + }) + return err +} + +func setCommitStatus(projectID interface{}, commitSha string, userEmail string, forceState string) error { + accessToken := os.Getenv("GITLAB_ACCESS_TOKEN") + if accessToken == "" { + return fmt.Errorf("GITLAB_ACCESS_TOKEN is required") + } + + gitlabClient, err := gitlab.NewOAuthClient(accessToken) + if err != nil { + return fmt.Errorf("creating client failed : %v", err) + } + + setState := gitlab.Failed + + if forceState == "" { + if passingUsers[userEmail] { + setState = gitlab.Success + } + } else { + setState = gitlab.BuildStateValue(forceState) + } + + options := &gitlab.SetCommitStatusOptions{ + State: setState, + Name: gitlab.String("easyCLA Bot"), + Description: gitlab.String(getDescription(setState)), + } + + if setState == gitlab.Failed { + options.TargetURL = gitlab.String(getTargetURL(projectID, commitSha, userEmail)) + } + + _, _, err = gitlabClient.Commits.SetCommitStatus(projectID, commitSha, options) + if err != nil { + return fmt.Errorf("setting commit status for the sha failed : %v", err) + } + + return nil +} + +func getDescription(status gitlab.BuildStateValue) string { + if status == gitlab.Failed { + return "User hasn't signed CLA" + } + return "User signed CLA" +} + +func getTargetURL(projectID interface{}, lastCommitSha, email string) string { + base := "http://localhost:8080/gitlab/sign" + + projectIDInt := projectID.(int) + projectIDStr := strconv.Itoa(projectIDInt) + + params := url.Values{} + params.Add("project_id", projectIDStr) + params.Add("sha", lastCommitSha) + params.Add("email", email) + + return base + "?" + params.Encode() +} diff --git a/cla-backend-go/cmd/gitlab/webhook/main.go b/cla-backend-go/cmd/gitlab/webhook/main.go new file mode 100644 index 000000000..b7a187b98 --- /dev/null +++ b/cla-backend-go/cmd/gitlab/webhook/main.go @@ -0,0 +1,88 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "os" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/xanzy/go-gitlab" +) + +const ( + hookURL = "https://7e182f2774e2.ngrok.io/gitlab/events" +) + +func main() { + log.Println("register webhook") + access_token := os.Getenv("GITLAB_ACCESS_TOKEN") + if access_token == "" { + log.Fatal("GITLAB_ACCESS_TOKEN is required") + } + + log.Infof("The gitlab access token is : %s", access_token) + + gitlabClient, err := gitlab.NewOAuthClient(access_token) + if err != nil { + log.Fatalf("creating client failed : %v", err) + } + + user, _, err := gitlabClient.Users.CurrentUser() + if err != nil { + log.Fatalf("fetching current user failed : %v", err) + } + + log.Infof("fetched current user : %s", user.Name) + + projects, _, err := gitlabClient.Projects.ListUserProjects(user.ID, &gitlab.ListProjectsOptions{}) + if err != nil { + log.Fatalf("listing projects failed : %v", err) + } + log.Printf("we fetched : %d projects for the account", len(projects)) + for _, p := range projects { + log.Println("**********************") + log.Println("Name : ", p.Name) + log.Println("ID: ", p.ID) + hooks, _, err := gitlabClient.Projects.ListProjectHooks(p.ID, &gitlab.ListProjectHooksOptions{}) + + if err != nil { + log.Fatalf("fetching hooks for project : %s, failed : %v", p.Name, err) + } + + var claHookFound bool + for _, hook := range hooks { + log.Println("**********************") + log.Infof("hook ID : %d", hook.ID) + log.Infof("URL : %s", hook.URL) + log.Infof("Merge Request Events Enabled : %v", hook.MergeRequestsEvents) + log.Infof("Enable SSL Verification : %v", hook.EnableSSLVerification) + + if hookURL == hook.URL { + claHookFound = true + break + } + } + + if claHookFound { + log.Infof("CLA Hook was found nothing to do") + continue + } + + log.Infof("adding webhook to the project : %s (%d)", p.Name, p.ID) + if err := addCLAHookToProject(gitlabClient, p.ID); err != nil { + log.Fatalf("adding hook to the project : %s (%d) failed : %v", p.Name, p.ID, err) + } + + } + +} + +func addCLAHookToProject(gitlabClient *gitlab.Client, projectID int) error { + _, _, err := gitlabClient.Projects.AddProjectHook(projectID, &gitlab.AddProjectHookOptions{ + URL: gitlab.String(hookURL), + MergeRequestsEvents: gitlab.Bool(true), + EnableSSLVerification: gitlab.Bool(false), + }) + return err +} diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 4cf1af8be..148d0d181 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -14,6 +14,10 @@ import ( "strconv" "strings" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + + "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/communitybridge/easycla/cla-backend-go/emails" "github.com/communitybridge/easycla/cla-backend-go/v2/dynamo_events" @@ -226,7 +230,10 @@ func server(localMode bool) http.Handler { if err != nil { logrus.Panic(err) } + // initialize github github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) + // initialize gitlab + gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) @@ -241,6 +248,7 @@ func server(localMode bool) http.Handler { v1CLAGroupRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, v1ProjectClaGroupRepo) metricsRepo := metrics.NewRepository(awsSession, stage, configFile.APIGatewayURL, v1ProjectClaGroupRepo) githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) + gitlabOrganizationRepo := gitlab_organizations.NewRepository(awsSession, stage) claManagerReqRepo := cla_manager.NewRepository(awsSession, stage) // Our service layer handlers @@ -291,6 +299,7 @@ func server(localMode bool) http.Handler { authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo) + gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v1ProjectClaGroupRepo) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, githubOrganizationsRepo, eventsService, autoEnableService, emailService) @@ -330,6 +339,7 @@ func server(localMode bool) http.Handler { v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) + gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 2d3629674..07c6d58dd 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -51,6 +51,9 @@ type Config struct { // GitHub Application GitHub GitHub `json:"github"` + // Gitlab Application + Gitlab Gitlab `json:"gitlab"` + // Dynamo Session Store SessionStoreTableName string `json:"sessionStoreTableName"` @@ -134,6 +137,15 @@ type GitHub struct { TestRepositoryID string `json:"test_repository_id"` } +// Gitlab model +type Gitlab struct { + ClientSecret string `json:"clientSecret"` + AppID string `json:"app_id"` + AppPrivateKey string `json:"app_private_key"` + RedirectURI string `json:"redirect_uri"` + WebHookURI string `json:"web_hook_uri"` +} + // MetricsReport keeps the config needed to send the metrics data report type MetricsReport struct { AwsSQSRegion string `json:"aws_sqs_region"` diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 06d83a970..4d98e3f1e 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -70,6 +70,11 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-gh-test-organization-installation-id-%s", stage), fmt.Sprintf("cla-gh-test-repository-%s", stage), fmt.Sprintf("cla-gh-test-repository-id-%s", stage), + fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage), + fmt.Sprintf("cla-gitlab-app-id-%s", stage), + fmt.Sprintf("cla-gitlab-app-private-key-%s", stage), + fmt.Sprintf("cla-gitlab-app-redirect-uri-%s", stage), + fmt.Sprintf("cla-gitlab-app-web-hook-uri-%s", stage), fmt.Sprintf("cla-corporate-base-%s", stage), fmt.Sprintf("cla-corporate-v1-base-%s", stage), fmt.Sprintf("cla-corporate-v2-base-%s", stage), @@ -150,6 +155,18 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint case fmt.Sprintf("cla-gh-test-repository-id-%s", stage): config.GitHub.TestRepositoryID = resp.value + // gitlab ssm + case fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage): + config.Gitlab.ClientSecret = resp.value + case fmt.Sprintf("cla-gitlab-app-id-%s", stage): + config.Gitlab.AppID = resp.value + case fmt.Sprintf("cla-gitlab-app-private-key-%s", stage): + config.Gitlab.AppPrivateKey = resp.value + case fmt.Sprintf("cla-gitlab-app-redirect-uri-%s", stage): + config.Gitlab.RedirectURI = resp.value + case fmt.Sprintf("cla-gitlab-app-web-hook-uri-%s", stage): + config.Gitlab.WebHookURI = resp.value + case fmt.Sprintf("cla-corporate-base-%s", stage): config.CorporateConsoleURL = resp.value case fmt.Sprintf("cla-corporate-v1-base-%s", stage): diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 7a2a7247c..47954707b 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -191,6 +191,26 @@ type GitHubOrganizationUpdatedEventData struct { BranchProtectionEnabled bool } +// GitlabOrganizationAddedEventData data model +type GitlabOrganizationAddedEventData struct { + GitlabOrganizationName string + AutoEnabled bool + AutoEnabledClaGroupID string + BranchProtectionEnabled bool +} + +// GitlabOrganizationDeletedEventData data model +type GitlabOrganizationDeletedEventData struct { + GitlabOrganizationName string +} + +// GitlabOrganizationUpdatedEventData data model +type GitlabOrganizationUpdatedEventData struct { + GitlabOrganizationName string + AutoEnabled bool + AutoEnabledClaGroupID string +} + // CCLAApprovalListRequestCreatedEventData data model type CCLAApprovalListRequestCreatedEventData struct { RequestID string @@ -652,6 +672,44 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEve return data, true } +// GetEventDetailsString returns the details string for this event +func (ed *GitlabOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("Gitlab Organization: %s was added with auto-enabled: %t, with branch protection enabled: %t", + ed.GitlabOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) + if ed.AutoEnabledClaGroupID != "" { + data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventDetailsString returns the details string for this event +func (ed *GitlabOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("GitHub Organization: %s was deleted ", ed.GitlabOrganizationName) + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventDetailsString returns the details string for this event +func (ed *GitlabOrganizationUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("GitHub Organization:%s was updated with auto-enabled: %t", + ed.GitlabOrganizationName, ed.AutoEnabled) + if ed.AutoEnabledClaGroupID != "" { + data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventDetailsString returns the details string for this event func (ed *CCLAApprovalListRequestApprovedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("User: %s approved a CCLA Approval Request for Project: %s and Company: %s with Request ID: %s.", @@ -1558,6 +1616,62 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEve return data, true } +// GetEventSummaryString returns the summary string for this event +func (ed *GitlabOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab organization %s was added with auto-enabled set to %t with branch protection enabled set to %t", + ed.GitlabOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) + if ed.AutoEnabledClaGroupID != "" { + data = data + fmt.Sprintf(" with auto-enabled-cla-group set to %s", ed.AutoEnabledClaGroupID) + } + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString returns the summary string for this event +func (ed *GitlabOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab organization %s was deleted", ed.GitlabOrganizationName) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString returns the summary string for this event +func (ed *GitlabOrganizationUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("Gitlab Organization: %s was updated with auto-enabled: %t", + ed.GitlabOrganizationName, ed.AutoEnabled) + if ed.AutoEnabledClaGroupID != "" { + data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + } + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for project %s", args.ProjectName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventSummaryString returns the summary string for this event func (ed *CCLAApprovalListRequestApprovedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s approved a CCLA approval request", args.UserName) diff --git a/cla-backend-go/events/event_types.go b/cla-backend-go/events/event_types.go index 4f36d5459..85cffeea7 100644 --- a/cla-backend-go/events/event_types.go +++ b/cla-backend-go/events/event_types.go @@ -49,6 +49,10 @@ const ( GitHubOrganizationDeleted = "github_organization.deleted" GitHubOrganizationUpdated = "github_organization.updated" + GitlabOrganizationAdded = "gitlab_organization.added" + GitlabOrganizationDeleted = "gitlab_organization.deleted" + GitlabOrganizationUpdated = "gitlab_organization.updated" + CompanyACLUserAdded = "company_acl.user_added" CompanyACLRequestAdded = "company_acl.request_added" CompanyACLRequestApproved = "company_acl.request_approved" diff --git a/cla-backend-go/gitlab/auth.go b/cla-backend-go/gitlab/auth.go new file mode 100644 index 000000000..d9da3a218 --- /dev/null +++ b/cla-backend-go/gitlab/auth.go @@ -0,0 +1,33 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/go-resty/resty/v2" +) + +// FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) +func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { + client := resty.New() + params := map[string]string{ + "client_id": config.GetConfig().Gitlab.AppID, + "client_secret": config.GetConfig().Gitlab.ClientSecret, + "code": code, + "grant_type": "authorization_code", + "redirect_uri": config.GetConfig().Gitlab.RedirectURI, + //"redirect_uri": "http://localhost:8080/v4/gitlab/oauth/callback", + } + + resp, err := client.R(). + SetQueryParams(params). + SetResult(&OauthSuccessResponse{}). + Post("https://gitlab.com/oauth/token") + + if err != nil { + return nil, err + } + + return resp.Result().(*OauthSuccessResponse), nil +} diff --git a/cla-backend-go/gitlab/client.go b/cla-backend-go/gitlab/client.go new file mode 100644 index 000000000..d04757245 --- /dev/null +++ b/cla-backend-go/gitlab/client.go @@ -0,0 +1,141 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + + "github.com/xanzy/go-gitlab" +) + +// OauthSuccessResponse is success response from Gitlab +type OauthSuccessResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + CreatedAt int `json:"created_at"` +} + +// NewGitlabOauthClient creates a new gitlab client from the given oauth info, authInfo is encrypted +func NewGitlabOauthClient(authInfo string) (*gitlab.Client, error) { + oauthResp, err := DecryptAuthInfo(authInfo) + if err != nil { + return nil, err + } + + log.Infof("creating oauth client with access token : %s", oauthResp.AccessToken) + return gitlab.NewOAuthClient(oauthResp.AccessToken) +} + +// EncryptAuthInfo encrypts the oauth response into a string +func EncryptAuthInfo(oauthResp *OauthSuccessResponse) (string, error) { + key := getGitlabAppPrivateKey() + keyDecoded, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", fmt.Errorf("decode key : %v", err) + } + + b, err := json.Marshal(oauthResp) + if err != nil { + return "", fmt.Errorf("oauth resp json marshall : %v", err) + } + authInfo := string(b) + //log.Infof("auth info before encrypting : %s", authInfo) + + encrypted, err := encrypt(keyDecoded, []byte(authInfo)) + if err != nil { + return "", fmt.Errorf("encrypt failed : %v", err) + } + + return hex.EncodeToString(encrypted), nil +} + +// DecryptAuthInfo decrytps the authinfo into OauthSuccessResponse data structure +func DecryptAuthInfo(authInfoEncoded string) (*OauthSuccessResponse, error) { + ciphertext, err := hex.DecodeString(authInfoEncoded) + if err != nil { + return nil, fmt.Errorf("decode auth info %s : %v", authInfoEncoded, err) + } + + //log.Infof("auth info decoded : %s", ciphertext) + + key := getGitlabAppPrivateKey() + keyDecoded, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, fmt.Errorf("decode key : %v", err) + } + + //log.Debugf("before decrypt : keyDecoded : %s, cipherText : %s", keyDecoded, ciphertext) + decrypted, err := decrypt(keyDecoded, ciphertext) + if err != nil { + return nil, fmt.Errorf("decrypt failed : %v", err) + } + //log.Debugf("after decrypt : keyDecoded : %s, decrypted : %s", keyDecoded, decrypted) + + var oauthResp OauthSuccessResponse + if err := json.Unmarshal(decrypted, &oauthResp); err != nil { + return nil, fmt.Errorf("unmarshall auth info : %v", err) + } + + return &oauthResp, nil +} + +func encrypt(key, message []byte) ([]byte, error) { + // Initialize block cipher + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Create the byte slice that will hold encrypted message + cipherText := make([]byte, aes.BlockSize+len(message)) + + // Generate the Initialization Vector (IV) nonce + // which is stored at the beginning of the byte slice + // The IV is the same length as the AES blocksize + iv := cipherText[:aes.BlockSize] + _, err = io.ReadFull(rand.Reader, iv) + if err != nil { + return nil, err + } + + // Choose the block cipher mode of operation + // Using the cipher feedback (CFB) mode here. + // CBCEncrypter also available. + cfb := cipher.NewCFBEncrypter(block, iv) + // Generate the encrypted message and store it + // in the remaining bytes after the IV nonce + cfb.XORKeyStream(cipherText[aes.BlockSize:], message) + + return cipherText, nil +} + +// AES decryption +func decrypt(key, cipherText []byte) ([]byte, error) { + // Initialize block cipher + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Separate the IV nonce from the encrypted message bytes + iv := cipherText[:aes.BlockSize] + cipherText = cipherText[aes.BlockSize:] + + // Decrypt the message using the CFB block mode + cfb := cipher.NewCFBDecrypter(block, iv) + cfb.XORKeyStream(cipherText, cipherText) + + return cipherText, nil +} diff --git a/cla-backend-go/gitlab/client_test.go b/cla-backend-go/gitlab/client_test.go new file mode 100644 index 000000000..46c994c2c --- /dev/null +++ b/cla-backend-go/gitlab/client_test.go @@ -0,0 +1,57 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +var key = "0WqnDWHnZKo2cmQ8m93EtY9ZBpfzQW4UnnEuRmgtJKM=" +var oauthRespStr = `{"access_token":"a30671b8749ba5d48925712344377f11a5aba43ec630f099e464b9843796e6a6","token_type":"Bearer","expires_in":0,"refresh_token":"0838a31d0d796973eacefdf513523e6e47aa06fac9d26622964da1e473509458","created_at":1626435922}` + +func TestNewGitlabOauthClient(t *testing.T) { + Init("124453345", key) + t.Cleanup(func() { + gitlabAppPrivateKey = "" + }) + + t.Logf("app private key is : %s", getGitlabAppPrivateKey()) + + var oauthResp OauthSuccessResponse + err := json.Unmarshal([]byte(oauthRespStr), &oauthResp) + assert.NoError(t, err) + + encrypted, err := EncryptAuthInfo(&oauthResp) + assert.NoError(t, err) + + client, err := NewGitlabOauthClient(encrypted) + assert.NoError(t, err) + assert.NotNil(t, client) +} + +func TestEncryptDecryptAuthInfo(t *testing.T) { + Init("124453345", key) + t.Cleanup(func() { + gitlabAppPrivateKey = "" + }) + + t.Logf("app private key is : %s", getGitlabAppPrivateKey()) + + var oauthResp OauthSuccessResponse + err := json.Unmarshal([]byte(oauthRespStr), &oauthResp) + assert.NoError(t, err) + t.Logf("unmarshall ok : %+v", oauthResp) + + encrypted, err := EncryptAuthInfo(&oauthResp) + assert.NoError(t, err) + t.Logf("encrypted auth info : %s", encrypted) + + oauthRespDecrypted, err := DecryptAuthInfo(encrypted) + assert.NoError(t, err) + + assert.Equal(t, &oauthResp, oauthRespDecrypted) +} diff --git a/cla-backend-go/gitlab/init.go b/cla-backend-go/gitlab/init.go new file mode 100644 index 000000000..b390e6124 --- /dev/null +++ b/cla-backend-go/gitlab/init.go @@ -0,0 +1,15 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +var gitlabAppPrivateKey string + +// Init initializes the required gitlab variables +func Init(glAppID string, glAppPrivateKey string) { + gitlabAppPrivateKey = glAppPrivateKey +} + +func getGitlabAppPrivateKey() string { + return gitlabAppPrivateKey +} diff --git a/cla-backend-go/gitlab/organization.go b/cla-backend-go/gitlab/organization.go new file mode 100644 index 000000000..5144bd4ad --- /dev/null +++ b/cla-backend-go/gitlab/organization.go @@ -0,0 +1,26 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "fmt" + + "github.com/xanzy/go-gitlab" +) + +// GetGroupByName gets a gitlab Group by the given name +func GetGroupByName(client *gitlab.Client, name string) (*gitlab.Group, error) { + groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{}) + if err != nil { + return nil, fmt.Errorf("fetching groups failed : %v", err) + } + + for _, group := range groups { + if group.Name == name { + return group, nil + } + } + + return nil, nil +} diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 106261197..6e9d455f7 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -20,6 +20,7 @@ require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fnproject/fdk-go v0.0.2 github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gin-gonic/gin v1.7.2 github.com/go-openapi/errors v0.19.6 github.com/go-openapi/loads v0.19.5 github.com/go-openapi/runtime v0.19.19 @@ -27,10 +28,11 @@ require ( github.com/go-openapi/strfmt v0.19.5 github.com/go-openapi/swag v0.19.9 github.com/go-openapi/validate v0.19.10 + github.com/go-playground/validator/v10 v10.7.0 // indirect github.com/go-resty/resty/v2 v2.3.0 github.com/gofrs/uuid v4.0.0+incompatible github.com/golang/mock v1.4.4 - github.com/golang/protobuf v1.4.3 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-github/v33 v33.0.0 github.com/google/uuid v1.1.4 github.com/gorilla/sessions v1.2.1 // indirect @@ -38,9 +40,12 @@ require ( github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 + github.com/json-iterator/go v1.1.11 // indirect github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f // indirect github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 github.com/kr/pretty v0.2.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.13 // indirect github.com/mitchellh/mapstructure v1.3.2 github.com/mozillazg/request v0.8.0 // indirect github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc @@ -57,13 +62,19 @@ require ( github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 + github.com/ugorji/go v1.2.6 // indirect github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a + github.com/xanzy/go-gitlab v0.50.1 go.uber.org/ratelimit v0.1.0 - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/text v0.3.6 // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/appengine v1.6.6 // indirect - google.golang.org/protobuf v1.24.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/ini.v1 v1.57.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 7e1875a77..e5bde5d75 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -80,8 +80,6 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= @@ -113,10 +111,6 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= @@ -126,10 +120,12 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e h1:5CNDPg63TSvbNi3viqFnIywPu2TqLMlWAPuFuuTrFe4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= +github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -206,6 +202,15 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNPlDK+Yk= github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.7.0 h1:gLi5ajTBBheLNt0ctewgq7eolXoDALQd5/y90Hh9ZgM= +github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= @@ -267,14 +272,15 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -283,14 +289,17 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM= github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -334,12 +343,16 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= @@ -390,8 +403,10 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -434,6 +449,9 @@ github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8 github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= @@ -452,8 +470,10 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -475,8 +495,10 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= @@ -520,9 +542,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -604,8 +625,13 @@ github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4 h1:euf5tLM++W5h5uyfs6NSMoCGGhw+hRXMLE/DU6hireM= github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= +github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= +github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= @@ -616,6 +642,8 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= +github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= +github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= @@ -654,8 +682,9 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -703,9 +732,12 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= @@ -717,8 +749,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -741,17 +774,25 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -768,7 +809,6 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -793,6 +833,7 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -806,25 +847,21 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -845,8 +882,9 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= @@ -854,7 +892,6 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index a92ccab80..60cf10ac0 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -134,6 +134,7 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" - Effect: Allow Action: - dynamodb:Query @@ -193,6 +194,9 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" environment: STAGE: ${self:provider.stage} diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 06a4ac8b5..27131ee98 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1604,6 +1604,81 @@ paths: tags: - github-repositories + /project/{projectSFID}/gitlab/organizations: + post: + summary: Add new Gitlab Organization in the project + description: Endpoint to create a new Gitlab Organization in EasyCLA + operationId: addProjectGitlabOrganization + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + - in: body + name: body + schema: + $ref: '#/definitions/create-gitlab-organization' + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gitlab-organization' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-organizations + get: + summary: Get the Gitlab organizations of the project + description: Endpoint to return the list of Gitlab organization for the project + operationId: getProjectGitlabOrganizations + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/project-gitlab-organizations' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-organizations + /cla-group/{claGroupID}/icla/signatures: get: summary: List individual signatures for CLA Group @@ -3665,6 +3740,43 @@ paths: tags: - github-activity + /gitlab/oauth/callback: + get: + summary: The endpoint is called after user authorizes EasyCLA bot + description: The endpoint is responsible for storing the access token for the user and registering the webhooks is autoenable is on + security: [] + operationId: gitlabOauthCallback + parameters: + - name: code + description: oauth code used to fetch the access token + in: query + type: string + required: true + - name: state + description: state is used to find the gitlab organization + in: query + type: string + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/success-response' + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-activity + responses: unauthorized: description: Unauthorized @@ -3825,7 +3937,7 @@ parameters: in: query type: string required: false - enum: [ccla,ecla,cla] + enum: [ ccla,ecla,cla ] claType: name: claType description: > @@ -3836,7 +3948,7 @@ parameters: in: query type: string required: false - enum: [ccla,ecla,icla] + enum: [ ccla,ecla,icla ] templateCLAType: name: claType in: query @@ -4119,6 +4231,12 @@ definitions: update-github-organization: $ref: './common/update-github-organization.yaml' + gitlab-organization: + $ref: './common/gitlab-organization.yaml' + + create-gitlab-organization: + $ref: './common/create-github-organization.yaml' + user: $ref: './common/user.yaml' @@ -5139,6 +5257,9 @@ definitions: items: type: string + gitlab-organizations: + $ref: './common/gitlab-organizations.yaml' + project-github-organizations: type: object properties: @@ -5190,6 +5311,56 @@ definitions: items: $ref: '#/definitions/project-github-repository' + project-gitlab-organizations: + type: object + properties: + list: + type: array + items: + $ref: '#/definitions/project-gitlab-organization' + + project-gitlab-organization: + type: object + properties: + auto_enabled: + type: boolean + description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. + x-omitempty: false + autoEnableCLAGroupID: + type: string + description: The CLA Group ID which is attached to the auto-enabled flag + autoEnabledCLAGroupName: + type: string + description: The CLA Group name which is attached to the auto-enabled flag + branchProtectionEnabled: + type: boolean + description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. + x-omitempty: false + installationURL: + type: string + x-nullable: true + format: uri + gitlab_organization_name: + type: string + description: The Gitlab Organization name + example: "kubernetes" + # Pattern aligns with UI and other platform services including Org Service + # \w Any word character (alphanumeric & underscore), dashes, periods + pattern: '^([\w\-\.]+){2,255}$' + minLength: 2 + maxLength: 255 + connection_status: + type: string + enum: + - connected + - partial_connection + - connection_failure + - no_connection + repositories: + type: array + items: + $ref: '#/definitions/project-github-repository' + project-github-repository: type: object properties: diff --git a/cla-backend-go/swagger/common/create-github-organization.yaml b/cla-backend-go/swagger/common/create-github-organization.yaml index 56e32e3ab..1432b16ad 100644 --- a/cla-backend-go/swagger/common/create-github-organization.yaml +++ b/cla-backend-go/swagger/common/create-github-organization.yaml @@ -7,7 +7,7 @@ required: properties: organizationName: type: string - description: The GitHub Organization name + description: The Organization name example: "kubernetes" # Pattern aligns with UI and other platform services including Org Service # \w Any word character (alphanumeric & underscore), dashes, periods @@ -23,5 +23,5 @@ properties: description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. branchProtectionEnabled: type: boolean - description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. + description: Flag to indicate if this Organization is configured to automatically setup branch protection on CLA enabled repositories. default: false diff --git a/cla-backend-go/swagger/common/gitlab-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization.yaml new file mode 100644 index 000000000..e344805bf --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-organization.yaml @@ -0,0 +1,82 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + organizationID: + type: string + description: internal id of the gitlab organization + dateCreated: + type: string + example: "2020-02-06T09:31:49.245630+0000" + minLength: 18 + maxLength: 64 + dateModified: + type: string + example: "2020-02-06T09:31:49.245646+0000" + minLength: 18 + maxLength: 64 + organizationName: + type: string + example: "communitybridge" + organizationSfid: + type: string + example: "a0941000002wBz4AAA" + version: + type: string + example: "v1" + projectSFID: + type: string + example: "a0941000002wBz4AAA" + enabled: + type: boolean + description: Flag that indicates whether this Gitlab Organization is active + x-omitempty: false + connected: + type: boolean + description: Flag that indicates whether this Gitlab Organization is authorized with Gitlab, if false it might mean that Gitlab Oauth process is not compeleted yet or the token was revoked and user needs to go through the auth process again + x-omitempty: false + autoEnabled: + type: boolean + description: Flag to indicate if this Gitlab Organization is configured to allow new repositories to be auto-enabled/auto-enrolled in EasyCLA. + x-omitempty: false + autoEnabledClaGroupID: + type: string + description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. + gitlabInfo: + type: object + properties: + error: + type: string + example: "unable to get gitlab info of communitybridge" + details: + type: object + properties: + id: + type: integer + x-nullable: true + example: 1476068 + bio: + type: string + x-nullable: true + htmlUrl: + type: string + x-nullable: true + example: "https://github.com/communitybridge" + format: uri + installationURL: + type: string + x-nullable: true + description: "if the Gitlab Organization is not connected yet can use this url to go through the process of authorizing the easyCLA bot" + format: uri + + repositories: + type: object + properties: + error: + type: string + example: "unable to get repositories for installation id : 6854001" + list: + type: array + items: + $ref: '#/definitions/github-repository-info' diff --git a/cla-backend-go/swagger/common/gitlab-organizations.yaml b/cla-backend-go/swagger/common/gitlab-organizations.yaml new file mode 100644 index 000000000..42a292a78 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-organizations.yaml @@ -0,0 +1,9 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + list: + type: array + items: + $ref: '#/definitions/gitlab-organization' diff --git a/cla-backend-go/utils/const.go b/cla-backend-go/utils/const.go new file mode 100644 index 000000000..bc69f3844 --- /dev/null +++ b/cla-backend-go/utils/const.go @@ -0,0 +1,15 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +const ( + // Connected status + Connected = "connected" + // PartialConnection status + PartialConnection = "partial_connection" + // ConnectionFailure status + ConnectionFailure = "connection_failure" + // NoConnection status + NoConnection = "no_connection" +) diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index 8654d9d7e..85a61676f 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -87,6 +87,8 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec "adminScopeAllowed": adminScopeAllowed, } + log.WithFields(f).Debugf("checking user auth for project tree") + // If we are running locally and want to disable permission checks if skipPermissionChecks() { log.WithFields(f).Debug("skipping permissions check") diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 3655538ba..e4ccf784b 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -62,17 +62,6 @@ func NewService(repo v1GithubOrg.RepositoryInterface, ghRepository v1Repositorie } } -const ( - // Connected status - Connected = "connected" - // PartialConnection status - PartialConnection = "partial_connection" - // ConnectionFailure status - ConnectionFailure = "connection_failure" - // NoConnection status - NoConnection = "no_connection" -) - func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGithubOrganizations, error) { f := logrus.Fields{ "functionName": "v2.github_organizations.service.GetGitHubOrganizations", @@ -166,12 +155,12 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) orgmap[org.OrganizationName] = rorg out.List = append(out.List, rorg) if org.OrganizationInstallationID == 0 { - rorg.ConnectionStatus = NoConnection + rorg.ConnectionStatus = utils.NoConnection } else { if org.Repositories.Error != "" { - rorg.ConnectionStatus = ConnectionFailure + rorg.ConnectionStatus = utils.ConnectionFailure } else { - rorg.ConnectionStatus = Connected + rorg.ConnectionStatus = utils.Connected } } } @@ -208,7 +197,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) log.WithFields(f).WithError(err).Warn("repository github id is not integer") } rorg.Repositories = append(rorg.Repositories, &models.ProjectGithubRepository{ - ConnectionStatus: Connected, + ConnectionStatus: utils.Connected, Enabled: repo.Enabled, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, @@ -223,7 +212,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) delete(connectedRepo, key) } else { rorg.Repositories = append(rorg.Repositories, &models.ProjectGithubRepository{ - ConnectionStatus: ConnectionFailure, + ConnectionStatus: utils.ConnectionFailure, Enabled: repo.Enabled, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, @@ -231,8 +220,8 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) ProjectID: repo.ProjectSFID, ParentProjectID: repo.RepositorySfdcID, }) - if rorg.ConnectionStatus == Connected { - rorg.ConnectionStatus = PartialConnection + if rorg.ConnectionStatus == utils.Connected { + rorg.ConnectionStatus = utils.PartialConnection } } } @@ -244,7 +233,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) continue } rorg.Repositories = append(rorg.Repositories, &models.ProjectGithubRepository{ - ConnectionStatus: Connected, + ConnectionStatus: utils.Connected, Enabled: false, RepositoryID: "", RepositoryName: notEnabledRepo.repoInfo.RepositoryName, diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go new file mode 100644 index 000000000..ef77190ba --- /dev/null +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -0,0 +1,206 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_organizations + +import ( + "context" + "fmt" + "strings" + + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" + "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/gofrs/uuid" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/sirupsen/logrus" + + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_organizations" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/go-openapi/runtime/middleware" +) + +// Configure setups handlers on api with service +func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service) { + + api.GitlabOrganizationsGetProjectGitlabOrganizationsHandler = gitlab_organizations.GetProjectGitlabOrganizationsHandlerFunc( + func(params gitlab_organizations.GetProjectGitlabOrganizationsParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + + f := logrus.Fields{ + "functionName": "gitlab_organizations.handlers.GitlabOrganizationsGetProjectGitlabOrganizationsHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to Get Project GitHub Organizations with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewGetProjectGitlabOrganizationsForbidden().WithPayload( + utils.ErrorResponseForbidden(reqID, msg)) + } + + result, err := service.GetGitlabOrganizations(ctx, params.ProjectSFID) + if err != nil { + if strings.ContainsAny(err.Error(), "getProjectNotFound") { + msg := fmt.Sprintf("Gitlab organization with project SFID not found: %s", params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewGetProjectGitlabOrganizationsNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, msg)) + } + + msg := fmt.Sprintf("failed to locate Gitlab organization by project SFID: %s, error: %+v", params.ProjectSFID, err) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewGetProjectGitlabOrganizationsBadRequest().WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + return gitlab_organizations.NewGetProjectGitlabOrganizationsOK().WithPayload(result) + }) + + api.GitlabOrganizationsAddProjectGitlabOrganizationHandler = gitlab_organizations.AddProjectGitlabOrganizationHandlerFunc( + func(params gitlab_organizations.AddProjectGitlabOrganizationParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + + f := logrus.Fields{ + "functionName": "Gitlab_organization.handlers.GitlabOrganizationsAddProjectGitlabOrganizationHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to Add Project Gitlab Organizations with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( + utils.ErrorResponseForbidden(reqID, msg)) + } + + // Quick check of the parameters + if params.Body == nil || params.Body.OrganizationName == nil { + msg := fmt.Sprintf("missing organization name in body: %+v", params.Body) + log.WithFields(f).Warn(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + f["organizationName"] = utils.StringValue(params.Body.OrganizationName) + + if params.Body.AutoEnabled == nil { + msg := fmt.Sprintf("missing autoEnabled name in body: %+v", params.Body) + log.WithFields(f).Warn(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + f["autoEnabled"] = utils.BoolValue(params.Body.AutoEnabled) + f["autoEnabledClaGroupID"] = params.Body.AutoEnabledClaGroupID + + if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + msg := "AutoEnabledClaGroupID can't be empty when AutoEnabled" + err := fmt.Errorf(msg) + log.WithFields(f).Warn(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + result, err := service.AddGitlabOrganization(ctx, params.ProjectSFID, params.Body) + if err != nil { + msg := fmt.Sprintf("unable to add github organization, error: %+v", err) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + // Log the event + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + LfUsername: authUser.UserName, + EventType: events.GitlabOrganizationAdded, + ProjectSFID: params.ProjectSFID, + EventData: &events.GitlabOrganizationAddedEventData{ + GitlabOrganizationName: *params.Body.OrganizationName, + }, + }) + + return gitlab_organizations.NewAddProjectGitlabOrganizationOK().WithPayload(result) + }) + + api.GitlabActivityGitlabOauthCallbackHandler = gitlab_activity.GitlabOauthCallbackHandlerFunc(func(params gitlab_activity.GitlabOauthCallbackParams) middleware.Responder { + f := logrus.Fields{ + "functionName": "gitlab_organization.handlers.GitlabActivityGitlabOauthCallbackHandler", + "code": params.Code, + "state": params.State, + } + + requestID, _ := uuid.NewV4() + reqID := requestID.String() + if params.Code == "" { + msg := "missing code parameter" + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + if params.State == "" { + msg := "missing state parameter" + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + codeParts := strings.Split(params.State, ":") + if len(codeParts) != 2 { + msg := fmt.Sprintf("invalid state variable passed : %s", params.State) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + gitlabOrganizationID := codeParts[0] + stateVar := codeParts[1] + + ctx := context.Background() + _, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) + if err != nil { + msg := fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + // now fetch the oauth credentials and store to db + oauthResp, err := gitlab.FetchOauthCredentials(params.Code) + if err != nil { + msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + log.Infof("oauth resp is like : %+v", oauthResp) + + err = service.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) + if err != nil { + msg := fmt.Sprintf("updating gitlab credentials failed : %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + return gitlab_activity.NewGitlabOauthCallbackOK().WithPayload(&models.SuccessResponse{ + Code: "200", + Message: "oauth credentials stored successfully", + XRequestID: reqID, + }) + }) +} diff --git a/cla-backend-go/v2/gitlab_organizations/models.go b/cla-backend-go/v2/gitlab_organizations/models.go new file mode 100644 index 000000000..2094a002b --- /dev/null +++ b/cla-backend-go/v2/gitlab_organizations/models.go @@ -0,0 +1,50 @@ +package gitlab_organizations + +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +import ( + models2 "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" +) + +// GitlabOrganization is data model for gitlab organizations +type GitlabOrganization struct { + OrganizationID string `json:"organization_id"` + DateCreated string `json:"date_created,omitempty"` + DateModified string `json:"date_modified,omitempty"` + OrganizationName string `json:"organization_name,omitempty"` + OrganizationNameLower string `json:"organization_name_lower,omitempty"` + OrganizationSFID string `json:"organization_sfid,omitempty"` + ProjectSFID string `json:"project_sfid"` + Enabled bool `json:"enabled"` + AutoEnabled bool `json:"auto_enabled"` + BranchProtectionEnabled bool `json:"branch_protection_enabled"` + AutoEnabledClaGroupID string `json:"auto_enabled_cla_group_id,omitempty"` + AuthInfo string `json:"auth_info"` + AuthState string `json:"auth_state"` + Version string `json:"version,omitempty"` +} + +// ToModel converts to models.GitlabOrganization +func ToModel(in *GitlabOrganization) *models2.GitlabOrganization { + return &models2.GitlabOrganization{ + OrganizationID: in.OrganizationID, + DateCreated: in.DateCreated, + DateModified: in.DateModified, + OrganizationName: in.OrganizationName, + OrganizationSfid: in.OrganizationSFID, + Version: in.Version, + Enabled: in.Enabled, + AutoEnabled: in.AutoEnabled, + AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, + ProjectSFID: in.ProjectSFID, + } +} + +func toModels(input []*GitlabOrganization) []*models2.GitlabOrganization { + out := make([]*models2.GitlabOrganization, 0) + for _, in := range input { + out = append(out, ToModel(in)) + } + return out +} diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go new file mode 100644 index 000000000..667726689 --- /dev/null +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -0,0 +1,352 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_organizations + +import ( + "context" + "errors" + "fmt" + "github.com/gofrs/uuid" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go/service/dynamodb/expression" + models2 "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" +) + +// indexes +const ( + GitlabOrgSFIDIndex = "gitlab-org-sfid-index" + GitlabOrgLowerNameIndex = "gitlab-organization-name-lower-search-index" + GitlabProjectSFIDOrganizationNameIndex = "gitlab-project-sfid-organization-name-index" +) + +// RepositoryInterface is interface for gitlab org data model +type RepositoryInterface interface { + AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.CreateGitlabOrganization) (*models2.GitlabOrganization, error) + GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) + GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) + UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error +} + +// Repository object/struct +type Repository struct { + stage string + dynamoDBClient *dynamodb.DynamoDB + gitlabOrgTableName string +} + +// NewRepository creates a new instance of the gitlabOrganizations repository +func NewRepository(awsSession *session.Session, stage string) RepositoryInterface { + return Repository{ + stage: stage, + dynamoDBClient: dynamodb.New(awsSession), + gitlabOrgTableName: fmt.Sprintf("cla-%s-gitlab-orgs", stage), + } +} + +func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.CreateGitlabOrganization) (*models2.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.repository.AddGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "parentProjectSFID": parentProjectSFID, + "projectSFID": projectSFID, + "organizationName": utils.StringValue(input.OrganizationName), + "autoEnabled": utils.BoolValue(input.AutoEnabled), + "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), + } + + // First, let's check to see if we have an existing gitlab organization with the same name + existingRecord, getErr := repo.GetGitlabOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) + if getErr != nil { + log.WithFields(f).WithError(getErr).Debug("unable to locate existing github organization by name") + } + + if existingRecord != nil && len(existingRecord.List) > 0 { + log.WithFields(f).Debugf("Existing github organization exists in our database, count: %d", len(existingRecord.List)) + if len(existingRecord.List) > 1 { + log.WithFields(f).Warning("more than one github organization with the same name in the database") + } + if parentProjectSFID == existingRecord.List[0].OrganizationSfid { + log.WithFields(f).Debug("Existing github organization with same parent SFID - should be able to update it") + } else { + log.WithFields(f).Debug("Existing github organization with different parent SFID - won't be able to update it - will return conflict") + } + return nil, fmt.Errorf("record already exists") + } + + // No existing records - create one + _, currentTime := utils.CurrentTime() + organizationID, err := uuid.NewV4() + if err != nil { + log.WithFields(f).WithError(err).Warnf("Unable to generate a UUID for gitlab org, error: %v", err) + return nil, err + } + + authStateNonce, err := uuid.NewV4() + if err != nil { + log.WithFields(f).WithError(err).Warnf("Unable to generate a auth nonce UUID for gitlab org, error: %v", err) + return nil, err + } + + enabled := false + gitlabOrg := &GitlabOrganization{ + OrganizationID: organizationID.String(), + DateCreated: currentTime, + DateModified: currentTime, + OrganizationName: *input.OrganizationName, + OrganizationNameLower: strings.ToLower(*input.OrganizationName), + OrganizationSFID: parentProjectSFID, + ProjectSFID: projectSFID, + Enabled: aws.BoolValue(&enabled), + AutoEnabled: aws.BoolValue(input.AutoEnabled), + AutoEnabledClaGroupID: input.AutoEnabledClaGroupID, + BranchProtectionEnabled: aws.BoolValue(input.BranchProtectionEnabled), + AuthState: authStateNonce.String(), + Version: "v1", + } + + log.WithFields(f).Debug("Encoding github organization record for adding to the database...") + av, err := dynamodbattribute.MarshalMap(gitlabOrg) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to marshall request for query") + return nil, err + } + + log.WithFields(f).Debug("Adding gitlab organization record to the database...") + _, err = repo.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ + Item: av, + TableName: aws.String(repo.gitlabOrgTableName), + ConditionExpression: aws.String("attribute_not_exists(organization_name)"), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeConditionalCheckFailedException: + log.WithFields(f).WithError(err).Warn("gitlab organization already exists") + return nil, fmt.Errorf("gitlab organization already exists") + } + } + log.WithFields(f).WithError(err).Warn("cannot put gitlab organization in dynamodb") + return nil, err + } + + return ToModel(gitlabOrg), nil +} + +// GetGitlabOrganizations get github organizations based on the project SFID +func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.repository.GetGitHubOrganizations", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + } + + condition := expression.Key("organization_sfid").Equal(expression.Value(projectSFID)) + builder := expression.NewBuilder().WithKeyCondition(condition) + + //filter := expression.Name("enabled").Equal(expression.Value(true)) + //builder = builder.WithFilter(filter) + + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + log.WithFields(f).Warnf("problem building query expression, error: %+v", err) + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.gitlabOrgTableName), + IndexName: aws.String(GitlabOrgSFIDIndex), + } + + results, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).Warnf("error retrieving github_organizations using project_sfid = %s. error = %s", projectSFID, err.Error()) + return nil, err + } + + if len(results.Items) == 0 { + log.WithFields(f).Debug("no results from query") + return &models2.GitlabOrganizations{ + List: []*models2.GitlabOrganization{}, + }, nil + } + + var resultOutput []*GitlabOrganization + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) + if err != nil { + return nil, err + } + + log.WithFields(f).Debug("building response model...") + gitlabOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) + return &models2.GitlabOrganizations{List: gitlabOrgList}, nil +} + +// GetGitlabOrganizationByName get github organization by name +func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganizations, error) { + f := logrus.Fields{ + "functionName": "v1.github_organizations.repository.GetGitHubOrganizationByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "githubOrganizationName": githubOrganizationName, + } + + condition := expression.Key("organization_name_lower").Equal(expression.Value(strings.ToLower(githubOrganizationName))) + builder := expression.NewBuilder().WithKeyCondition(condition) + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + return nil, err + } + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.gitlabOrgTableName), + IndexName: aws.String(GitlabOrgLowerNameIndex), + } + + log.WithFields(f).Debugf("querying for github organization by name using organization_name_lower=%s...", strings.ToLower(githubOrganizationName)) + results, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving github_organizations using githubOrganizationName = %s", githubOrganizationName) + return nil, err + } + if len(results.Items) == 0 { + log.WithFields(f).Debug("Unable to find github organization by name - no results") + return &models2.GitlabOrganizations{ + List: []*models2.GitlabOrganization{}, + }, nil + } + var resultOutput []*GitlabOrganization + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) + if err != nil { + log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) + return nil, err + } + + ghOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) + return &models2.GitlabOrganizations{List: ghOrgList}, nil +} + +// GetGitlabOrganization by organization name +func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "gitlab_organizations.repository.GetGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + } + + log.WithFields(f).Debug("Querying for github organization by name...") + result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "organization_id": { + S: aws.String(gitlabOrganizationID), + }, + }, + TableName: aws.String(repo.gitlabOrgTableName), + }) + if err != nil { + return nil, err + } + if len(result.Item) == 0 { + log.WithFields(f).Debug("Unable to find github organization by name - no results") + return nil, nil + } + + var org GitlabOrganization + err = dynamodbattribute.UnmarshalMap(result.Item, &org) + if err != nil { + log.WithFields(f).Warnf("error unmarshalling organization table data, error: %v", err) + return nil, err + } + return &org, nil +} + +// UpdateGitlabOrganizationAuth updates the specified Gitlab organization oauth info +func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error { + f := logrus.Fields{ + "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationAuth", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + "tableName": repo.gitlabOrgTableName, + } + + _, currentTime := utils.CurrentTime() + gitlabOrg, lookupErr := repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + if lookupErr != nil { + log.WithFields(f).Warnf("error looking up Gitlab organization by id, error: %+v", lookupErr) + return lookupErr + } + if gitlabOrg == nil { + lookupErr := errors.New("unable to lookup Gitlab organization by id") + log.WithFields(f).Warnf("error looking up Gitlab organization, error: %+v", lookupErr) + return lookupErr + } + + expressionAttributeNames := map[string]*string{ + "#A": aws.String("auth_info"), + "#M": aws.String("date_modified"), + } + expressionAttributeValues := map[string]*dynamodb.AttributeValue{ + ":a": { + S: aws.String(authInfo), + }, + ":m": { + S: aws.String(currentTime), + }, + } + updateExpression := "SET #A = :a, #M = :m" + + input := &dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "organization_id": { + S: aws.String(gitlabOrg.OrganizationID), + }, + }, + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + UpdateExpression: &updateExpression, + TableName: aws.String(repo.gitlabOrgTableName), + } + + log.WithFields(f).Debug("updating gitlab organization record...") + _, updateErr := repo.dynamoDBClient.UpdateItem(input) + if updateErr != nil { + log.WithFields(f).Warnf("unable to update Gitlab organization record, error: %+v", updateErr) + return updateErr + } + + return nil +} + +func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*GitlabOrganization) []*models2.GitlabOrganization { + f := logrus.Fields{ + "functionName": "buildGitlabOrganizationListModels", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + log.WithFields(f).Debugf("fetching gitlab info for the list") + // Convert the database model to a response model + return toModels(gitlabOrganizations) + + // TODO: Fetch the gitlab information +} diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go new file mode 100644 index 000000000..7ef3cf11a --- /dev/null +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -0,0 +1,266 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_organizations + +import ( + "context" + "fmt" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/go-openapi/strfmt" + "net/url" + "sort" + "strings" + + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + "github.com/communitybridge/easycla/cla-backend-go/gitlab" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/utils" + v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/sirupsen/logrus" +) + +// Service contains functions of GitlabOrganizations service +type Service interface { + GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGitlabOrganizations, error) + AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.CreateGitlabOrganization) (*models.GitlabOrganization, error) + GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) + GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) + UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error +} + +type service struct { + repo RepositoryInterface + projectsCLAGroupService projects_cla_groups.Repository +} + +// NewService creates a new githubOrganizations service +func NewService(repo RepositoryInterface, projectsCLAGroupService projects_cla_groups.Repository) Service { + return service{ + repo: repo, + projectsCLAGroupService: projectsCLAGroupService, + } +} + +func (s service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + } + + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) + dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + if err != nil { + return nil, err + } + + return ToModel(dbModel), nil +} + +func (s service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.UpdateGitlabOrganizationAuth", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + } + + log.WithFields(f).Debugf("updating gitlab org auth") + authInfoEncrypted, err := gitlab.EncryptAuthInfo(oauthResp) + if err != nil { + return fmt.Errorf("encrypt failed : %v", err) + } + + return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, authInfoEncrypted) + +} + +func (s service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + "authState": authState, + } + + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) + dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + if err != nil { + return nil, err + } + + if dbModel.AuthState != authState { + return nil, fmt.Errorf("auth state doesn't match") + } + + return ToModel(dbModel), nil +} + +func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.CreateGitlabOrganization) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.AddGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "autoEnabled": utils.BoolValue(input.AutoEnabled), + "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), + "organizationName": utils.StringValue(input.OrganizationName), + } + + log.WithFields(f).Debug("looking up project in project service...") + psc := v2ProjectService.GetClient() + project, err := psc.GetProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading project details from the project service") + return nil, err + } + + var parentProjectSFID string + if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && + (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { + parentProjectSFID = projectSFID + } else { + parentProjectSFID = utils.StringValue(project.Parent) + } + f["parentProjectSFID"] = parentProjectSFID + log.WithFields(f).Debug("located parentProjectID...") + + log.WithFields(f).Debug("adding github organization...") + resp, err := s.repo.AddGitlabOrganization(ctx, parentProjectSFID, projectSFID, input) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem adding github organization for project") + return nil, err + } + + return resp, nil +} + +func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGitlabOrganizations, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizations", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + } + + // Load the GitHub Organization and Repository details - result will be missing CLA Group info and ProjectSFID details + log.WithFields(f).Debugf("loading Gitlab organizations for projectSFID: %s", projectSFID) + orgs, err := s.repo.GetGitlabOrganizations(ctx, projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading gitlab organizations from the project service") + return nil, err + } + + psc := v2ProjectService.GetClient() + log.WithFields(f).Debug("loading project details from the project service...") + projectServiceRecord, err := psc.GetProject(projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading project details from the project service") + return nil, err + } + + var parentProjectSFID string + if utils.IsProjectHasRootParent(projectServiceRecord) { + parentProjectSFID = projectSFID + } else { + parentProjectSFID = utils.StringValue(projectServiceRecord.Parent) + } + f["parentProjectSFID"] = parentProjectSFID + log.WithFields(f).Debug("located parentProjectID...") + + // Our response model + out := &models.ProjectGitlabOrganizations{ + List: make([]*models.ProjectGitlabOrganization, 0), + } + + // Next, we need to load a bunch of additional data for the response including the github status (if it's still connected/live, not renamed/moved), the CLA Group details, etc. + + // A temp data model for holding the intermediate results + type gitlabRepoInfo struct { + orgName string + repoInfo *v1Models.GithubRepositoryInfo + } + + orgmap := make(map[string]*models.ProjectGitlabOrganization) + for _, org := range orgs.List { + autoEnabledCLAGroupName := "" + if org.AutoEnabledClaGroupID != "" { + log.WithFields(f).Debugf("Loading CLA Group by ID: %s to obtain the name for GitHub auth enabled CLA Group response", org.AutoEnabledClaGroupID) + claGroupMode, claGroupLookupErr := s.projectsCLAGroupService.GetCLAGroup(ctx, org.AutoEnabledClaGroupID) + if claGroupLookupErr != nil { + log.WithFields(f).WithError(claGroupLookupErr).Warnf("Unable to lookup CLA Group by ID: %s", org.AutoEnabledClaGroupID) + } + if claGroupMode != nil { + autoEnabledCLAGroupName = claGroupMode.ProjectName + } + } + + orgDetailed, err := s.repo.GetGitlabOrganization(ctx, org.OrganizationID) + if err != nil { + log.WithFields(f).Errorf("fetching gitlab org failed : %s : %v", org.OrganizationID, err) + continue + } + + installationURL := buildInstallationURL(org.OrganizationID, orgDetailed.AuthState) + rorg := &models.ProjectGitlabOrganization{ + AutoEnabled: org.AutoEnabled, + AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, + AutoEnabledCLAGroupName: autoEnabledCLAGroupName, + GitlabOrganizationName: org.OrganizationName, + Repositories: make([]*models.ProjectGithubRepository, 0), + InstallationURL: installationURL, + } + + if orgDetailed.AuthInfo == "" { + rorg.ConnectionStatus = utils.NoConnection + } else { + glClient, err := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo) + if err != nil { + log.WithFields(f).Errorf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) + rorg.ConnectionStatus = utils.ConnectionFailure + } else { + user, _, err := glClient.Users.CurrentUser() + if err != nil { + log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) + rorg.ConnectionStatus = utils.ConnectionFailure + } else { + log.WithFields(f).Debugf("connected to user : %s for gitlab org : %s", user.Name, org.OrganizationID) + rorg.ConnectionStatus = utils.Connected + } + } + } + + orgmap[org.OrganizationName] = rorg + out.List = append(out.List, rorg) + } + + // Sort everything nicely + sort.Slice(out.List, func(i, j int) bool { + return strings.ToLower(out.List[i].GitlabOrganizationName) < strings.ToLower(out.List[j].GitlabOrganizationName) + }) + for _, orgList := range out.List { + sort.Slice(orgList.Repositories, func(i, j int) bool { + return strings.ToLower(orgList.Repositories[i].RepositoryName) < strings.ToLower(orgList.Repositories[j].RepositoryName) + }) + } + + return out, nil +} + +func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI { + base := "https://gitlab.com/oauth/authorize" + c := config.GetConfig() + state := fmt.Sprintf("%s:%s", gitlabOrgID, authStateNonce) + + params := url.Values{} + params.Add("client_id", c.Gitlab.AppID) + params.Add("redirect_uri", c.Gitlab.RedirectURI) + //params.Add("redirect_uri", "http://localhost:8080/v4/gitlab/oauth/callback") + params.Add("response_type", "code") + params.Add("state", state) + params.Add("scope", "read_user read_api read_repository write_repository api") + + installationURL := strfmt.URI(base + "?" + params.Encode()) + return &installationURL + +} From e77b6453ada25acda1545188db9096b6628bd3d0 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 27 Jul 2021 18:54:51 +0300 Subject: [PATCH 0370/1276] gitlab org python dynamo models (#3059) Signed-off-by: makkalot --- cla-backend/cla/models/dynamo_models.py | 233 +++++++++++++++++- cla-backend/cla/models/model_interfaces.py | 46 ++++ .../cla/tests/unit/test_gitlab_org_models.py | 40 +++ 3 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 cla-backend/cla/tests/unit/test_gitlab_org_models.py diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 221106e6e..f50f85ff6 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -403,6 +403,56 @@ class Meta: organization_sfid = UnicodeAttribute(hash_key=True) +class GitlabOrgSFIndex(GlobalSecondaryIndex): + """ + This class represents a global secondary index for querying gitlab organizations by a Salesforce ID. + """ + + class Meta: + """Meta class for external ID github org index.""" + + index_name = "gitlab-org-sfid-index" + write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) + read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) + projection = AllProjection() + + organization_sfid = UnicodeAttribute(hash_key=True) + + +class GitlabOrgProjectSfidOrganizationNameIndex(GlobalSecondaryIndex): + """ + This class represents a global secondary index for querying gitlab organizations by a Project sfid and + Organization Name. + """ + + class Meta: + """Meta class for external ID github org index.""" + + index_name = "gitlab-project-sfid-organization-name-index" + write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) + read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) + projection = AllProjection() + + project_sfid = UnicodeAttribute(hash_key=True) + organization_name = UnicodeAttribute(range_key=True) + + +class GitlabOrganizationNameLowerIndex(GlobalSecondaryIndex): + """ + This class represents a global secondary index for querying gitlab organizations by Organization Name. + """ + + class Meta: + """Meta class for external ID github org index.""" + + index_name = "gitlab-organization-name-lower-search-index" + write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) + read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) + projection = AllProjection() + + organization_name_lower = UnicodeAttribute(hash_key=True) + + class GerritProjectIDIndex(GlobalSecondaryIndex): """ This class represents a global secondary index for querying gerrit's by the project ID @@ -2943,7 +2993,8 @@ def get_employee_signature_by_company_project(self, company_id, project_id, user "Why do we have more than one employee signature for this user? - Will return the first one only.") return signatures[0] - def get_employee_signature_by_company_project_list(self, company_id, project_id, user_id) -> Optional[List[Signature]]: + def get_employee_signature_by_company_project_list(self, company_id, project_id, user_id) -> Optional[ + List[Signature]]: """ Returns the employee signature for the specified user associated with the project/company. Returns None if no employee signature exists for @@ -3535,10 +3586,34 @@ def get_expire_timestamp(self): return exp_datetime.timestamp() +class GitlabOrgModel(BaseModel): + """ + Represents a Gitlab Organization in the database. + """ + + class Meta: + table_name = "cla-{}-gitlab-orgs".format(stage) + if stage == "local": + host = "http://localhost:8000" + + organization_id = UnicodeAttribute(hash_key=True) + organization_name = UnicodeAttribute(null=True) + organization_name_lower = UnicodeAttribute(null=True) + organization_sfid = UnicodeAttribute() + project_sfid = UnicodeAttribute() + organization_sfid_index = GitlabOrgSFIndex() + project_sfid_organization_name_index = GitlabOrgProjectSfidOrganizationNameIndex() + organization_name_lowe_index = GitlabOrganizationNameLowerIndex() + auto_enabled = BooleanAttribute(null=True) + auto_enabled_cla_group_id = UnicodeAttribute(null=True) + branch_protection_enabled = BooleanAttribute(null=True) + enabled = BooleanAttribute(null=True) + note = UnicodeAttribute(null=True) + + class GitHubOrgModel(BaseModel): """ - Represents a Github Organization in the database. - Company_id, project_id are deprecated now that organizations are under an SFDC ID. + Represents a Gitlab Organization in the database. """ class Meta: @@ -3553,7 +3628,9 @@ class Meta: organization_installation_id = NumberAttribute(null=True) organization_sfid = UnicodeAttribute() project_sfid = UnicodeAttribute() - organization_sfid_index = GithubOrgSFIndex() + organization_sfid_index = GitlabOrgSFIndex() + project_sfid_organization_name_index = GitlabOrgProjectSfidOrganizationNameIndex() + organization_name_lowe_index = GitlabOrganizationNameLowerIndex() organization_project_id = UnicodeAttribute(null=True) organization_company_id = UnicodeAttribute(null=True) auto_enabled = BooleanAttribute(null=True) @@ -3646,7 +3723,7 @@ def get_note(self): :rtype: str """ return self.model.note - + def get_enabled(self): return self.model.enabled @@ -3678,7 +3755,7 @@ def set_branch_protection_enabled(self, branch_protection_enabled): def set_note(self, note): self.model.note = note - + def set_enabled(self, enabled): self.model.enabled = enabled @@ -3717,6 +3794,150 @@ def all(self): return ret +class GitlabOrg(model_interfaces.GitlabOrg): # pylint: disable=too-many-public-methods + """ + ORM-agnostic wrapper for the DynamoDB GitlabOrg model. + """ + + def __init__( + self, organization_id=None, organization_name=None, organization_sfid=None, + project_sfid=None, auto_enabled=False, branch_protection_enabled=False, note=None, enabled=True + ): + super(GitlabOrg).__init__() + self.model = GitlabOrgModel() + if not organization_id: + organization_id = str(uuid.uuid4()) + self.model.organization_id = organization_id + + self.model.organization_name = organization_name + if self.model.organization_name: + self.model.organization_name_lower = self.model.organization_name.lower() + + self.model.organization_sfid = organization_sfid + self.model.project_sfid = project_sfid + self.model.auto_enabled = auto_enabled + self.model.branch_protection_enabled = branch_protection_enabled + self.model.enabled = enabled + self.model.note = note + + def __str__(self): + return ( + f'organization id:{self.model.organization_id}, ' + f'organization name:{self.model.organization_name}, ' + f'organization SFID: {self.model.organization_sfid}, ' + f'auto_enabled: {self.model.auto_enabled},' + f'branch_protection_enabled: {self.model.branch_protection_enabled},' + f'enabled: {self.model.enabled},' + f'note: {self.model.note}' + ) + + def to_dict(self): + ret = dict(self.model) + if ret["organization_sfid"] == "null": + ret["organization_sfid"] = None + return ret + + def save(self) -> None: + self.model.date_modified = datetime.datetime.utcnow() + self.model.save() + + def load(self, organization_id: str): + try: + organization = self.model.get(organization_id) + except GitlabOrgModel.DoesNotExist: + raise cla.models.DoesNotExist("Gitlab Org not found") + self.model = organization + + def delete(self): + self.model.delete() + + def get_organization_id(self): + return self.model.organization_id + + def get_organization_name(self): + return self.model.organization_name + + def get_organization_sfid(self): + return self.model.organization_sfid + + def get_project_sfid(self): + return self.model.project_sfid + + def get_organization_name_lower(self): + return self.model.organization_name_lower + + def get_auto_enabled(self): + return self.model.auto_enabled + + def get_branch_protection_enabled(self): + return self.model.branch_protection_enabled + + def get_note(self): + """ + Getter for the note. + :return: the note value for the github organization record + :rtype: str + """ + return self.model.note + + def get_enabled(self): + return self.model.enabled + + def set_organization_name(self, organization_name): + self.model.organization_name = organization_name + if self.model.organization_name: + self.model.organization_name_lower = self.model.organization_name.lower() + + def set_organization_sfid(self, organization_sfid): + self.model.organization_sfid = organization_sfid + + def set_project_sfid(self, project_sfid): + self.model.project_sfid = project_sfid + + def set_organization_name_lower(self, organization_name_lower): + self.model.organization_name_lower = organization_name_lower + + def set_auto_enabled(self, auto_enabled): + self.model.auto_enabled = auto_enabled + + def set_branch_protection_enabled(self, branch_protection_enabled): + self.model.branch_protection_enabled = branch_protection_enabled + + def set_note(self, note): + self.model.note = note + + def set_enabled(self, enabled): + self.model.enabled = enabled + + def get_organization_by_sfid(self, sfid) -> List: + organization_generator = self.model.organization_sfid_index.query(sfid) + organizations = [] + for org_model in organization_generator: + org = GitlabOrg() + org.model = org_model + organizations.append(org) + return organizations + + def get_organization_by_lower_name(self, organization_name): + organization_name = organization_name.lower() + organization_generator = self.model.organization_name_lowe_index.query(organization_name) + organizations = [] + for org_model in organization_generator: + org = GitlabOrg() + org.model = org_model + organizations.append(org) + return organizations + + def all(self): + orgs = self.model.scan() + ret = [] + for organization in orgs: + org = GitlabOrg() + org.model = organization + ret.append(org) + return ret + + class GerritModel(BaseModel): """ Represents a Gerrit Instance in the database. diff --git a/cla-backend/cla/models/model_interfaces.py b/cla-backend/cla/models/model_interfaces.py index 49665dacc..0dce58e9d 100644 --- a/cla-backend/cla/models/model_interfaces.py +++ b/cla-backend/cla/models/model_interfaces.py @@ -1788,6 +1788,52 @@ def set_document_tab_height(self, tab_height): raise NotImplementedError() +class GitlabOrg(object): + """ + Interface to the GitlabOrg model + """ + + def to_dict(self): + """ + Converts models to dictionaries for JSON serialization. + + :return: A dict representation of the model. + :rtype: dict + """ + raise NotImplementedError() + + def save(self): + """ + Simple abstraction around the supported ORMs to save a model. + """ + raise NotImplementedError() + + def load(self, organization_id): + """ + Simple abstraction around the supported ORMs to load a model. + Should populate the current object. + + :param organization_id: The gitlab organization's ID. + :type organization_id: string + """ + raise NotImplementedError() + + def delete(self): + """ + Simple abstraction around the supported ORMs to delete a model. + """ + raise NotImplementedError() + + def all(self): + """ + Fetches all gitlab organizations in the CLA system. + + :return: A list of GitlabOrg objects. + :rtype: [cla.models.model_interfaces.GitlabOrg] + """ + raise NotImplementedError() + + class GitHubOrg(object): """ Interface to the GitHubOrg model. diff --git a/cla-backend/cla/tests/unit/test_gitlab_org_models.py b/cla-backend/cla/tests/unit/test_gitlab_org_models.py new file mode 100644 index 000000000..e0c3ed85a --- /dev/null +++ b/cla-backend/cla/tests/unit/test_gitlab_org_models.py @@ -0,0 +1,40 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +from cla.models.dynamo_models import GitlabOrg + + +def test_gitlab_org_model(): + gitlab_org = GitlabOrg(organization_name="GitlabOrg1") + assert gitlab_org.get_organization_id() + assert gitlab_org.get_organization_name() == "GitlabOrg1" + assert gitlab_org.get_organization_name_lower() == "gitlaborg1" + assert not gitlab_org.get_auto_enabled() + assert gitlab_org.get_enabled() + assert not gitlab_org.get_branch_protection_enabled() + assert not gitlab_org.get_project_sfid() + assert not gitlab_org.get_organization_sfid() + + gitlab_org.set_organization_name("GitlabOrg2") + assert gitlab_org.get_organization_name() == "GitlabOrg2" + assert gitlab_org.get_organization_name_lower() == "gitlaborg2" + + gitlab_org.set_enabled(False) + assert not gitlab_org.get_enabled() + + gitlab_org.set_project_sfid("project_sfid_1") + assert gitlab_org.get_project_sfid() == "project_sfid_1" + + gitlab_org.set_organization_sfid("organization_sfid_1") + assert gitlab_org.get_organization_sfid() == "organization_sfid_1" + + gitlab_org.set_branch_protection_enabled(True) + assert gitlab_org.get_branch_protection_enabled() + gitlab_org.set_auto_enabled(True) + assert gitlab_org.get_auto_enabled() + + gitlab_org_dict = gitlab_org.to_dict() + assert gitlab_org_dict["organization_id"] == gitlab_org.get_organization_id() + assert gitlab_org_dict["organization_name"] == "GitlabOrg2" + assert gitlab_org_dict["project_sfid"] == "project_sfid_1" + assert gitlab_org_dict["organization_sfid"] == "organization_sfid_1" From 71377777784d9b431acf045501a8159dddbb8756 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 27 Jul 2021 15:30:47 -0700 Subject: [PATCH 0371/1276] Added Logic to Resolve Inconsistent Nil Slice In API Response (#3065) --- cla-backend-go/Makefile | 5 +- .../github/branch_protection/interfaces.go | 2 +- .../github/branch_protection/mock.go | 2 +- .../branch_protection/protected_branch.go | 2 +- .../protected_branch_limiter.go | 2 +- cla-backend-go/github/client.go | 2 +- cla-backend-go/github/github_installation.go | 8 +- cla-backend-go/github/github_org.go | 2 +- cla-backend-go/github/github_repository.go | 2 +- cla-backend-go/github/github_user.go | 2 +- cla-backend-go/github/handlers.go | 2 +- cla-backend-go/go.mod | 32 +- cla-backend-go/go.sum | 515 +++++++++++------- cla-backend-go/signatures/converters.go | 8 +- cla-backend-go/signatures/service.go | 2 +- cla-backend-go/tests/utils_conversion_test.go | 28 + cla-backend-go/utils/conversion.go | 9 + cla-backend-go/v2/dynamo_events/autoenable.go | 2 +- cla-backend-go/v2/github_activity/handlers.go | 2 +- cla-backend-go/v2/github_activity/service.go | 2 +- .../v2/github_activity/service_test.go | 2 +- .../v2/gitlab_organizations/repository.go | 3 +- .../v2/gitlab_organizations/service.go | 5 +- 23 files changed, 405 insertions(+), 236 deletions(-) create mode 100644 cla-backend-go/tests/utils_conversion_test.go diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 406f757a2..89cfb5ab2 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -41,7 +41,8 @@ setup: $(LINT_TOOL) setup-dev setup-deploy tool-setup: @echo "Installing gobin for installing tools..." @# gobin is the equivalent of 'go get' whilst in module-aware mode but this does not modify your go.mod - GO111MODULE=off go get -u github.com/myitcv/gobin + #GO111MODULE=off go get -u github.com/myitcv/gobin + go get -u github.com/myitcv/gobin setup_dev: setup-dev setup-dev: tool-setup @@ -199,7 +200,7 @@ run: deps: go env -w GOPRIVATE=github.com/LF-Engineering/* - go mod download + go mod download -x build: build-linux build-linux: deps diff --git a/cla-backend-go/github/branch_protection/interfaces.go b/cla-backend-go/github/branch_protection/interfaces.go index 847b3d250..ee97e3947 100644 --- a/cla-backend-go/github/branch_protection/interfaces.go +++ b/cla-backend-go/github/branch_protection/interfaces.go @@ -6,7 +6,7 @@ package branch_protection import ( "context" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" "github.com/shurcooL/githubv4" ) diff --git a/cla-backend-go/github/branch_protection/mock.go b/cla-backend-go/github/branch_protection/mock.go index 456925c0e..b0ac6cd4d 100644 --- a/cla-backend-go/github/branch_protection/mock.go +++ b/cla-backend-go/github/branch_protection/mock.go @@ -13,7 +13,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - github "github.com/google/go-github/v33/github" + github "github.com/google/go-github/v37/github" githubv4 "github.com/shurcooL/githubv4" ) diff --git a/cla-backend-go/github/branch_protection/protected_branch.go b/cla-backend-go/github/branch_protection/protected_branch.go index ea56ef5a3..5ba835f20 100644 --- a/cla-backend-go/github/branch_protection/protected_branch.go +++ b/cla-backend-go/github/branch_protection/protected_branch.go @@ -14,7 +14,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" - githubpkg "github.com/google/go-github/v33/github" + githubpkg "github.com/google/go-github/v37/github" ) const ( diff --git a/cla-backend-go/github/branch_protection/protected_branch_limiter.go b/cla-backend-go/github/branch_protection/protected_branch_limiter.go index 7166e9de8..ead673382 100644 --- a/cla-backend-go/github/branch_protection/protected_branch_limiter.go +++ b/cla-backend-go/github/branch_protection/protected_branch_limiter.go @@ -8,7 +8,7 @@ import ( "fmt" "github.com/communitybridge/easycla/cla-backend-go/github" - githubpkg "github.com/google/go-github/v33/github" + githubpkg "github.com/google/go-github/v37/github" "github.com/shurcooL/githubv4" "go.uber.org/ratelimit" "golang.org/x/time/rate" diff --git a/cla-backend-go/github/client.go b/cla-backend-go/github/client.go index 84070b97e..e57e5749c 100644 --- a/cla-backend-go/github/client.go +++ b/cla-backend-go/github/client.go @@ -13,7 +13,7 @@ import ( "github.com/shurcooL/githubv4" "github.com/bradleyfalzon/ghinstallation" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" "golang.org/x/oauth2" ) diff --git a/cla-backend-go/github/github_installation.go b/cla-backend-go/github/github_installation.go index e609694c2..d8f2b9fe2 100644 --- a/cla-backend-go/github/github_installation.go +++ b/cla-backend-go/github/github_installation.go @@ -11,7 +11,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) @@ -40,15 +40,15 @@ func GetInstallationRepositories(ctx context.Context, installationID int64) ([]* } for { - repos, resp, err := client.Apps.ListRepos(ctx, opts) + listReposResponse, resp, err := client.Apps.ListRepos(ctx, opts) if err != nil { msg := fmt.Sprintf("error while getting repositories associated for installation, error: %+v", err) log.WithFields(f).WithError(err).Warn(msg) return nil, errors.New(msg) } - log.WithFields(f).Debugf("fetched %d records...", len(repos)) - allRepos = append(allRepos, repos...) + log.WithFields(f).Debugf("fetched %d records...", len(listReposResponse.Repositories)) + allRepos = append(allRepos, listReposResponse.Repositories...) if resp.NextPage == 0 { break } diff --git a/cla-backend-go/github/github_org.go b/cla-backend-go/github/github_org.go index fb8df7581..9bcc4be1e 100644 --- a/cla-backend-go/github/github_org.go +++ b/cla-backend-go/github/github_org.go @@ -12,7 +12,7 @@ import ( "github.com/sirupsen/logrus" log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" ) // errors diff --git a/cla-backend-go/github/github_repository.go b/cla-backend-go/github/github_repository.go index 268da0859..7025efd3d 100644 --- a/cla-backend-go/github/github_repository.go +++ b/cla-backend-go/github/github_repository.go @@ -13,7 +13,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" ) // errors diff --git a/cla-backend-go/github/github_user.go b/cla-backend-go/github/github_user.go index 32a18d728..78745611e 100644 --- a/cla-backend-go/github/github_user.go +++ b/cla-backend-go/github/github_user.go @@ -9,7 +9,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" ) // GetUserDetails return github users details diff --git a/cla-backend-go/github/handlers.go b/cla-backend-go/github/handlers.go index 24784a1a8..4f7516366 100644 --- a/cla-backend-go/github/handlers.go +++ b/cla-backend-go/github/handlers.go @@ -20,7 +20,7 @@ import ( "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" - ghLib "github.com/google/go-github/v33/github" + ghLib "github.com/google/go-github/v37/github" "github.com/savaki/dynastore" "golang.org/x/oauth2" "golang.org/x/oauth2/github" diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 6e9d455f7..d584c184d 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT module github.com/communitybridge/easycla/cla-backend-go -go 1.15 +go 1.16 replace github.com/awslabs/aws-lambda-go-api-proxy => github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 @@ -19,7 +19,6 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fnproject/fdk-go v0.0.2 - github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/gin-gonic/gin v1.7.2 github.com/go-openapi/errors v0.19.6 github.com/go-openapi/loads v0.19.5 @@ -31,50 +30,41 @@ require ( github.com/go-playground/validator/v10 v10.7.0 // indirect github.com/go-resty/resty/v2 v2.3.0 github.com/gofrs/uuid v4.0.0+incompatible - github.com/golang/mock v1.4.4 - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-github/v33 v33.0.0 + github.com/golang/mock v1.6.0 + github.com/google/go-github/v37 v37.0.0 github.com/google/uuid v1.1.4 github.com/gorilla/sessions v1.2.1 // indirect github.com/imroc/req v0.3.0 github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 - github.com/json-iterator/go v1.1.11 // indirect github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f // indirect github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 github.com/kr/pretty v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.13 // indirect - github.com/mitchellh/mapstructure v1.3.2 + github.com/mitchellh/mapstructure v1.4.1 github.com/mozillazg/request v0.8.0 // indirect github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc - github.com/pelletier/go-toml v1.8.0 // indirect github.com/rs/cors v1.7.0 github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect - github.com/sirupsen/logrus v1.7.0 - github.com/spf13/afero v1.3.0 // indirect - github.com/spf13/cast v1.3.1 // indirect + github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.1 - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.6.1 + github.com/spf13/viper v1.8.1 + github.com/stretchr/testify v1.7.0 github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 github.com/ugorji/go v1.2.6 // indirect github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a github.com/xanzy/go-gitlab v0.50.1 go.uber.org/ratelimit v0.1.0 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect + golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 + golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect - golang.org/x/text v0.3.6 // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e - google.golang.org/appengine v1.6.6 // indirect google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/ini.v1 v1.57.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index e5bde5d75..2375188ca 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -4,25 +4,41 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -30,27 +46,20 @@ github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJ github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -65,60 +74,59 @@ github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c h1:+0HFd5KSZ/mm3JmhmrDukiId5iR6w4+BdFtfSy4yWIc= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -127,16 +135,13 @@ github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2 github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -217,171 +222,163 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= -github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM= -github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= +github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3bOQ/tM= +github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -390,7 +387,6 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -398,76 +394,61 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2hdUafNG5zxQdTw5KhDMK5C0a8U= github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= -github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0 h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -476,123 +457,97 @@ github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1y github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= -github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.3.0 h1:Ysnmjh1Di8EaWaBv40CYR4IdaIsBc5996Gh1oZzCBKk= -github.com/spf13/afero v1.3.0/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -606,24 +561,24 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 h1:xp/21gmSPTeWIkalsgXw2njIh3zZyrRRcuCgQfOPLLU= github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8/go.mod h1:K3DbqPpP2WE/9MWokWWzgFZcbgtMb9Wd5CYk9AAbEN8= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -632,44 +587,47 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -689,28 +647,41 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -728,20 +699,49 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -750,8 +750,11 @@ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -772,22 +775,52 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -809,6 +842,7 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -819,26 +853,78 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200424195722-358506031216 h1:tP48fLPdUK6KA51XXU9OBvvjWPzKOwEldOl7Ab4Z+8U= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200424195722-358506031216/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -847,39 +933,88 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -887,12 +1022,16 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go index e7f2d979b..c78593f62 100644 --- a/cla-backend-go/signatures/converters.go +++ b/cla-backend-go/signatures/converters.go @@ -81,10 +81,10 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results ProjectID: dbSignature.SignatureProjectID, Created: dbSignature.DateCreated, Modified: dbSignature.DateModified, - EmailApprovalList: dbSignature.EmailApprovalList, - DomainApprovalList: dbSignature.EmailDomainApprovalList, - GithubUsernameApprovalList: dbSignature.GitHubUsernameApprovalList, - GithubOrgApprovalList: dbSignature.GitHubOrgApprovalList, + EmailApprovalList: utils.GetNilSliceIfEmpty(dbSignature.EmailApprovalList), + DomainApprovalList: utils.GetNilSliceIfEmpty(dbSignature.EmailDomainApprovalList), + GithubUsernameApprovalList: utils.GetNilSliceIfEmpty(dbSignature.GitHubUsernameApprovalList), + GithubOrgApprovalList: utils.GetNilSliceIfEmpty(dbSignature.GitHubOrgApprovalList), UserName: dbSignature.UserName, UserLFID: dbSignature.UserLFUsername, UserGHID: dbSignature.UserGithubUsername, diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 9bf4e84c5..fd293811c 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -26,7 +26,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" - githubpkg "github.com/google/go-github/v33/github" + githubpkg "github.com/google/go-github/v37/github" "golang.org/x/oauth2" ) diff --git a/cla-backend-go/tests/utils_conversion_test.go b/cla-backend-go/tests/utils_conversion_test.go new file mode 100644 index 000000000..e18cd79a3 --- /dev/null +++ b/cla-backend-go/tests/utils_conversion_test.go @@ -0,0 +1,28 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +// TestValidCompanyName is a test for the GetNilSliceIfEmpty +func TestGetNilSliceIfEmptyWithData(t *testing.T) { + slice := []string{"dog", "cat"} + assert.Equal(t, []string{"dog", "cat"}, utils.GetNilSliceIfEmpty(slice), "GetNilSliceIfEmpty - With Data") +} + +// TestGetNilSliceIfEmptyWithEmptySlice is a test for the GetNilSliceIfEmpty +func TestGetNilSliceIfEmptyWithEmptySlice(t *testing.T) { + var slice []string + assert.Nil(t, utils.GetNilSliceIfEmpty(slice), "GetNilSliceIfEmpty - Empty Slice") +} + +// TestGetNilSliceIfEmptyWithNil is a test for the GetNilSliceIfEmpty +func TestGetNilSliceIfEmptyWithNil(t *testing.T) { + assert.Nil(t, utils.GetNilSliceIfEmpty(nil), "GetNilSliceIfEmpty - Nil") +} diff --git a/cla-backend-go/utils/conversion.go b/cla-backend-go/utils/conversion.go index e51c740c1..973251cc1 100644 --- a/cla-backend-go/utils/conversion.go +++ b/cla-backend-go/utils/conversion.go @@ -41,3 +41,12 @@ func BoolValue(input *bool) bool { func Bool(input bool) *bool { return &input } + +// GetNilSliceIfEmpty returns a nil reference is the specified slice is empty, otherwise returns a reference to the original slice +func GetNilSliceIfEmpty(slice []string) []string { + if len(slice) == 0 { + return nil + } + + return slice +} diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index 6eb4a1cf4..1db0a8b03 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -20,7 +20,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/go-openapi/swag" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" "github.com/sirupsen/logrus" ) diff --git a/cla-backend-go/v2/github_activity/handlers.go b/cla-backend-go/v2/github_activity/handlers.go index 92027822b..e9f86fdb1 100644 --- a/cla-backend-go/v2/github_activity/handlers.go +++ b/cla-backend-go/v2/github_activity/handlers.go @@ -11,7 +11,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/google/go-github/v33/github" // with go modules enabled (GO111MODULE=on or outside GOPATH)0:w + "github.com/google/go-github/v37/github" // with go modules enabled (GO111MODULE=on or outside GOPATH)0:w "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index 3c79a5fa5..5214cca3a 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -26,7 +26,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/repositories" log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" ) // Service is responsible for handling the github activity events diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go index b667ee0eb..aa40e8352 100644 --- a/cla-backend-go/v2/github_activity/service_test.go +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -13,7 +13,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/github_organizations" "github.com/communitybridge/easycla/cla-backend-go/repositories/mock" "github.com/golang/mock/gomock" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v37/github" "github.com/stretchr/testify/assert" ) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 667726689..4495b961e 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -7,9 +7,10 @@ import ( "context" "errors" "fmt" - "github.com/gofrs/uuid" "strings" + "github.com/gofrs/uuid" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 7ef3cf11a..740617ef4 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -6,12 +6,13 @@ package gitlab_organizations import ( "context" "fmt" - "github.com/communitybridge/easycla/cla-backend-go/config" - "github.com/go-openapi/strfmt" "net/url" "sort" "strings" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/go-openapi/strfmt" + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gitlab" From 7c28b248857548018d6168d8fbacffbd2642f14d Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 28 Jul 2021 20:41:59 +0300 Subject: [PATCH 0372/1276] adding update and delete operations to gitlab organizations (#3080) Signed-off-by: makkalot --- cla-backend-go/go.sum | 181 ++++++++++++++++++ cla-backend-go/swagger/cla.v2.yaml | 79 ++++++++ .../common/update-github-organization.yaml | 2 +- .../v2/gitlab_organizations/handlers.go | 86 +++++++++ .../v2/gitlab_organizations/repository.go | 151 ++++++++++++++- .../v2/gitlab_organizations/service.go | 52 ++++- 6 files changed, 541 insertions(+), 10 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 2375188ca..23f30f8c3 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -17,28 +17,38 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -46,20 +56,29 @@ github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJ github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -74,42 +93,63 @@ github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= @@ -119,14 +159,18 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -135,13 +179,19 @@ github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2 github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -222,41 +272,58 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -286,8 +353,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -308,9 +377,12 @@ github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3 github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -322,8 +394,11 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -332,45 +407,68 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -378,6 +476,7 @@ github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= @@ -387,6 +486,7 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -394,7 +494,9 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -402,6 +504,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -409,28 +512,40 @@ github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2h github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -446,9 +561,12 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -457,13 +575,20 @@ github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1y github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -476,19 +601,25 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= @@ -501,37 +632,51 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= +github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -543,7 +688,9 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= @@ -565,6 +712,7 @@ github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -579,6 +727,7 @@ github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 h1:xp/21gmSP github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8/go.mod h1:K3DbqPpP2WE/9MWokWWzgFZcbgtMb9Wd5CYk9AAbEN8= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -587,25 +736,37 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -618,15 +779,18 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -652,6 +816,7 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -670,8 +835,10 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -681,6 +848,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -813,6 +981,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -888,6 +1057,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -915,6 +1085,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -965,6 +1136,7 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -985,6 +1157,7 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1000,16 +1173,20 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1031,7 +1208,11 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 27131ee98..ac0cafbae 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1679,6 +1679,82 @@ paths: tags: - gitlab-organizations + /project/{projectSFID}/gitlab/organizations/{orgName}/config: + put: + summary: Update Gitlab Organization Configuration + description: Endpoint to adjust the Gitlab Organization Configuration, such as toggling the auto-enable flag + operationId: updateProjectGitlabOrganizationConfig + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + - name: orgName + in: path + type: string + required: true + - in: body + name: body + schema: + $ref: '#/definitions/update-gitlab-organization' + required: true + responses: + '200': + description: 'Resource Updated' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + tags: + - gitlab-organizations + + delete: + summary: Delete Gitlab organization in the project + description: Endpoint to delete the Gitlab organization for the project + operationId: deleteProjectGitlabOrganization + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + - name: orgName + in: path + type: string + required: true + responses: + '204': + description: 'Deleted' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + tags: + - gitlab-organizations + /cla-group/{claGroupID}/icla/signatures: get: summary: List individual signatures for CLA Group @@ -4237,6 +4313,9 @@ definitions: create-gitlab-organization: $ref: './common/create-github-organization.yaml' + update-gitlab-organization: + $ref: './common/update-github-organization.yaml' + user: $ref: './common/user.yaml' diff --git a/cla-backend-go/swagger/common/update-github-organization.yaml b/cla-backend-go/swagger/common/update-github-organization.yaml index 82ccc4339..440297615 100644 --- a/cla-backend-go/swagger/common/update-github-organization.yaml +++ b/cla-backend-go/swagger/common/update-github-organization.yaml @@ -13,5 +13,5 @@ properties: description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. branchProtectionEnabled: type: boolean - description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. + description: Flag to indicate if this Organization is configured to automatically setup branch protection on CLA enabled repositories. x-omitempty: true diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index ef77190ba..63f9684a8 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -5,9 +5,12 @@ package gitlab_organizations import ( "context" + "errors" "fmt" "strings" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" "github.com/communitybridge/easycla/cla-backend-go/gitlab" @@ -136,6 +139,89 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. return gitlab_organizations.NewAddProjectGitlabOrganizationOK().WithPayload(result) }) + api.GitlabOrganizationsUpdateProjectGitlabOrganizationConfigHandler = gitlab_organizations.UpdateProjectGitlabOrganizationConfigHandlerFunc(func(params gitlab_organizations.UpdateProjectGitlabOrganizationConfigParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + if params.Body.AutoEnabled == nil { + return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ + Code: "400", + Message: "EasyCLA - 400 Bad Request - missing auto enable value in body", + }) + } + + if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ + Code: "400", + Message: "EasyCLA - 400 Bad Request - AutoEnabledClaGroupID can't be empty when AutoEnabled", + }) + } + + err := service.UpdateGitlabOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + if err != nil { + if errors.Is(err, projects_cla_groups.ErrCLAGroupDoesNotExist) { + return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, err.Error())) + } + return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, "updating gitlab org", err)) + } + + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.GitlabOrganizationUpdated, + ProjectSFID: params.ProjectSFID, + LfUsername: authUser.UserName, + UserName: authUser.UserName, + EventData: &events.GitlabOrganizationUpdatedEventData{ + GitlabOrganizationName: params.OrgName, + AutoEnabled: *params.Body.AutoEnabled, + }, + }) + + return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigOK() + }) + + api.GitlabOrganizationsDeleteProjectGitlabOrganizationHandler = gitlab_organizations.DeleteProjectGitlabOrganizationHandlerFunc(func(params gitlab_organizations.DeleteProjectGitlabOrganizationParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "github_organization.handlers.GithubOrganizationsDeleteProjectGithubOrganizationHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSFID, + "orgName": params.OrgName, + "authUser": authUser.UserName, + "authEmail": authUser.Email, + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("authUser %s does not have access to Delete Project GitHub Organizations with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewDeleteProjectGitlabOrganizationForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + err := service.DeleteGitlabOrganization(ctx, params.ProjectSFID, params.OrgName) + if err != nil { + if strings.Contains(err.Error(), "getProjectNotFound") { + msg := fmt.Sprintf("project not found with given SFID: %s", params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewDeleteProjectGitlabOrganizationNotFound().WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + } + msg := fmt.Sprintf("problem deleting Gitlab Organization with project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewDeleteProjectGitlabOrganizationBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + LfUsername: authUser.UserName, + EventType: events.GitHubOrganizationDeleted, + ProjectSFID: params.ProjectSFID, + EventData: &events.GitlabOrganizationDeletedEventData{ + GitlabOrganizationName: params.OrgName, + }, + }) + + return gitlab_organizations.NewDeleteProjectGitlabOrganizationNoContent() + }) + api.GitlabActivityGitlabOauthCallbackHandler = gitlab_activity.GitlabOauthCallbackHandlerFunc(func(params gitlab_activity.GitlabOauthCallbackParams) middleware.Responder { f := logrus.Fields{ "functionName": "gitlab_organization.handlers.GitlabActivityGitlabOauthCallbackHandler", diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 4495b961e..2ba7b52ec 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -36,6 +36,8 @@ type RepositoryInterface interface { GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error + UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error + DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } // Repository object/struct @@ -98,7 +100,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS return nil, err } - enabled := false + enabled := true gitlabOrg := &GitlabOrganization{ OrganizationID: organizationID.String(), DateCreated: currentTime, @@ -154,8 +156,8 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s condition := expression.Key("organization_sfid").Equal(expression.Value(projectSFID)) builder := expression.NewBuilder().WithKeyCondition(condition) - //filter := expression.Name("enabled").Equal(expression.Value(true)) - //builder = builder.WithFilter(filter) + filter := expression.Name("enabled").Equal(expression.Value(true)) + builder = builder.WithFilter(filter) // Use the nice builder to create the expression expr, err := builder.Build() @@ -207,6 +209,8 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOr "githubOrganizationName": githubOrganizationName, } + githubOrganizationName = strings.ToLower(githubOrganizationName) + condition := expression.Key("organization_name_lower").Equal(expression.Value(strings.ToLower(githubOrganizationName))) builder := expression.NewBuilder().WithKeyCondition(condition) // Use the nice builder to create the expression @@ -339,6 +343,147 @@ func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabO return nil } +func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { + f := logrus.Fields{ + "functionName": "gitlab_organizations.repository.UpdateGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "organizationName": organizationName, + "autoEnabled": autoEnabled, + "autoEnabledClaGroupID": autoEnabledClaGroupID, + "branchProtectionEnabled": branchProtectionEnabled, + "tableName": repo.gitlabOrgTableName, + } + + _, currentTime := utils.CurrentTime() + gitlabOrgs, lookupErr := repo.GetGitlabOrganizationByName(ctx, organizationName) + if lookupErr != nil { + log.WithFields(f).Warnf("error looking up Gitlab organization by name, error: %+v", lookupErr) + return lookupErr + } + if gitlabOrgs == nil || len(gitlabOrgs.List) == 0 { + lookupErr := errors.New("unable to lookup Gitlab organization by name") + log.WithFields(f).Warnf("error looking up Gitlab organization, error: %+v", lookupErr) + return lookupErr + } + + gitlabOrg := gitlabOrgs.List[0] + + expressionAttributeNames := map[string]*string{ + "#A": aws.String("auto_enabled"), + "#C": aws.String("auto_enabled_cla_group_id"), + "#B": aws.String("branch_protection_enabled"), + "#M": aws.String("date_modified"), + } + expressionAttributeValues := map[string]*dynamodb.AttributeValue{ + ":a": { + BOOL: aws.Bool(autoEnabled), + }, + ":c": { + S: aws.String(autoEnabledClaGroupID), + }, + ":b": { + BOOL: aws.Bool(branchProtectionEnabled), + }, + ":m": { + S: aws.String(currentTime), + }, + } + updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m" + + if enabled != nil { + expressionAttributeNames["#E"] = aws.String("enabled") + expressionAttributeValues[":e"] = &dynamodb.AttributeValue{ + BOOL: aws.Bool(*enabled), + } + updateExpression = updateExpression + ", #E = :e " + } + + input := &dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "organization_id": { + S: aws.String(gitlabOrg.OrganizationID), + }, + }, + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + UpdateExpression: &updateExpression, + TableName: aws.String(repo.gitlabOrgTableName), + } + + log.WithFields(f).Debugf("updating gitlab organization record: %+v", input) + _, updateErr := repo.dynamoDBClient.UpdateItem(input) + if updateErr != nil { + log.WithFields(f).Warnf("unable to update Gitlab organization record, error: %+v", updateErr) + return updateErr + } + + return nil +} + +func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { + f := logrus.Fields{ + "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "githubOrgName": gitlabOrgName, + } + + var gitlabOrganizationID string + orgs, orgErr := repo.GetGitlabOrganizations(ctx, projectSFID) + if orgErr != nil { + errMsg := fmt.Sprintf("gitlab organization is not found using projectSFID: %s, error: %+v", projectSFID, orgErr) + log.WithFields(f).Warn(errMsg) + return errors.New(errMsg) + } + + for _, githubOrg := range orgs.List { + if strings.EqualFold(githubOrg.OrganizationName, gitlabOrgName) { + gitlabOrganizationID = githubOrg.OrganizationID + break + } + } + + log.WithFields(f).Debug("Deleting GitHub organization...") + // Update enabled flag as false + _, currentTime := utils.CurrentTime() + note := fmt.Sprintf("Enabled set to false due to org deletion at %s ", currentTime) + _, err := repo.dynamoDBClient.UpdateItem( + &dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "organization_id": { + S: aws.String(gitlabOrganizationID), + }, + }, + ExpressionAttributeNames: map[string]*string{ + "#E": aws.String("enabled"), + "#N": aws.String("note"), + "#D": aws.String("date_modified"), + }, + ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":e": { + BOOL: aws.Bool(false), + }, + ":n": { + S: aws.String(note), + }, + ":d": { + S: aws.String(currentTime), + }, + }, + UpdateExpression: aws.String("SET #E = :e, #N = :n, #D = :d"), + TableName: aws.String(repo.gitlabOrgTableName), + }, + ) + if err != nil { + errMsg := fmt.Sprintf("error deleting gitlab organization: %s - %+v", gitlabOrgName, err) + log.WithFields(f).Warnf(errMsg) + return errors.New(errMsg) + } + + return nil +} + func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*GitlabOrganization) []*models2.GitlabOrganization { f := logrus.Fields{ "functionName": "buildGitlabOrganizationListModels", diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 740617ef4..0f23e7beb 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -29,19 +29,21 @@ type Service interface { AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.CreateGitlabOrganization) (*models.GitlabOrganization, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) + UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error + DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error } type service struct { - repo RepositoryInterface - projectsCLAGroupService projects_cla_groups.Repository + repo RepositoryInterface + claGroupRepository projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo RepositoryInterface, projectsCLAGroupService projects_cla_groups.Repository) Service { +func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups.Repository) Service { return service{ - repo: repo, - projectsCLAGroupService: projectsCLAGroupService, + repo: repo, + claGroupRepository: claGroupRepository, } } @@ -78,6 +80,17 @@ func (s service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganiz } +func (s service) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { + // check if valid cla group id is passed + if autoEnabledClaGroupID != "" { + if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { + return err + } + } + + return s.repo.UpdateGitlabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) +} + func (s service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", @@ -187,7 +200,7 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) autoEnabledCLAGroupName := "" if org.AutoEnabledClaGroupID != "" { log.WithFields(f).Debugf("Loading CLA Group by ID: %s to obtain the name for GitHub auth enabled CLA Group response", org.AutoEnabledClaGroupID) - claGroupMode, claGroupLookupErr := s.projectsCLAGroupService.GetCLAGroup(ctx, org.AutoEnabledClaGroupID) + claGroupMode, claGroupLookupErr := s.claGroupRepository.GetCLAGroup(ctx, org.AutoEnabledClaGroupID) if claGroupLookupErr != nil { log.WithFields(f).WithError(claGroupLookupErr).Warnf("Unable to lookup CLA Group by ID: %s", org.AutoEnabledClaGroupID) } @@ -248,6 +261,33 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) return out, nil } +func (s service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error { + f := logrus.Fields{ + "functionName": "DeleteGitHubOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "gitlabOrgName": gitlabOrgName, + } + + // Lookup the parent + parentProjectSFID, projErr := v2ProjectService.GetClient().GetParentProject(projectSFID) + if projErr != nil { + log.WithFields(f).Warnf("problem fetching project parent SFID, error: %+v", projErr) + return projErr + } + + log.WithFields(f).Debugf("retrieved parent of project sfid : %s -> %s", projectSFID, parentProjectSFID) + + // Todo: Enable this when the repositories are implemented + //err := s.ghRepository.DisableRepositoriesOfGithubOrganization(ctx, parentProjectSFID, gitlabOrgName) + //if err != nil { + // log.WithFields(f).Warnf("problem disabling repositories for github organizations, error: %+v", projErr) + // return err + //} + + return s.repo.DeleteGitlabOrganization(ctx, projectSFID, gitlabOrgName) +} + func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI { base := "https://gitlab.com/oauth/authorize" c := config.GetConfig() From 6f893baa9b8132f4f8b4d4c2c8e66c991ec14fd1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 28 Jul 2021 18:23:28 -0700 Subject: [PATCH 0373/1276] Added Gitlab Approval List Support (#3081) --- cla-backend-go/approval_list/service.go | 4 +- cla-backend-go/cla_manager/handlers.go | 12 +- cla-backend-go/cla_manager/service.go | 22 +- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/company/service.go | 8 +- cla-backend-go/events/event_data.go | 184 ++++++++++++ cla-backend-go/project/service.go | 2 +- cla-backend-go/serverless.yml | 4 +- cla-backend-go/signatures/constants.go | 28 ++ cla-backend-go/signatures/converters.go | 7 +- cla-backend-go/signatures/dbmodels.go | 7 +- cla-backend-go/signatures/models.go | 10 +- cla-backend-go/signatures/projections.go | 15 +- cla-backend-go/signatures/repository.go | 219 +++++++++++--- cla-backend-go/signatures/service.go | 124 +++++++- .../swagger/common/icla-signature.yaml | 4 + .../common/signature-approval-list.yaml | 41 ++- cla-backend-go/swagger/common/signature.yaml | 22 +- cla-backend-go/swagger/common/user.yaml | 26 +- cla-backend-go/tests/utils_test.go | 189 ------------ cla-backend-go/tests/utils_validators_test.go | 272 ++++++++++++++++++ cla-backend-go/userSubscribeLambda/main.go | 6 +- cla-backend-go/users/handlers.go | 4 +- cla-backend-go/users/models.go | 4 +- cla-backend-go/users/repository.go | 194 ++++++++++++- cla-backend-go/users/service.go | 31 +- cla-backend-go/utils/cla_user.go | 2 +- cla-backend-go/utils/constants.go | 14 +- cla-backend-go/utils/utils.go | 129 --------- cla-backend-go/utils/validators.go | 171 +++++++++++ cla-backend-go/v2/cla_manager/service.go | 2 +- cla-backend-go/v2/company/service.go | 2 +- .../v2/dynamo_events/cla_manager.go | 2 +- cla-backend-go/v2/signatures/handlers.go | 12 +- cla-backend-go/v2/signatures/validators.go | 40 ++- cla-backend-go/v2/user-service/client.go | 2 +- cla-backend/serverless.yml | 4 +- 37 files changed, 1389 insertions(+), 434 deletions(-) create mode 100644 cla-backend-go/signatures/constants.go create mode 100644 cla-backend-go/tests/utils_validators_test.go create mode 100644 cla-backend-go/utils/validators.go diff --git a/cla-backend-go/approval_list/service.go b/cla-backend-go/approval_list/service.go index 21ee64305..77d9e9353 100644 --- a/cla-backend-go/approval_list/service.go +++ b/cla-backend-go/approval_list/service.go @@ -336,7 +336,7 @@ func (s service) sendRequestSentEmail(companyModel *models.Company, claGroupMode // Need to determine which email... var whichEmail = "" if manager.LfEmail != "" { - whichEmail = manager.LfEmail + whichEmail = manager.LfEmail.String() } // If no LF Email try to grab the first other email in their email list @@ -391,7 +391,7 @@ func (s service) sendRequestRejectedEmailToRecipient(emailParams emails.CommonEm // Need to determine which email... var whichEmail = "" if manager.LfEmail != "" { - whichEmail = manager.LfEmail + whichEmail = manager.LfEmail.String() } // If no LF Email try to grab the first other email in their email list diff --git a/cla-backend-go/cla_manager/handlers.go b/cla-backend-go/cla_manager/handlers.go index 8bea2b3ce..68c8da253 100644 --- a/cla-backend-go/cla_manager/handlers.go +++ b/cla-backend-go/cla_manager/handlers.go @@ -7,6 +7,8 @@ import ( "context" "fmt" + "github.com/go-openapi/strfmt" + "github.com/LF-Engineering/lfx-kit/auth" user_service "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" @@ -192,7 +194,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. sendRequestAccessEmailToCLAManagers(emailSvc, emails.RequestAccessToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, - RecipientAddress: manager.LfEmail, + RecipientAddress: manager.LfEmail.String(), CompanyName: companyModel.CompanyName, }, RequesterName: params.Body.UserName, @@ -368,7 +370,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. sendRequestApprovedEmailToCLAManagers(emailSvc, emails.RequestApprovedToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, - RecipientAddress: manager.LfEmail, + RecipientAddress: manager.LfEmail.String(), CompanyName: companyModel.CompanyName, }, RequesterName: request.UserName, @@ -483,7 +485,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. sendRequestDeniedEmailToCLAManagers(emailSvc, emails.RequestDeniedToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, - RecipientAddress: manager.LfEmail, + RecipientAddress: manager.LfEmail.String(), CompanyName: companyModel.CompanyName, }, RequesterName: request.UserName, @@ -654,7 +656,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. DateModified: nowStr, Emails: userServiceClient.EmailsToSlice(sfdcUserObject), GithubUsername: sfdcUserObject.GithubID, //this is the github username - LfEmail: userServiceClient.GetPrimaryEmail(sfdcUserObject), + LfEmail: strfmt.Email(userServiceClient.GetPrimaryEmail(sfdcUserObject)), LfUsername: sfdcUserObject.Username, Note: "created from SF record", UserExternalID: sfdcUserObject.ID, @@ -759,7 +761,7 @@ func Configure(api *operations.ClaAPI, service IService, companyService company. DateModified: nowStr, Emails: userServiceClient.EmailsToSlice(sfdcUserObject), GithubUsername: sfdcUserObject.GithubID, //this is the github username - LfEmail: userServiceClient.GetPrimaryEmail(sfdcUserObject), + LfEmail: strfmt.Email(userServiceClient.GetPrimaryEmail(sfdcUserObject)), LfUsername: sfdcUserObject.Username, Note: "created from SF record", UserExternalID: sfdcUserObject.ID, diff --git a/cla-backend-go/cla_manager/service.go b/cla-backend-go/cla_manager/service.go index 07b18574e..e76e1bc5a 100644 --- a/cla-backend-go/cla_manager/service.go +++ b/cla-backend-go/cla_manager/service.go @@ -191,7 +191,7 @@ func (s service) DeleteRequest(requestID string) error { return nil } -// AddClaManager Adds LFID to Signature Access Control List list +// AddClaManager Adds LFID to Signature Access Control list func (s service) AddClaManager(ctx context.Context, authUser *auth.User, companyID string, claGroupID string, LFID string, projectSFName string) (*models.Signature, error) { f := logrus.Fields{ @@ -249,17 +249,17 @@ func (s service) AddClaManager(ctx context.Context, authUser *auth.User, company sendClaManagerAddedEmailToCLAManagers(s.emailTemplateService, emails.ClaManagerAddedToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, - RecipientAddress: manager.LfEmail, + RecipientAddress: manager.LfEmail.String(), CompanyName: companyModel.CompanyName, }, Name: userModel.Username, - Email: userModel.LfEmail, + Email: userModel.LfEmail.String(), }, claGroupModel) } // Notify the added user sendClaManagerAddedEmailToUser(s.emailTemplateService, emails.CommonEmailParams{ RecipientName: userModel.Username, - RecipientAddress: userModel.LfEmail, + RecipientAddress: userModel.LfEmail.String(), CompanyName: companyModel.CompanyName, }, claGroupModel) @@ -279,7 +279,7 @@ func (s service) AddClaManager(ctx context.Context, authUser *auth.User, company CompanyName: companyModel.CompanyName, ProjectName: claGroupModel.ProjectName, UserName: userModel.Username, - UserEmail: userModel.LfEmail, + UserEmail: userModel.LfEmail.String(), UserLFID: userModel.LfUsername, }, }) @@ -368,18 +368,18 @@ func (s service) RemoveClaManager(ctx context.Context, authUser *auth.User, comp sendClaManagerDeleteEmailToCLAManagers(s.emailTemplateService, emails.ClaManagerDeletedToCLAManagersTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: manager.Username, - RecipientAddress: manager.LfEmail, + RecipientAddress: manager.LfEmail.String(), CompanyName: companyModel.CompanyName, }, Name: userModel.LfUsername, - Email: userModel.LfEmail, + Email: userModel.LfEmail.String(), }, claGroupModel) } // Notify the removed manager sendRemovedClaManagerEmailToRecipient(s.emailTemplateService, emails.CommonEmailParams{ RecipientName: userModel.LfUsername, - RecipientAddress: userModel.LfEmail, + RecipientAddress: userModel.LfEmail.String(), CompanyName: companyModel.CompanyName, }, claGroupModel, claManagers) @@ -399,7 +399,7 @@ func (s service) RemoveClaManager(ctx context.Context, authUser *auth.User, comp CompanyName: companyModel.CompanyName, ProjectName: claGroupModel.ProjectName, UserName: userModel.LfUsername, - UserEmail: userModel.LfEmail, + UserEmail: userModel.LfEmail.String(), UserLFID: LFID, }, }) @@ -452,7 +452,7 @@ func sendClaManagerAddedEmailToCLAManagers(emailSvc emails.EmailTemplateService, func sendRemovedClaManagerEmailToRecipient(emailSvc emails.EmailTemplateService, emailParams emails.CommonEmailParams, claGroupModel *models.ClaGroup, claManagers []models.User) { projectName := claGroupModel.ProjectName - emailCLAManagerParams := []emails.ClaManagerInfoParams{} + var emailCLAManagerParams []emails.ClaManagerInfoParams log.Debugf("CLA Managers found: %+v", claManagers) @@ -462,7 +462,7 @@ func sendRemovedClaManagerEmailToRecipient(emailSvc emails.EmailTemplateService, // Need to determine which email... var whichEmail = "" if companyAdmin.LfEmail != "" { - whichEmail = companyAdmin.LfEmail + whichEmail = companyAdmin.LfEmail.String() log.Debugf("Found email : %s ", whichEmail) } diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 148d0d181..d143fb378 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" + "github.com/go-openapi/strfmt" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/gitlab" @@ -651,7 +653,7 @@ func createUserFromRequest(authorizer auth.Authorizer, usersService users.Servic // Attempt to create the user newUser := &models.User{ - LfEmail: claUser.LFEmail, + LfEmail: strfmt.Email(claUser.LFEmail), LfUsername: claUser.LFUsername, Username: claUser.Name, } diff --git a/cla-backend-go/company/service.go b/cla-backend-go/company/service.go index ab513e0d4..aa3ec938b 100644 --- a/cla-backend-go/company/service.go +++ b/cla-backend-go/company/service.go @@ -9,6 +9,8 @@ import ( "sort" "strings" + "github.com/go-openapi/strfmt" + "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/users" @@ -617,10 +619,10 @@ func (s service) getPreferredNameAndEmail(ctx context.Context, lfid string) (str userEmail := userModel.LfEmail if userEmail == "" && userModel.Emails != nil && len(userModel.Emails) > 0 { - userEmail = userModel.Emails[0] + userEmail = strfmt.Email(userModel.Emails[0]) } - return userName, userEmail, nil + return userName, userEmail.String(), nil } func (s service) GetCompanyByExternalID(ctx context.Context, companySFID string) (*models.Company, error) { @@ -866,7 +868,7 @@ func getCompanyAdmin(ctx context.Context, companySFID string) (*models.User, err for _, rs := range usc.RoleScopes { if rs.RoleName == "company-admin" { companyAdmin := &models.User{ - LfEmail: usc.Contact.EmailAddress, + LfEmail: strfmt.Email(usc.Contact.EmailAddress), LfUsername: usc.Contact.Username, UserExternalID: usc.Contact.ID, Username: usc.Contact.Name, diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 47954707b..130fc63fa 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -327,6 +327,26 @@ type CLAApprovalListRemoveGitHubOrgData struct { ApprovalListGitHubOrg string } +// CLAApprovalListAddGitlabUsernameData data model +type CLAApprovalListAddGitlabUsernameData struct { + ApprovalListGitlabUsername string +} + +// CLAApprovalListRemoveGitlabUsernameData data model +type CLAApprovalListRemoveGitlabUsernameData struct { + ApprovalListGitlabUsername string +} + +// CLAApprovalListAddGitlabOrgData data model +type CLAApprovalListAddGitlabOrgData struct { + ApprovalListGitlabOrg string +} + +// CLAApprovalListRemoveGitlabOrgData data model +type CLAApprovalListRemoveGitlabOrgData struct { + ApprovalListGitlabOrg string +} + // ApprovalListGitHubOrganizationAddedEventData data model type ApprovalListGitHubOrganizationAddedEventData struct { GitHubOrganizationName string @@ -988,6 +1008,94 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEve return data, true } +// GetEventDetailsString returns the details string for this event +func (ed *CLAApprovalListAddGitlabUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab username %s was added to the approval list", ed.ApprovalListGitlabUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventDetailsString returns the details string for this event +func (ed *CLAApprovalListRemoveGitlabUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab username %s was removed from the approval list", ed.ApprovalListGitlabUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventDetailsString returns the details string for this event +func (ed *CLAApprovalListAddGitlabOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab organization %s was added to the approval list", ed.ApprovalListGitlabOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventDetailsString returns the details string for this event +func (ed *CLAApprovalListRemoveGitlabOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab organization %s was removed from the approval list", ed.ApprovalListGitlabOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventDetailsString returns the details string for this event func (ed *CCLAApprovalListRequestCreatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The CCLA Approval Request was created for the Project: %s, Company: %s with Request ID: %s", @@ -1964,6 +2072,82 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEve return data, true } +// GetEventSummaryString returns the summary string for this event +func (ed *CLAApprovalListAddGitlabUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab username %s was added to the approval list", ed.ApprovalListGitlabUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString returns the summary string for this event +func (ed *CLAApprovalListRemoveGitlabUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab username %s was removed from the approval list", ed.ApprovalListGitlabUsername) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString returns the summary string for this event +func (ed *CLAApprovalListAddGitlabOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab organization %s was added to the approval list", ed.ApprovalListGitlabOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + +// GetEventSummaryString returns the summary string for this event +func (ed *CLAApprovalListRemoveGitlabOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The Gitlab organization %s was removed from the approval list", ed.ApprovalListGitlabOrg) + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if args.ProjectName != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + } + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the CLA Manager %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventSummaryString returns the summary string for this event func (ed *CCLAApprovalListRequestCreatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The user %s created a CCLA Approval Request", args.UserName) diff --git a/cla-backend-go/project/service.go b/cla-backend-go/project/service.go index f998eec85..a861aa0ba 100644 --- a/cla-backend-go/project/service.go +++ b/cla-backend-go/project/service.go @@ -376,7 +376,7 @@ func (s service) GetCLAManagers(ctx context.Context, claGroupID string) ([]*mode return nil, err } managers = append(managers, &models.ClaManagerUser{ - UserEmail: u.LfEmail, + UserEmail: u.LfEmail.String(), UserLFID: u.LfUsername, UserName: u.Username, }) diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index 60cf10ac0..efbae4f06 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -141,8 +141,10 @@ provider: Resource: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" diff --git a/cla-backend-go/signatures/constants.go b/cla-backend-go/signatures/constants.go new file mode 100644 index 000000000..5e1cd5f42 --- /dev/null +++ b/cla-backend-go/signatures/constants.go @@ -0,0 +1,28 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package signatures + +// SignatureEmailApprovalListColumn is the name of the signature column for the email approval list +const SignatureEmailApprovalListColumn = "email_whitelist" // TODO: rename column to email_approval_list + +// SignatureDomainApprovalListColumn is the name of the signature column for the domain approval list +const SignatureDomainApprovalListColumn = "domain_whitelist" // TODO: rename column to domain_approval_list + +// SignatureGitHubUsernameApprovalListColumn is the name of the signature column for the GitHub username approval list +const SignatureGitHubUsernameApprovalListColumn = "github_whitelist" // TODO: rename column to github_username_approval_list + +// SignatureGitHubOrgApprovalListColumn is the name of the signature column for the GitHub organization approval list +const SignatureGitHubOrgApprovalListColumn = "github_org_whitelist" // TODO: rename column to github_org_approval_list + +// SignatureGitlabUsernameApprovalListColumn is the name of the signature column for gitlab username approval lists +const SignatureGitlabUsernameApprovalListColumn = "gitlab_username_approval_list" + +// SignatureGitlabOrgApprovalListColumn is the name of the signature column for gitlab organization approval lists +const SignatureGitlabOrgApprovalListColumn = "gitlab_org_approval_list" + +// SignatureUserGitHubUsername is the name of the signature column for user gitlab username +const SignatureUserGitHubUsername = "user_github_username" + +// SignatureUserGitlabUsername is the name of the signature column for user gitlab username +const SignatureUserGitlabUsername = "user_gitlab_username" diff --git a/cla-backend-go/signatures/converters.go b/cla-backend-go/signatures/converters.go index c78593f62..c6cfa05b7 100644 --- a/cla-backend-go/signatures/converters.go +++ b/cla-backend-go/signatures/converters.go @@ -85,9 +85,14 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results DomainApprovalList: utils.GetNilSliceIfEmpty(dbSignature.EmailDomainApprovalList), GithubUsernameApprovalList: utils.GetNilSliceIfEmpty(dbSignature.GitHubUsernameApprovalList), GithubOrgApprovalList: utils.GetNilSliceIfEmpty(dbSignature.GitHubOrgApprovalList), + GitlabUsernameApprovalList: utils.GetNilSliceIfEmpty(dbSignature.GitlabUsernameApprovalList), + GitlabOrgApprovalList: utils.GetNilSliceIfEmpty(dbSignature.GitlabOrgApprovalList), UserName: dbSignature.UserName, UserLFID: dbSignature.UserLFUsername, - UserGHID: dbSignature.UserGithubUsername, + UserGHID: dbSignature.UserGithubID, + UserGHUsername: dbSignature.UserGithubUsername, + UserGitlabID: dbSignature.UserGitlabID, + UserGitlabUsername: dbSignature.UserGitlabUsername, SignedOn: signedOn, SignatoryName: dbSignature.SignatoryName, UserDocusignName: dbSignature.UserDocusignName, diff --git a/cla-backend-go/signatures/dbmodels.go b/cla-backend-go/signatures/dbmodels.go index 00056661d..f9579c703 100644 --- a/cla-backend-go/signatures/dbmodels.go +++ b/cla-backend-go/signatures/dbmodels.go @@ -23,10 +23,13 @@ type ItemSignature struct { EmailDomainApprovalList []string `json:"domain_whitelist"` GitHubUsernameApprovalList []string `json:"github_whitelist"` GitHubOrgApprovalList []string `json:"github_org_whitelist"` - GitLabUsernameApprovalList []string `json:"gitlab_username_approval_list"` - GitLabGroupApprovalList []string `json:"gitlab_group_approval_list"` + GitlabUsernameApprovalList []string `json:"gitlab_username_approval_list"` + GitlabOrgApprovalList []string `json:"gitlab_org_approval_list"` SignatureACL []string `json:"signature_acl"` + UserGithubID string `json:"user_github_id"` UserGithubUsername string `json:"user_github_username"` + UserGitlabID string `json:"user_gitlab_id"` + UserGitlabUsername string `json:"user_gitlab_username"` UserLFUsername string `json:"user_lf_username"` UserName string `json:"user_name"` UserEmail string `json:"user_email"` diff --git a/cla-backend-go/signatures/models.go b/cla-backend-go/signatures/models.go index db5a76043..0fd083496 100644 --- a/cla-backend-go/signatures/models.go +++ b/cla-backend-go/signatures/models.go @@ -20,6 +20,7 @@ type SignatureCompanyID struct { type ApprovalCriteria struct { UserEmail string GitHubUsername string + GitlabUsername string } //ApprovalList data model @@ -34,10 +35,11 @@ type ApprovalList struct { EmailApprovals []string DomainApprovals []string GitHubUsernameApprovals []string - GHUsernames []string - GHOrgApprovals []string - GitLabUsernameApprovals []string - GitLabGroupApprovals []string + GitHubUsernames []string + GitHubOrgApprovals []string + GitlabUsernameApprovals []string + GitlabOrgApprovals []string + GitlabUsernames []string GerritICLAECLAs []string ICLAs []*models.IclaSignature ECLAs []*models.Signature diff --git a/cla-backend-go/signatures/projections.go b/cla-backend-go/signatures/projections.go index 6bd143612..de7b0e1e5 100644 --- a/cla-backend-go/signatures/projections.go +++ b/cla-backend-go/signatures/projections.go @@ -24,13 +24,14 @@ func buildProjection() expression.ProjectionBuilder { expression.Name("signature_signed"), // T/F expression.Name("signature_type"), // ccla or cla expression.Name("signature_user_ccla_company_id"), // reference to the company - expression.Name("email_whitelist"), - expression.Name("domain_whitelist"), - expression.Name("github_whitelist"), - expression.Name("github_org_whitelist"), - expression.Name("gitlab_username_approval_list"), // added for GitLab support - expression.Name("gitlab_project_approval_list"), // added for GitLab support - expression.Name("user_github_username"), + expression.Name(SignatureEmailApprovalListColumn), + expression.Name(SignatureDomainApprovalListColumn), + expression.Name(SignatureGitHubUsernameApprovalListColumn), + expression.Name(SignatureGitHubOrgApprovalListColumn), + expression.Name(SignatureGitlabUsernameApprovalListColumn), // added for GitLab support + expression.Name(SignatureGitlabOrgApprovalListColumn), // added for GitLab support + expression.Name(SignatureUserGitHubUsername), + expression.Name(SignatureUserGitlabUsername), expression.Name("user_lf_username"), expression.Name("user_name"), expression.Name("user_email"), diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index f937e432d..777a195e2 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -1169,7 +1169,7 @@ func (repo repository) CreateProjectSummaryReport(ctx context.Context, params si }, nil } -// GetProjectCompanySignature returns a the signature for the specified project and specified company with the other query flags +// GetProjectCompanySignature returns the signature for the specified project and specified company with the other query flags func (repo repository) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetProjectCompanySignature", @@ -1513,7 +1513,7 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, if criteria != nil && criteria.GitHubUsername != "" { log.WithFields(f).Debugf("adding Githubusername criteria filter for :%s ", criteria.GitHubUsername) - filter = addAndCondition(filter, expression.Name("user_github_username").Equal(expression.Value(criteria.GitHubUsername)), &filterAdded) + filter = addAndCondition(filter, expression.Name(SignatureUserGitHubUsername).Equal(expression.Value(criteria.GitHubUsername)), &filterAdded) } if criteria != nil && criteria.UserEmail != "" { @@ -2108,10 +2108,12 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Keep track of existing company approvals approvalList := ApprovalList{ + EmailApprovals: cclaSignature.EmailApprovalList, DomainApprovals: cclaSignature.DomainApprovalList, - GHOrgApprovals: cclaSignature.GithubOrgApprovalList, GitHubUsernameApprovals: cclaSignature.GithubUsernameApprovalList, - EmailApprovals: cclaSignature.EmailApprovalList, + GitHubOrgApprovals: cclaSignature.GithubOrgApprovalList, + GitlabUsernameApprovals: cclaSignature.GitlabUsernameApprovalList, + GitlabOrgApprovals: cclaSignature.GitlabOrgApprovalList, CLAManager: claManager, ICLAs: make([]*models.IclaSignature, 0), ECLAs: make([]*models.Signature, 0), @@ -2131,7 +2133,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } authUser := auth.User{ - Email: claManager.LfEmail, + Email: claManager.LfEmail.String(), UserName: claManager.LfUsername, } @@ -2165,7 +2167,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // If we have an add or remove email list...we need to run an update for this column if (params.AddEmailApprovalList != nil && len(params.AddEmailApprovalList) > 0) || (params.RemoveEmailApprovalList != nil && len(params.RemoveEmailApprovalList) > 0) { - columnName := "email_whitelist" + columnName := SignatureEmailApprovalListColumn attrList := buildApprovalAttributeList(ctx, cclaSignature.EmailApprovalList, params.AddEmailApprovalList, params.RemoveEmailApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { @@ -2179,7 +2181,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } } else { haveAdditions = true - expressionAttributeNames["#E"] = aws.String("email_whitelist") + expressionAttributeNames["#E"] = aws.String(columnName) expressionAttributeValues[":e"] = attrList updateExpression = updateExpression + " #E = :e, " } @@ -2207,7 +2209,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model criteria := &ApprovalCriteria{ UserEmail: email, } - log.WithFields(f).Debugf("Updating signature records for emailApprovalList: %+v ", params.RemoveEmailApprovalList) + log.WithFields(f).Debugf("Updating signature records for email approval list: %+v ", params.RemoveEmailApprovalList) signs, appErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) if appErr != nil { log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", appErr) @@ -2215,7 +2217,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if len(signs.Signatures) == 0 { - log.WithFields(f).Debugf("company employee signatures do not exist for company:%s and project: %s ", companyID, projectID) + log.WithFields(f).Debugf("company employee signatures do not exist for company: %s and project: %s ", companyID, projectID) } if len(signs.Signatures) > 0 { @@ -2240,12 +2242,12 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if utils.StringInSlice(user.LfUsername, gerritICLAECLAs) { gerritIclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeICLA) if gerritIclaErr != nil { - msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + msg := fmt.Sprintf("unable to remove gerrit user: %s from group: %s", user.LfUsername, approvalList.ClaGroupID) log.WithFields(f).WithError(gerritIclaErr).Warn(msg) } eclaErr := repo.gerritService.RemoveUserFromGroup(ctx, &authUser, approvalList.ClaGroupID, user.LfUsername, utils.ClaTypeECLA) if eclaErr != nil { - msg := fmt.Sprintf("unable to remove gerrit user:%s from group:%s", user.LfUsername, approvalList.ClaGroupID) + msg := fmt.Sprintf("unable to remove gerrit user: %s from group: %s", user.LfUsername, approvalList.ClaGroupID) log.WithFields(f).WithError(eclaErr).Warn(msg) } } @@ -2284,7 +2286,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model if (params.AddDomainApprovalList != nil && len(params.AddDomainApprovalList) > 0) || (params.RemoveDomainApprovalList != nil && len(params.RemoveDomainApprovalList) > 0) { - columnName := "domain_whitelist" + columnName := SignatureDomainApprovalListColumn attrList := buildApprovalAttributeList(ctx, cclaSignature.DomainApprovalList, params.AddDomainApprovalList, params.RemoveDomainApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { @@ -2342,7 +2344,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if (params.AddGithubUsernameApprovalList != nil && len(params.AddGithubUsernameApprovalList) > 0) || (params.RemoveGithubUsernameApprovalList != nil && len(params.RemoveGithubUsernameApprovalList) > 0) { - columnName := "github_whitelist" + columnName := SignatureGitHubUsernameApprovalListColumn attrList := buildApprovalAttributeList(ctx, cclaSignature.GithubUsernameApprovalList, params.AddGithubUsernameApprovalList, params.RemoveGithubUsernameApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { @@ -2356,9 +2358,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } } else { haveAdditions = true - expressionAttributeNames["#G"] = aws.String(columnName) - expressionAttributeValues[":g"] = attrList - updateExpression = updateExpression + " #G = :g, " + expressionAttributeNames["#GHU"] = aws.String(columnName) + expressionAttributeValues[":ghu"] = attrList + updateExpression = updateExpression + " #GHU = :ghu, " } if params.RemoveGithubUsernameApprovalList != nil { // if email removal update signature approvals @@ -2381,7 +2383,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model criteria := &ApprovalCriteria{ GitHubUsername: ghUsername, } - log.WithFields(f).Debugf("Updating signature records for ghUsernameApporvalList: %+v ", params.RemoveGithubUsernameApprovalList) + log.WithFields(f).Debugf("Updating signature records for github username apporval list: %+v ", params.RemoveGithubUsernameApprovalList) signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) if ghUserErr != nil { log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) @@ -2394,13 +2396,13 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Get ICLAs claUser, claErr := repo.usersRepo.GetUserByGitHubUsername(ghUsername) if claErr != nil { - log.WithFields(f).Debugf("unable to get User by GH Username: %s ", ghUsername) + log.WithFields(f).Debugf("unable to get user by github username: %s ", ghUsername) return } if claUser != nil { icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID, &approved, &signed) if iclaErr != nil || icla == nil { - log.WithFields(f).Debugf("unable to get icla signature for user with ghUsername: %s ", ghUsername) + log.WithFields(f).Debugf("unable to get icla signature for user with github username: %s ", ghUsername) } if icla != nil { // Convert to IclSignature instance to leverage invalidateSignatures helper function @@ -2425,7 +2427,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } if (params.AddGithubOrgApprovalList != nil && len(params.AddGithubOrgApprovalList) > 0) || (params.RemoveGithubOrgApprovalList != nil && len(params.RemoveGithubOrgApprovalList) > 0) { - columnName := "github_org_whitelist" + columnName := SignatureGitHubOrgApprovalListColumn attrList := buildApprovalAttributeList(ctx, cclaSignature.GithubOrgApprovalList, params.AddGithubOrgApprovalList, params.RemoveGithubOrgApprovalList) // If no entries after consolidating all the updates, we need to remove the column if attrList == nil || attrList.L == nil { @@ -2439,9 +2441,9 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model } } else { haveAdditions = true - expressionAttributeNames["#GO"] = aws.String("github_org_whitelist") - expressionAttributeValues[":go"] = attrList - updateExpression = updateExpression + " #GO = :go, " + expressionAttributeNames["#GHO"] = aws.String(columnName) + expressionAttributeValues[":gho"] = attrList + updateExpression = updateExpression + " #GHO = :gho, " } if params.RemoveGithubOrgApprovalList != nil { @@ -2452,14 +2454,14 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model // Get repositories by CLAGroup repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) if getRepoByCLAGroupErr != nil { - msg := fmt.Sprintf("unable to fetch repositories for claGroupID: %s ", projectID) + msg := fmt.Sprintf("unable to fetch repositories for cla group ID: %s ", projectID) log.WithFields(f).WithError(getRepoByCLAGroupErr).Warn(msg) return nil, errors.New(msg) } var ghOrgRepositories []*models.GithubRepository var ghOrgs []*models.GithubOrganization for _, repository := range repositories { - // Check for matching organization name in repositories table against approvalList removal GH Orgs + // Check for matching organization name in repositories table against approvalList removal GitHub organizations if utils.StringInSlice(repository.RepositoryOrganizationName, approvalList.ApprovalList) { ghOrgRepositories = append(ghOrgRepositories, repository) } @@ -2479,18 +2481,169 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model for _, ghOrg := range ghOrgs { ghOrgUsers, getOrgMembersErr := github.GetOrganizationMembers(ctx, ghOrg.OrganizationName, ghOrg.OrganizationInstallationID) if getOrgMembersErr != nil { - msg := fmt.Sprintf("unable to fetch ghOrgUsers for org: %s ", ghOrg.OrganizationName) + msg := fmt.Sprintf("unable to fetch github organization users for org: %s ", ghOrg.OrganizationName) log.WithFields(f).WithError(getOrgMembersErr).Warnf(msg) return nil, errors.New(msg) } ghUsernames = append(ghUsernames, ghOrgUsers...) } - approvalList.GHUsernames = utils.RemoveDuplicates(ghUsernames) + approvalList.GitHubUsernames = utils.RemoveDuplicates(ghUsernames) repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) } } + if (params.AddGitlabUsernameApprovalList != nil && len(params.AddGitlabUsernameApprovalList) > 0) || (params.RemoveGitlabUsernameApprovalList != nil && len(params.RemoveGitlabUsernameApprovalList) > 0) { + columnName := SignatureGitlabUsernameApprovalListColumn + attrList := buildApprovalAttributeList(ctx, cclaSignature.GitlabUsernameApprovalList, params.AddGitlabUsernameApprovalList, params.RemoveGitlabUsernameApprovalList) + // If no entries after consolidating all the updates, we need to remove the column + if attrList == nil || attrList.L == nil { + var rmColErr error + cclaSignature, rmColErr = repo.removeColumn(ctx, cclaSignature.SignatureID, columnName) + if rmColErr != nil { + msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", + columnName, companyID, projectID, true, true) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + } else { + haveAdditions = true + expressionAttributeNames["#GLU"] = aws.String(columnName) + expressionAttributeValues[":glu"] = attrList + updateExpression = updateExpression + " #GLU = :glu, " + } + if params.RemoveGitlabUsernameApprovalList != nil { + // if email removal update signature approvals + if params.RemoveGitlabUsernameApprovalList != nil { + approvalList.Criteria = utils.GitlabUsernameCriteria + approvalList.ApprovalList = params.RemoveGitlabUsernameApprovalList + approvalList.Action = utils.RemoveApprovals + approvalList.ClaGroupID = projectID + approvalList.ClaGroupName = claGroupModel.ProjectName + approvalList.CompanyID = companyID + approvalList.Version = claGroupModel.Version + + // Get ICLAs + var wg sync.WaitGroup + wg.Add(len(params.RemoveGitlabUsernameApprovalList)) + for _, ghUsername := range params.RemoveGitlabUsernameApprovalList { + go func(gitLabUsername string) { + defer wg.Done() + var iclas []*models.IclaSignature + var eclas []*models.Signature + + criteria := &ApprovalCriteria{ + GitlabUsername: gitLabUsername, + } + log.WithFields(f).Debugf("Updating signature records for gitlab username apporval list: %+v ", params.RemoveGitlabUsernameApprovalList) + signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) + if ghUserErr != nil { + log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) + return + } + if signs.Signatures != nil { + approvalList.ECLAs = signs.Signatures + eclas = signs.Signatures + } + + claUser, claErr := repo.usersRepo.GetUserByGitlabUsername(gitLabUsername) + if claErr != nil { + log.WithFields(f).Debugf("unable to get User by gitlab username: %s ", gitLabUsername) + return + } + if claUser != nil { + icla, iclaErr := repo.GetIndividualSignature(ctx, projectID, claUser.UserID, &approved, &signed) + if iclaErr != nil || icla == nil { + log.WithFields(f).Debugf("unable to get icla signature for user with gitlab username: %s ", gitLabUsername) + } + if icla != nil { + // Convert to IclSignature instance to leverage invalidateSignatures helper function + approvalList.ICLAs = []*models.IclaSignature{{ + GitlabUsername: icla.UserGHUsername, + LfUsername: icla.UserLFID, + SignatureID: icla.SignatureID, + }} + } + } + + repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) + + // Send Email + repo.sendEmail(ctx, getBestEmail(claUser), &approvalList, iclas, eclas) + + }(ghUsername) + } + wg.Wait() + } + } + } + + if (params.AddGitlabOrgApprovalList != nil && len(params.AddGitlabOrgApprovalList) > 0) || (params.RemoveGitlabOrgApprovalList != nil && len(params.RemoveGitlabOrgApprovalList) > 0) { + columnName := SignatureGitlabOrgApprovalListColumn + attrList := buildApprovalAttributeList(ctx, cclaSignature.GitlabOrgApprovalList, params.AddGitlabOrgApprovalList, params.RemoveGitlabOrgApprovalList) + // If no entries after consolidating all the updates, we need to remove the column + if attrList == nil || attrList.L == nil { + var rmColErr error + cclaSignature, rmColErr = repo.removeColumn(ctx, cclaSignature.SignatureID, columnName) + if rmColErr != nil { + msg := fmt.Sprintf("unable to remove column %s for signature for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", + columnName, companyID, projectID, true, true) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + } else { + haveAdditions = true + expressionAttributeNames["#GLO"] = aws.String(columnName) + expressionAttributeValues[":glo"] = attrList + updateExpression = updateExpression + " #GLO = :glo, " + } + + if params.RemoveGithubOrgApprovalList != nil { + approvalList.Criteria = utils.GitlabOrgCriteria + approvalList.ApprovalList = params.RemoveGitlabOrgApprovalList + approvalList.Action = utils.RemoveApprovals + approvalList.Version = claGroupModel.Version + // Get repositories by CLAGroup + repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) + if getRepoByCLAGroupErr != nil { + msg := fmt.Sprintf("unable to fetch repositories for cla group ID: %s ", projectID) + log.WithFields(f).WithError(getRepoByCLAGroupErr).Warn(msg) + return nil, errors.New(msg) + } + var gitLabOrgRepositories []*models.GithubRepository + var gitLabOrgs []*models.GithubOrganization + for _, repository := range repositories { + // Check for matching organization name in repositories table against approvalList removal gitlab organizations/groups + if utils.StringInSlice(repository.RepositoryOrganizationName, approvalList.ApprovalList) { + gitLabOrgRepositories = append(gitLabOrgRepositories, repository) + } + } + + for _, gitLabOrgRepo := range gitLabOrgRepositories { + gitLabOrg, getGitlabOrgErr := repo.ghOrgRepo.GetGitHubOrganization(ctx, gitLabOrgRepo.RepositoryOrganizationName) + if getGitlabOrgErr != nil { + msg := fmt.Sprintf("unable to get gitlab organization by name: %s ", gitLabOrgRepo.RepositoryOrganizationName) + log.WithFields(f).WithError(getGitlabOrgErr).Warn(msg) + return nil, errors.New(msg) + } + gitLabOrgs = append(gitLabOrgs, gitLabOrg) + } + + var gitLabUsernames []string + for _, gitLabOrg := range gitLabOrgs { + gitLabOrgUsers, getOrgMembersErr := github.GetOrganizationMembers(ctx, gitLabOrg.OrganizationName, gitLabOrg.OrganizationInstallationID) + if getOrgMembersErr != nil { + msg := fmt.Sprintf("unable to fetch gitLabOrgUsers for org: %s ", gitLabOrg.OrganizationName) + log.WithFields(f).WithError(getOrgMembersErr).Warnf(msg) + return nil, errors.New(msg) + } + gitLabUsernames = append(gitLabUsernames, gitLabOrgUsers...) + } + approvalList.GitlabUsernames = utils.RemoveDuplicates(gitLabUsernames) + + repo.invalidateSignatures(ctx, &approvalList, claManager, eventArgs) + } + } // Ensure at least one value is set for us to update if !haveAdditions { log.WithFields(f).Debugf("no updates required to any of the approved list values company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t - expecting at least something to update", @@ -2511,7 +2664,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model }, ExpressionAttributeNames: expressionAttributeNames, ExpressionAttributeValues: expressionAttributeValues, - UpdateExpression: aws.String(updateExpression), //aws.String("SET #L = :l"), + UpdateExpression: aws.String(updateExpression), } log.WithFields(f).Debugf("updating approval list for company ID: %s project ID: %s, type: ccla, signed: %t, approved: %t", @@ -2721,7 +2874,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur email := getBestEmail(user) authUser := auth.User{ - Email: claManager.LfEmail, + Email: claManager.LfEmail.String(), UserName: claManager.LfUsername, } @@ -2757,7 +2910,7 @@ func (repo repository) verifyUserApprovals(ctx context.Context, userID, signatur } } else if approvalList.Criteria == utils.GitHubOrgCriteria { // Handle GH Org Approvals - if utils.StringInSlice(user.GithubUsername, approvalList.GHUsernames) { + if utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernames) { if !utils.StringInSlice(getBestEmail(user), approvalList.EmailApprovals) && !utils.StringInSlice(user.GithubUsername, approvalList.GitHubUsernameApprovals) { //Invalidate record @@ -2869,7 +3022,7 @@ func (repo repository) AddUsersDetails(ctx context.Context, signatureID string, } var email string if userModel.LfEmail != "" { - email = userModel.LfEmail + email = userModel.LfEmail.String() } else { if len(userModel.Emails) > 0 { email = userModel.Emails[0] @@ -2885,7 +3038,7 @@ func (repo repository) AddUsersDetails(ctx context.Context, signatureID string, }, } ue := utils.NewDynamoUpdateExpression() - ue.AddAttributeName("#gh_username", "user_github_username", userModel.GithubUsername != "") + ue.AddAttributeName("#gh_username", SignatureUserGitHubUsername, userModel.GithubUsername != "") ue.AddAttributeName("#lf_username", "user_lf_username", userModel.LfUsername != "") ue.AddAttributeName("#name", "user_name", userModel.Username != "") ue.AddAttributeName("#email", "user_email", email != "") @@ -2996,7 +3149,7 @@ func (repo repository) GetClaGroupICLASignatures(ctx context.Context, claGroupID log.WithFields(f).Debugf("adding search term filter for: '%s'", searchTermValue) searchTermExpression := expression.Name("signature_reference_name_lower").Contains(strings.ToLower(searchTermValue)). Or(expression.Name("user_email").Contains(strings.ToLower(searchTermValue))). - Or(expression.Name("user_github_username").Contains(strings.ToLower(searchTermValue))). + Or(expression.Name(SignatureUserGitHubUsername).Contains(strings.ToLower(searchTermValue))). Or(expression.Name("user_docusign_name").Contains(strings.ToLower(searchTermValue))) filter = addAndCondition(filter, searchTermExpression, &filterAdded) } diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index fd293811c..b78bf2b6e 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -527,6 +527,10 @@ func buildApprovalListSummary(approvalListChanges *models.ApprovalList) string { approvalListSummary += appendList(approvalListChanges.RemoveGithubUsernameApprovalList, "Removed GitHub User:") approvalListSummary += appendList(approvalListChanges.AddGithubOrgApprovalList, "Added GitHub Organization:") approvalListSummary += appendList(approvalListChanges.RemoveGithubOrgApprovalList, "Removed GitHub Organization:") + approvalListSummary += appendList(approvalListChanges.AddGitlabUsernameApprovalList, "Added Gitlab User:") + approvalListSummary += appendList(approvalListChanges.RemoveGitlabUsernameApprovalList, "Removed Gitlab User:") + approvalListSummary += appendList(approvalListChanges.AddGitlabOrgApprovalList, "Added Gitlab Organization:") + approvalListSummary += appendList(approvalListChanges.RemoveGitlabOrgApprovalList, "Removed Gitlab Organization:") approvalListSummary += "" return approvalListSummary } @@ -627,25 +631,66 @@ func (s service) getRemoveGitHubContributors(approvalList *models.ApprovalList) return userModelList } + +// getAddGitlabContributors is a helper function to look up the Gitlab contributors impacted by the Approval List update +func (s service) getAddGitlabContributors(approvalList *models.ApprovalList) []*models.User { + var userModelList []*models.User + for _, value := range approvalList.AddGitlabUsernameApprovalList { + userModel, err := s.usersService.GetUserByGitHubUsername(value) + if err != nil { + log.Warnf("unable to lookup user by Gitlab username: %s, error: %+v", value, err) + } else { + userModelList = append(userModelList, userModel) + } + } + + return userModelList +} + +// getRemoveGitlabContributors is a helper function to look up the Gitlab contributors impacted by the Approval List update +func (s service) getRemoveGitlabContributors(approvalList *models.ApprovalList) []*models.User { + var userModelList []*models.User + for _, value := range approvalList.RemoveGitlabUsernameApprovalList { + userModel, err := s.usersService.GetUserByGitHubUsername(value) + if err != nil { + log.Warnf("unable to lookup user by Gitlab username: %s, error: %+v", value, err) + } else { + userModelList = append(userModelList, userModel) + } + } + + return userModelList +} + func (s service) sendRequestAccessEmailToContributors(authUser *auth.User, companyModel *models.Company, claGroupModel *models.ClaGroup, approvalList *models.ApprovalList) { addEmailUsers := s.getAddEmailContributors(approvalList) for _, user := range addEmailUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "added", "to", + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail.String(), "added", "to", fmt.Sprintf("you are authorized to contribute to %s on behalf of %s", claGroupModel.ProjectName, companyModel.CompanyName)) } removeEmailUsers := s.getRemoveEmailContributors(approvalList) for _, user := range removeEmailUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "removed", "from", + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail.String(), "removed", "from", fmt.Sprintf("you are no longer authorized to contribute to %s on behalf of %s ", claGroupModel.ProjectName, companyModel.CompanyName)) } addGitHubUsers := s.getAddGitHubContributors(approvalList) for _, user := range addGitHubUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "added", "to", + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail.String(), "added", "to", fmt.Sprintf("you are authorized to contribute to %s on behalf of %s", claGroupModel.ProjectName, companyModel.CompanyName)) } removeGitHubUsers := s.getRemoveGitHubContributors(approvalList) for _, user := range removeGitHubUsers { - sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail, "removed", "from", + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail.String(), "removed", "from", + fmt.Sprintf("you are no longer authorized to contribute to %s on behalf of %s ", claGroupModel.ProjectName, companyModel.CompanyName)) + } + addGitlabUsers := s.getAddGitlabContributors(approvalList) + for _, user := range addGitlabUsers { + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail.String(), "added", "to", + fmt.Sprintf("you are authorized to contribute to %s on behalf of %s", claGroupModel.ProjectName, companyModel.CompanyName)) + } + removeGitlabUsers := s.getRemoveGitlabContributors(approvalList) + for _, user := range removeGitlabUsers { + sendRequestAccessEmailToContributorRecipient(authUser, companyModel, claGroupModel, user.Username, user.LfEmail.String(), "removed", "from", fmt.Sprintf("you are no longer authorized to contribute to %s on behalf of %s ", claGroupModel.ProjectName, companyModel.CompanyName)) } } @@ -788,6 +833,75 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models }, }) } + for _, value := range approvalList.AddGitlabUsernameApprovalList { + // Send an event + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, + EventData: &events.CLAApprovalListAddGitlabUsernameData{ + ApprovalListGitlabUsername: value, + }, + }) + } + for _, value := range approvalList.RemoveGitlabUsernameApprovalList { + // Send an event + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, + EventData: &events.CLAApprovalListRemoveGitlabUsernameData{ + ApprovalListGitlabUsername: value, + }, + }) + } + for _, value := range approvalList.AddGitlabOrgApprovalList { + // Send an event + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ClaApprovalListUpdated, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, + EventData: &events.CLAApprovalListAddGitlabOrgData{ + ApprovalListGitlabOrg: value, + }, + }) + } + for _, value := range approvalList.RemoveGitlabOrgApprovalList { + // Send an event + s.eventsService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.ClaApprovalListUpdated, + CLAGroupID: claGroupModel.ProjectID, + ProjectID: claGroupModel.ProjectExternalID, + ClaGroupModel: claGroupModel, + CompanyID: companyModel.CompanyID, + CompanyModel: companyModel, + LfUsername: userModel.LfUsername, + UserID: userModel.UserID, + UserModel: userModel, + ProjectSFID: claGroupModel.ProjectExternalID, + EventData: &events.CLAApprovalListRemoveGitlabOrgData{ + ApprovalListGitlabOrg: value, + }, + }) + } } func (s service) GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string) (*models.IclaSignatures, error) { @@ -853,7 +967,7 @@ func sendRequestAccessEmailToContributorRecipient(authUser *auth.User, companyMo // getBestEmail is a helper function to return the best email address for the user model func getBestEmail(userModel *models.User) string { if userModel.LfEmail != "" { - return userModel.LfEmail + return userModel.LfEmail.String() } for _, email := range userModel.Emails { diff --git a/cla-backend-go/swagger/common/icla-signature.yaml b/cla-backend-go/swagger/common/icla-signature.yaml index 58bde5232..39662700c 100644 --- a/cla-backend-go/swagger/common/icla-signature.yaml +++ b/cla-backend-go/swagger/common/icla-signature.yaml @@ -14,6 +14,10 @@ properties: type: string description: the user's github username example: 'tomcruise' + gitlab_username: + type: string + description: the user's gitlab username + example: 'tomcruise' lf_username: type: string description: the user's LF username diff --git a/cla-backend-go/swagger/common/signature-approval-list.yaml b/cla-backend-go/swagger/common/signature-approval-list.yaml index 1fc372e91..a9f80ad0f 100644 --- a/cla-backend-go/swagger/common/signature-approval-list.yaml +++ b/cla-backend-go/swagger/common/signature-approval-list.yaml @@ -7,50 +7,87 @@ description: A signature approval list for a project / company properties: AddEmailApprovalList: type: array + title: Add User Email description: a list of zero or more email addresses to be added to the approval list x-nullable: true items: type: string RemoveEmailApprovalList: type: array - description: a list of zero or more email addresses to be from to the approval list + title: Remove User Email + description: a list of zero or more email addresses to be removed from the approval list x-nullable: true items: type: string AddDomainApprovalList: type: array + title: Add Domain Email description: a list of zero or more domains to be added to the approval list x-nullable: true items: type: string + example: 'linuxfoundation.org' RemoveDomainApprovalList: type: array + title: Remove Domain Email description: a list of zero or more domains to be removed from the approval list x-nullable: true items: type: string + example: 'linuxfoundation.org' AddGithubUsernameApprovalList: type: array + title: Add GitHub Username description: a list of zero or more GitHub user name values to be added to the approval list x-nullable: true items: type: string RemoveGithubUsernameApprovalList: type: array + title: Remove GitHub Username description: a list of zero or more GitHub user name values to be removed from the approval list x-nullable: true items: type: string AddGithubOrgApprovalList: type: array + title: Add GitHub Organization description: a list of zero or more GitHub organization values to be added to the approval list x-nullable: true items: type: string RemoveGithubOrgApprovalList: type: array + title: Remove GitHub Organization description: a list of zero or more GitHub organization values to be removed from the approval list x-nullable: true items: type: string - + AddGitlabUsernameApprovalList: + type: array + title: Add Gitlab Username + description: a list of zero or more Gitlab user name values to be added to the approval list + x-nullable: true + items: + type: string + RemoveGitlabUsernameApprovalList: + type: array + title: Remove Gitlab Username + description: a list of zero or more Gitlab user name values to be removed from the approval list + x-nullable: true + items: + type: string + AddGitlabOrgApprovalList: + type: array + title: Add Gitlab Organization + description: a list of zero or more Gitlab organization values to be added to the approval list + x-nullable: true + items: + type: string + RemoveGitlabOrgApprovalList: + type: array + title: Remove Gitlab Organization + description: a list of zero or more Gitlab organization values to be removed from the approval list + x-nullable: true + items: + type: string diff --git a/cla-backend-go/swagger/common/signature.yaml b/cla-backend-go/swagger/common/signature.yaml index 0e61a9012..912ffca13 100644 --- a/cla-backend-go/swagger/common/signature.yaml +++ b/cla-backend-go/swagger/common/signature.yaml @@ -81,7 +81,15 @@ properties: userGHUsername: type: string description: the user's GitHub username, when available - example: linux-user + example: 'github-user' + userGitlabID: + type: string + description: the user's Gitlab ID, when available + example: '1864' + userGitlabUsername: + type: string + description: the user's Gitlab username, when available + example: 'gitlab-user' userLFID: type: string description: the user's LF Login ID @@ -132,6 +140,18 @@ properties: x-nullable: true items: type: string + gitlabUsernameApprovalList: + type: array + description: a list of zero or more Gitlab user name values in the approval list + x-nullable: true + items: + type: string + gitlabOrgApprovalList: + type: array + description: a list of zero or more Gitlab organization values in the approval list + x-nullable: true + items: + type: string userDocusignName: type: string description: full name used on docusign document diff --git a/cla-backend-go/swagger/common/user.yaml b/cla-backend-go/swagger/common/user.yaml index d4f332b0a..4e59f25c1 100644 --- a/cla-backend-go/swagger/common/user.yaml +++ b/cla-backend-go/swagger/common/user.yaml @@ -7,9 +7,11 @@ title: User description: User model properties: userID: - type: string + $ref: './common/properties/internal-id.yaml' + description: the user's internal/unique ID userExternalID: - type: string + $ref: './common/properties/external-id.yaml' + description: the user's external ID tied to SF username: type: string dateCreated: @@ -17,21 +19,37 @@ properties: dateModified: type: string lfEmail: - type: string + $ref: './common/properties/email.yaml' lfUsername: type: string companyID: - type: string + $ref: './common/properties/internal-id.yaml' + description: the user's optional company ID githubID: type: string + description: the user's github ID + example: '123434' githubUsername: type: string + description: the user's github username + example: 'grapes42' + gitlabID: + type: string + description: the user's gitlab ID + example: '123434' + gitlabUsername: + type: string + description: the user's gitlab username + example: 'orangejuice' admin: type: boolean version: type: string + description: the version identifier for this record + example: 'v1' note: type: string + description: an optional note for this user record emails: type: array items: diff --git a/cla-backend-go/tests/utils_test.go b/cla-backend-go/tests/utils_test.go index a180ad0a3..235a82bd5 100644 --- a/cla-backend-go/tests/utils_test.go +++ b/cla-backend-go/tests/utils_test.go @@ -9,8 +9,6 @@ import ( "testing" "time" - "github.com/gofrs/uuid" - "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/stretchr/testify/assert" @@ -214,158 +212,6 @@ func TestTrimRemoveTrailingSpace(t *testing.T) { } -// TestValidEmail tests the email validator -func TestValidEmail(t *testing.T) { - validEmails := []string{ - "user@linuxfoundation.org", - "user+test@linuxfoundation.org", - } - inValidEmails := []string{ - "user@linuxfoundation_org", - "user/linuxfoundation.org", - "userlinuxfoundation.org", - } - - for _, email := range validEmails { - assert.True(t, utils.ValidEmail(email), fmt.Sprintf("valid email %s", email)) - } - - for _, email := range inValidEmails { - assert.False(t, utils.ValidEmail(email), fmt.Sprintf("invalid email %s", email)) - } -} - -// TestValidDomain tests the domain validator -func TestValidDomain(t *testing.T) { - validDomains := []string{ - "linuxfoundation.org", - "wikipedia.org", - "google.com", - "slack.com", - "slack-domain-with-dash.com", - } - - validWildcardDomains := []string{ - "linuxfoundation.org", - "wikipedia.org", - "google.com", - "slack.com", - "slack-domain-with-dash.com", - "*.google.com", - "*.us.google.com", - } - - inValidDomains := []string{ - "*.google.com", // test case with allowWildcards = false - "linuxfoundation_org", - "*.linuxfoundation_org", // test case with allowWildcards = false - "/linuxfoundation.org", - "linuxfoundation+fun.org", - "user_linuxfoundation.org", - } - - inWildcardValidDomains := []string{ - "linuxfoundation_org", - "/linuxfoundation.org", - "linuxfoundation+fun.org", - "*.linuxfoundation+fun.org", - "user_linuxfoundation.org", - "*.user_linuxfoundation.org", - } - - for _, domain := range validDomains { - msg, valid := utils.ValidDomain(domain, false) - assert.True(t, valid, fmt.Sprintf("valid domain %s %s", domain, msg)) - } - - for _, domain := range validWildcardDomains { - msg, valid := utils.ValidDomain(domain, true) - assert.True(t, valid, fmt.Sprintf("valid domain %s %s", domain, msg)) - } - - for _, domain := range inValidDomains { - msg, valid := utils.ValidDomain(domain, false) - assert.False(t, valid, fmt.Sprintf("invalid domain %s %s", domain, msg)) - } - - for _, domain := range inWildcardValidDomains { - msg, valid := utils.ValidDomain(domain, true) - assert.False(t, valid, fmt.Sprintf("invalid domain %s %s", domain, msg)) - } -} - -// TestGitHubUsername tests the GitHub username validator -func TestGitHubUsername(t *testing.T) { - validGitHubUsername := []string{ - "linuxfoundation", - "user123", - "user_123", - "user_name_with_underscores", - } - inValidGitHubUsername := []string{ - "li", // too short - "/linuxfoundation", - "linuxfoundation+fun", - "user&linuxfoundation", - "user{linuxfoundation", - "user}linuxfoundation", - "user*linuxfoundation", - "user@linuxfoundation", - "user!linuxfoundation", - "user^linuxfoundation", - "++userlinuxfoundation", - "\\userlinuxfoundation", - } - - for _, username := range validGitHubUsername { - msg, valid := utils.ValidGitHubUsername(username) - assert.True(t, valid, fmt.Sprintf("valid GitHub Username %s %s", username, msg)) - } - - for _, username := range inValidGitHubUsername { - msg, valid := utils.ValidGitHubUsername(username) - assert.False(t, valid, fmt.Sprintf("invalid GitHub Username %s %s", username, msg)) - } -} - -// TestGitHubOrg tests the GitHub username validator -func TestGitHubOrg(t *testing.T) { - validGitHubOrg := []string{ - "linuxfoundation", - "linuxfoundation.org", - "user123", - "user-123", - "user-123.org", - "user-123.com", - "user_123", - "user_name_with_underscores", - } - inValidGitHubOrg := []string{ - "li", // too short - "/linuxfoundation", - "linuxfoundation+fun", - "user&linuxfoundation", - "user{linuxfoundation", - "user}linuxfoundation", - "user*linuxfoundation", - "user@linuxfoundation", - "user!linuxfoundation", - "user^linuxfoundation", - "++userlinuxfoundation", - "\\userlinuxfoundation", - } - - for _, org := range validGitHubOrg { - msg, valid := utils.ValidGitHubOrg(org) - assert.True(t, valid, fmt.Sprintf("valid GitHub Organization %s %s", org, msg)) - } - - for _, org := range inValidGitHubOrg { - msg, valid := utils.ValidGitHubOrg(org) - assert.False(t, valid, fmt.Sprintf("invalid GitHub Organization %s %s", org, msg)) - } -} - // TestGetPathFromURL tests for getting the path for a URL func TestGetPathFromURL(t *testing.T) { input := "https://cla-signature-files-dev.s3.amazonaws.com/contract-group/66b97366-a298-4625-965e-0c292c39f9a2/template/ccla-2020-09-25T22-37-51Z.pdf" @@ -374,38 +220,3 @@ func TestGetPathFromURL(t *testing.T) { assert.Nil(t, err, "GetPathFromURL error is nil") assert.Equal(t, expected, result) } - -func TestIsUUIDv4True(t *testing.T) { - v4, err := uuid.NewV4() - assert.Nil(t, err, "NewV4 UUID is nil") - assert.True(t, utils.IsUUIDv4(v4.String()), fmt.Sprintf("%s is a v4 UUID", v4.String())) -} - -func TestIsUUIDv4LikeSFID(t *testing.T) { - sfid := "0014100000TdznWAAR" - assert.False(t, utils.IsUUIDv4(sfid), fmt.Sprintf("%s is not v4 UUID", sfid)) -} - -func TestIsSalesForceID(t *testing.T) { - trueTestData := []string{ - "00117000015vpjX", - "00117000015vpjXAAQ", - } - falseTestData := []string{ - "", - "00117", - "-00117", - "00117000015vpj-", - "0011700001-vpjXAAQ", - "0011700001?vpjXAAQ", - "0011700001&vpjXAAQ", - "0011700001_vpjXAAQ", - } - - for i := range trueTestData { - assert.True(t, utils.IsSalesForceID(trueTestData[i])) - } - for i := range falseTestData { - assert.False(t, utils.IsSalesForceID(falseTestData[i])) - } -} diff --git a/cla-backend-go/tests/utils_validators_test.go b/cla-backend-go/tests/utils_validators_test.go new file mode 100644 index 000000000..fab0ff3e8 --- /dev/null +++ b/cla-backend-go/tests/utils_validators_test.go @@ -0,0 +1,272 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "fmt" + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/gofrs/uuid" + + "github.com/stretchr/testify/assert" +) + +// TestValidEmail tests the email validator +func TestValidEmail(t *testing.T) { + validEmails := []string{ + "user@linuxfoundation.org", + "user+test@linuxfoundation.org", + } + inValidEmails := []string{ + "user@linuxfoundation_org", + "user/linuxfoundation.org", + "userlinuxfoundation.org", + } + + for _, email := range validEmails { + assert.True(t, utils.ValidEmail(email), fmt.Sprintf("valid email %s", email)) + } + + for _, email := range inValidEmails { + assert.False(t, utils.ValidEmail(email), fmt.Sprintf("invalid email %s", email)) + } +} + +// TestValidDomain tests the domain validator +func TestValidDomain(t *testing.T) { + validDomains := []string{ + "linuxfoundation.org", + "wikipedia.org", + "google.com", + "slack.com", + "slack-domain-with-dash.com", + } + + validWildcardDomains := []string{ + "linuxfoundation.org", + "wikipedia.org", + "google.com", + "slack.com", + "slack-domain-with-dash.com", + "*.google.com", + "*.us.google.com", + } + + inValidDomains := []string{ + "*.google.com", // test case with allowWildcards = false + "linuxfoundation_org", + "*.linuxfoundation_org", // test case with allowWildcards = false + "/linuxfoundation.org", + "linuxfoundation+fun.org", + "user_linuxfoundation.org", + } + + inWildcardValidDomains := []string{ + "linuxfoundation_org", + "/linuxfoundation.org", + "linuxfoundation+fun.org", + "*.linuxfoundation+fun.org", + "user_linuxfoundation.org", + "*.user_linuxfoundation.org", + } + + for _, domain := range validDomains { + msg, valid := utils.ValidDomain(domain, false) + assert.True(t, valid, fmt.Sprintf("valid domain %s %s", domain, msg)) + } + + for _, domain := range validWildcardDomains { + msg, valid := utils.ValidDomain(domain, true) + assert.True(t, valid, fmt.Sprintf("valid domain %s %s", domain, msg)) + } + + for _, domain := range inValidDomains { + msg, valid := utils.ValidDomain(domain, false) + assert.False(t, valid, fmt.Sprintf("invalid domain %s %s", domain, msg)) + } + + for _, domain := range inWildcardValidDomains { + msg, valid := utils.ValidDomain(domain, true) + assert.False(t, valid, fmt.Sprintf("invalid domain %s %s", domain, msg)) + } +} + +// TestGitHubUsername tests the GitHub username validator +func TestGitHubUsername(t *testing.T) { + validGitHubUsername := []string{ + "linuxfoundation", + "user123", + "user_123", + "user_name_with_underscores", + } + inValidGitHubUsername := []string{ + "li", // too short + "/linuxfoundation", + "linuxfoundation+fun", + "user&linuxfoundation", + "user{linuxfoundation", + "user}linuxfoundation", + "user*linuxfoundation", + "user@linuxfoundation", + "user!linuxfoundation", + "user^linuxfoundation", + "++userlinuxfoundation", + "\\userlinuxfoundation", + } + + for _, username := range validGitHubUsername { + msg, valid := utils.ValidGitHubUsername(username) + assert.True(t, valid, fmt.Sprintf("valid GitHub Username %s %s", username, msg)) + } + + for _, username := range inValidGitHubUsername { + msg, valid := utils.ValidGitHubUsername(username) + assert.False(t, valid, fmt.Sprintf("invalid GitHub Username %s %s", username, msg)) + } +} + +// TestGitHubOrg tests the GitHub username validator +func TestGitHubOrg(t *testing.T) { + validGitHubOrg := []string{ + "linuxfoundation", + "linuxfoundation.org", + "user123", + "user-123", + "user-123.org", + "user-123.com", + "user_123", + "user_name_with_underscores", + } + inValidGitHubOrg := []string{ + "li", // too short + "/linuxfoundation", + "linuxfoundation+fun", + "user&linuxfoundation", + "user{linuxfoundation", + "user}linuxfoundation", + "user*linuxfoundation", + "user@linuxfoundation", + "user!linuxfoundation", + "user^linuxfoundation", + "++userlinuxfoundation", + "\\userlinuxfoundation", + } + + for _, org := range validGitHubOrg { + msg, valid := utils.ValidGitHubOrg(org) + assert.True(t, valid, fmt.Sprintf("valid GitHub Organization %s %s", org, msg)) + } + + for _, org := range inValidGitHubOrg { + msg, valid := utils.ValidGitHubOrg(org) + assert.False(t, valid, fmt.Sprintf("invalid GitHub Organization %s %s", org, msg)) + } +} + +// TestGitlabUsername tests the Gitlab username validator +func TestGitlabUsername(t *testing.T) { + validGitlabUsername := []string{ + "linuxfoundationuser", + "user1234", + "user_1234", + "user_name_with_underscores_gitlab", + } + inValidGitlabUsername := []string{ + "ii", // too short + "/linuxfoundationuser", + "linuxfoundationuser+fun", + "user&linuxfoundationuser", + "user{linuxfoundationuser", + "user}linuxfoundationuser", + "user*linuxfoundationuser", + "user@linuxfoundationuser", + "user!linuxfoundationuser", + "user^linuxfoundationuser", + "++userlinuxfoundationuser", + "\\userlinuxfoundationuser", + } + + for _, username := range validGitlabUsername { + msg, valid := utils.ValidGitlabUsername(username) + assert.True(t, valid, fmt.Sprintf("valid Gitlab Username %s %s", username, msg)) + } + + for _, username := range inValidGitlabUsername { + msg, valid := utils.ValidGitlabUsername(username) + assert.False(t, valid, fmt.Sprintf("invalid Gitlab Username %s %s", username, msg)) + } +} + +// TestGitlabOrg tests the GitHub username validator +func TestGitlabOrg(t *testing.T) { + validGitlabOrg := []string{ + "linuxfoundationgrp", + "linuxfoundationgrp.org", + "user1234", + "user-1234", + "user-1234.org", + "user-1234.com", + "user_1234", + "user_name_with_underscores_gitlab", + } + inValidGitlabOrg := []string{ + "hi", // too short + "/linuxfoundationgrp", + "linuxfoundationgrp+fun", + "user&linuxfoundationgrp", + "user{linuxfoundationgrp", + "user}linuxfoundationgrp", + "user*linuxfoundationgrp", + "user@linuxfoundationgrp", + "user!linuxfoundationgrp", + "user^linuxfoundationgrp", + "++userlinuxfoundationgrp", + "\\userlinuxfoundationgrp", + } + + for _, org := range validGitlabOrg { + msg, valid := utils.ValidGitHubOrg(org) + assert.True(t, valid, fmt.Sprintf("valid GitHub Organization %s %s", org, msg)) + } + + for _, org := range inValidGitlabOrg { + msg, valid := utils.ValidGitHubOrg(org) + assert.False(t, valid, fmt.Sprintf("invalid GitHub Organization %s %s", org, msg)) + } +} +func TestIsUUIDv4True(t *testing.T) { + v4, err := uuid.NewV4() + assert.Nil(t, err, "NewV4 UUID is nil") + assert.True(t, utils.IsUUIDv4(v4.String()), fmt.Sprintf("%s is a v4 UUID", v4.String())) +} + +func TestIsUUIDv4LikeSFID(t *testing.T) { + sfid := "0014100000TdznWAAR" + assert.False(t, utils.IsUUIDv4(sfid), fmt.Sprintf("%s is not v4 UUID", sfid)) +} + +func TestIsSalesForceID(t *testing.T) { + trueTestData := []string{ + "00117000015vpjX", + "00117000015vpjXAAQ", + } + falseTestData := []string{ + "", + "00117", + "-00117", + "00117000015vpj-", + "0011700001-vpjXAAQ", + "0011700001?vpjXAAQ", + "0011700001&vpjXAAQ", + "0011700001_vpjXAAQ", + } + + for i := range trueTestData { + assert.True(t, utils.IsSalesForceID(trueTestData[i])) + } + for i := range falseTestData { + assert.False(t, utils.IsSalesForceID(falseTestData[i])) + } +} diff --git a/cla-backend-go/userSubscribeLambda/main.go b/cla-backend-go/userSubscribeLambda/main.go index 8d3730785..959026f76 100644 --- a/cla-backend-go/userSubscribeLambda/main.go +++ b/cla-backend-go/userSubscribeLambda/main.go @@ -9,6 +9,8 @@ import ( "os" "runtime" + "github.com/go-openapi/strfmt" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" @@ -162,7 +164,7 @@ func Create(ctx context.Context, user event.Event) { DateCreated: nowStr, DateModified: nowStr, Emails: emails, - LfEmail: primaryEmail, + LfEmail: strfmt.Email(primaryEmail), LfUsername: sfdcUserObject.Username, Note: "Create via user-service event", UserExternalID: sfdcUserObject.ID, @@ -306,7 +308,7 @@ func createUserFromUpdatedModel(userModelUpdated *usersModels.UserUpdated) error newUserModel := &models.User{ Emails: emails, - LfEmail: primaryEmail, + LfEmail: strfmt.Email(primaryEmail), LfUsername: sfdcUserObject.Username, Note: "Update via user-service event", UserExternalID: sfdcUserObject.ID, diff --git a/cla-backend-go/users/handlers.go b/cla-backend-go/users/handlers.go index ee0b84f28..3350a3391 100644 --- a/cla-backend-go/users/handlers.go +++ b/cla-backend-go/users/handlers.go @@ -6,6 +6,8 @@ package users import ( "fmt" + "github.com/go-openapi/strfmt" + "github.com/sirupsen/logrus" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -43,7 +45,7 @@ func Configure(api *operations.ClaAPI, service Service, eventsService events.Ser } newUser := &models.User{ - LfEmail: claUser.LFEmail, + LfEmail: strfmt.Email(claUser.LFEmail), LfUsername: claUser.LFUsername, Username: claUser.Name, } diff --git a/cla-backend-go/users/models.go b/cla-backend-go/users/models.go index 30196061d..461c4944d 100644 --- a/cla-backend-go/users/models.go +++ b/cla-backend-go/users/models.go @@ -16,7 +16,9 @@ type DBUser struct { Version string `json:"version"` UserEmails []string `json:"user_emails"` UserGithubID string `json:"user_github_id"` - UserCompanyID string `json:"user_company_id"` UserGithubUsername string `json:"user_github_username"` + UserGitlabID string `json:"user_gitlab_id"` + UserGitlabUsername string `json:"user_gitlab_username"` + UserCompanyID string `json:"user_company_id"` Note string `json:"note"` } diff --git a/cla-backend-go/users/repository.go b/cla-backend-go/users/repository.go index 1d633f3c7..24123b118 100644 --- a/cla-backend-go/users/repository.go +++ b/cla-backend-go/users/repository.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/go-openapi/strfmt" + "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" @@ -39,7 +41,10 @@ type UserRepository interface { GetUserByExternalID(userExternalID string) (*models.User, error) GetUserByUserName(userName string, fullMatch bool) (*models.User, error) GetUserByEmail(userEmail string) (*models.User, error) + GetUserByGitHubID(gitHubID string) (*models.User, error) GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) + GetUserByGitlabID(gitlabID string) (*models.User, error) + GetUserByGitlabUsername(gitlabUsername string) (*models.User, error) SearchUsers(searchField string, searchTerm string, fullMatch bool) (*models.Users, error) } @@ -100,9 +105,21 @@ func (repo repository) CreateUser(user *models.User) (*models.User, error) { } } + if user.GitlabID != "" { + attributes["user_gitlab_id"] = &dynamodb.AttributeValue{ + S: aws.String(user.GitlabID), + } + } + + if user.GitlabUsername != "" { + attributes["user_gitlab_username"] = &dynamodb.AttributeValue{ + S: aws.String(user.GitlabUsername), + } + } + if user.LfEmail != "" { attributes["lf_email"] = &dynamodb.AttributeValue{ - S: aws.String(user.LfEmail), + S: aws.String(user.LfEmail.String()), } } @@ -257,7 +274,7 @@ func (repo repository) Save(user *models.UserUpdate) (*models.User, error) { expressionAttributeValues := map[string]*dynamodb.AttributeValue{} updateExpression := "SET " - if user.LfEmail != "" && oldUserModel.LfEmail != user.LfEmail { + if user.LfEmail != "" && oldUserModel.LfEmail.String() != user.LfEmail { log.WithFields(f).Debugf("building query - adding lf_email: %s", user.LfEmail) expressionAttributeNames["#E"] = aws.String("lf_email") expressionAttributeValues[":e"] = &dynamodb.AttributeValue{S: aws.String(user.LfEmail)} @@ -694,10 +711,64 @@ func (repo repository) GetUserByEmail(userEmail string) (*models.User, error) { return convertDBUserModel(dbUserModels[0]), nil } +// GetUserByGitHubID fetches the user record by github ID +func (repo repository) GetUserByGitHubID(gitHubID string) (*models.User, error) { + f := logrus.Fields{ + "functionName": "users.repository.GetUserByGitHubID", + "gitHubID": gitHubID, + } + // This is the key we want to match + condition := expression.Key("user_github_id").Equal(expression.Value(gitHubID)) + + // These are the columns we want returned + projection := buildUserProjection() + + // Use the nice builder to create the expression + expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(projection).Build() + if err != nil { + log.WithFields(f).WithError(err).Warnf("error building expression for user_github_id : %s, error: %v", gitHubID, err) + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + TableName: aws.String(repo.tableName), + IndexName: aws.String("github-id-index"), + } + + // Make the DynamoDB Query API call + result, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving user by user_github_id: %s, error: %+v", gitHubID, err) + return nil, err + } + + // The user model + var dbUserModels []DBUser + + err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &dbUserModels) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error unmarshalling user record from database for user_github_id: %s, error: %+v", gitHubID, err) + return nil, err + } + + if len(dbUserModels) == 0 { + return nil, errors.NotFound("user not found when searching by user_github_id: %s", gitHubID) + } else if len(dbUserModels) > 1 { + log.WithFields(f).WithError(err).Warnf("retrieved %d results for the user_github_id query when we should return 0 or 1", len(dbUserModels)) + } + + return convertDBUserModel(dbUserModels[0]), nil +} + // GetUserByGitHubUsername fetches the user record by github username func (repo repository) GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) { f := logrus.Fields{ - "functionName": "users.repository.GetUserByGitHubUsername", + "functionName": "users.repository.GetUserByGitlabUsername", "gitHubUsername": gitHubUsername, } // This is the key we want to match @@ -748,6 +819,114 @@ func (repo repository) GetUserByGitHubUsername(gitHubUsername string) (*models.U return convertDBUserModel(dbUserModels[0]), nil } +// GetUserByGitlabID fetches the user record by gitlab ID +func (repo repository) GetUserByGitlabID(gitlabID string) (*models.User, error) { + f := logrus.Fields{ + "functionName": "users.repository.GetUserByGitlabID", + "gitlabID": gitlabID, + } + // This is the key we want to match + condition := expression.Key("user_gitlab_id").Equal(expression.Value(gitlabID)) + + // These are the columns we want returned + projection := buildUserProjection() + + // Use the nice builder to create the expression + expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(projection).Build() + if err != nil { + log.WithFields(f).WithError(err).Warnf("error building expression for user_gitlab_id : %s, error: %v", gitlabID, err) + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + TableName: aws.String(repo.tableName), + IndexName: aws.String("gitlab-id-index"), + } + + // Make the DynamoDB Query API call + result, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving user by user_gitlab_id: %s, error: %+v", gitlabID, err) + return nil, err + } + + // The user model + var dbUserModels []DBUser + + err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &dbUserModels) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error unmarshalling user record from database for user_gitlab_id: %s, error: %+v", gitlabID, err) + return nil, err + } + + if len(dbUserModels) == 0 { + return nil, errors.NotFound("user not found when searching by user_gitlab_id: %s", gitlabID) + } else if len(dbUserModels) > 1 { + log.WithFields(f).WithError(err).Warnf("retrieved %d results for the user_gitlab_id query when we should return 0 or 1", len(dbUserModels)) + } + + return convertDBUserModel(dbUserModels[0]), nil +} + +// GetUserByGitlabUsername fetches the user record by gitlab username +func (repo repository) GetUserByGitlabUsername(gitlabUsername string) (*models.User, error) { + f := logrus.Fields{ + "functionName": "users.repository.GetUserByGitlabUsername", + "gitlabUsername": gitlabUsername, + } + // This is the key we want to match + condition := expression.Key("user_gitlab_username").Equal(expression.Value(gitlabUsername)) + + // These are the columns we want returned + projection := buildUserProjection() + + // Use the nice builder to create the expression + expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(projection).Build() + if err != nil { + log.WithFields(f).WithError(err).Warnf("error building expression for user_gitlab_username : %s, error: %v", gitlabUsername, err) + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + TableName: aws.String(repo.tableName), + IndexName: aws.String("gitlab-username-index"), + } + + // Make the DynamoDB Query API call + result, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving user by user_gitlab_username: %s, error: %+v", gitlabUsername, err) + return nil, err + } + + // The user model + var dbUserModels []DBUser + + err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &dbUserModels) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error unmarshalling user record from database for user_gitlab_username: %s, error: %+v", gitlabUsername, err) + return nil, err + } + + if len(dbUserModels) == 0 { + return nil, errors.NotFound("user not found when searching by user_gitlab_username: %s", gitlabUsername) + } else if len(dbUserModels) > 1 { + log.WithFields(f).WithError(err).Warnf("retrieved %d results for the user_gitlab_username query when we should return 0 or 1", len(dbUserModels)) + } + + return convertDBUserModel(dbUserModels[0]), nil +} + func (repo repository) SearchUsers(searchField string, searchTerm string, fullMatch bool) (*models.Users, error) { f := logrus.Fields{ "functionName": "users.repository.SearchUsers", @@ -818,7 +997,6 @@ func (repo repository) SearchUsers(searchField string, searchTerm string, fullMa users = append(users, userList...) if results.LastEvaluatedKey["user_id"] != nil { - //log.Debugf("LastEvaluatedKey: %+v", result.LastEvaluatedKey["signature_id"]) lastEvaluatedKey = *results.LastEvaluatedKey["user_id"].S scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ "user_id": { @@ -858,7 +1036,7 @@ func convertDBUserModel(user DBUser) *models.User { UserID: user.UserID, UserExternalID: user.UserExternalID, Admin: user.Admin, - LfEmail: user.LFEmail, + LfEmail: strfmt.Email(user.LFEmail), LfUsername: user.LFUsername, DateCreated: user.DateCreated, DateModified: user.DateModified, @@ -866,8 +1044,10 @@ func convertDBUserModel(user DBUser) *models.User { Version: user.Version, Emails: user.UserEmails, GithubID: user.UserGithubID, - CompanyID: user.UserCompanyID, GithubUsername: user.UserGithubUsername, + GitlabID: user.UserGitlabID, + GitlabUsername: user.UserGitlabUsername, + CompanyID: user.UserCompanyID, Note: user.Note, } } @@ -885,6 +1065,8 @@ func buildUserProjection() expression.ProjectionBuilder { expression.Name("user_emails"), expression.Name("user_github_username"), expression.Name("user_github_id"), + expression.Name("user_gitlab_username"), + expression.Name("user_gitlab_id"), expression.Name("date_created"), expression.Name("date_modified"), expression.Name("version"), diff --git a/cla-backend-go/users/service.go b/cla-backend-go/users/service.go index 715519965..f2c2c0a20 100644 --- a/cla-backend-go/users/service.go +++ b/cla-backend-go/users/service.go @@ -20,7 +20,10 @@ type Service interface { GetUserByLFUserName(lfUserName string) (*models.User, error) GetUserByUserName(userName string, fullMatch bool) (*models.User, error) GetUserByEmail(userEmail string) (*models.User, error) - GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) + GetUserByGitHubID(gitHubID string) (*models.User, error) + GetUserByGitHubUsername(gitlabUsername string) (*models.User, error) + GetUserByGitlabID(gitHubID string) (*models.User, error) + GetUserByGitlabUsername(gitlabUsername string) (*models.User, error) SearchUsers(field string, searchTerm string, fullMatch bool) (*models.Users, error) } @@ -109,7 +112,7 @@ func (s service) GetUser(userID string) (*models.User, error) { return s.repo.GetUser(userID) } -// GetuserByLFUserName returns the user record associated with the LF Username value +// GetUserByLFUserName returns the user record associated with the LF Username value func (s service) GetUserByLFUserName(lfUserName string) (*models.User, error) { if lfUserName == "" { return nil, errors.New("username is empty") @@ -133,6 +136,14 @@ func (s service) GetUserByEmail(userEmail string) (*models.User, error) { return s.repo.GetUserByEmail(userEmail) } +// GetUserByGitHubID fetches the user by GitHub ID +func (s service) GetUserByGitHubID(gitHubID string) (*models.User, error) { + if gitHubID == "" { + return nil, errors.New("gitHubID is empty") + } + return s.repo.GetUserByGitHubID(gitHubID) +} + // GetUserByGitHubUsername fetches the user by GitHub username func (s service) GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) { if gitHubUsername == "" { @@ -141,6 +152,22 @@ func (s service) GetUserByGitHubUsername(gitHubUsername string) (*models.User, e return s.repo.GetUserByGitHubUsername(gitHubUsername) } +// GetUserByGitlabID fetches the user by Gitlab ID +func (s service) GetUserByGitlabID(gitlabID string) (*models.User, error) { + if gitlabID == "" { + return nil, errors.New("gitlabID is empty") + } + return s.repo.GetUserByGitlabID(gitlabID) +} + +// GetUserByGitlabUsername fetches the user by Gitlab username +func (s service) GetUserByGitlabUsername(gitlabUsername string) (*models.User, error) { + if gitlabUsername == "" { + return nil, errors.New("gitlabUsername is empty") + } + return s.repo.GetUserByGitHubUsername(gitlabUsername) +} + // SearchUsers attempts to locate the user by the searchField and searchTerm fields func (s service) SearchUsers(searchField string, searchTerm string, fullMatch bool) (*models.Users, error) { return s.repo.SearchUsers(searchField, searchTerm, fullMatch) diff --git a/cla-backend-go/utils/cla_user.go b/cla-backend-go/utils/cla_user.go index 7b5a462e5..3f9fe4543 100644 --- a/cla-backend-go/utils/cla_user.go +++ b/cla-backend-go/utils/cla_user.go @@ -29,7 +29,7 @@ func GetBestUsername(user *models.User) string { // GetBestEmail is a helper function to return the best email address for the user model func GetBestEmail(userModel *models.User) string { if userModel.LfEmail != "" { - return userModel.LfEmail + return userModel.LfEmail.String() } for _, email := range userModel.Emails { diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 03c9b233f..a95b42785 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -156,18 +156,24 @@ const EmailDomainCriteria = "Email Domain Criteria" //EmailCriteria represents approvals based on email addresses const EmailCriteria = "Email Criteria" -//GitHubOrgCriteria represents approvals based on GH org membership -const GitHubOrgCriteria = "GitHub Org Criteria" - //AddApprovals is an action for adding approvals const AddApprovals = "AddApprovals" //RemoveApprovals is an action for removing approvals const RemoveApprovals = "RemoveApprovals" -//GitHubUsernameCriteria represents criteria based on GH username +//GitHubUsernameCriteria represents criteria based on GitHub username const GitHubUsernameCriteria = "GitHubUsername" +//GitHubOrgCriteria represents approvals based on GitHub org membership +const GitHubOrgCriteria = "GitHub Org Criteria" + +//GitlabUsernameCriteria represents criteria based on gitlab username +const GitlabUsernameCriteria = "GitHubUsername" + +//GitlabOrgCriteria represents approvals based on gitlab org group membership +const GitlabOrgCriteria = "Gitlab Org Criteria" + // SignatureQueryDefaultAll the signature query default active value - A flag to indicate how a default signature // query should return data - show only 'active' signatures or 'all' signatures when no other query signed/approved // params are provided diff --git a/cla-backend-go/utils/utils.go b/cla-backend-go/utils/utils.go index 22a3e8e0c..9d0b82993 100644 --- a/cla-backend-go/utils/utils.go +++ b/cla-backend-go/utils/utils.go @@ -6,17 +6,13 @@ package utils import ( "fmt" "math" - "regexp" "strconv" "strings" "time" - "unicode/utf8" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/sirupsen/logrus" - "github.com/gofrs/uuid" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/dynamodb" ) @@ -199,128 +195,3 @@ func SliceDifference(a, b []string) []string { } return diff } - -// ValidEmail tests the specified email string, returns true if email is valid, returns false otherwise -func ValidEmail(email string) bool { - emailRegexp := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") - return emailRegexp.MatchString(strings.TrimSpace(email)) -} - -// ValidDomain tests the specified domain string, returns true if domain is valid, returns false otherwise -func ValidDomain(domain string, allowWildcard bool) (string, bool) { // nolint - domain = strings.TrimSpace(domain) - - switch { - case len(domain) == 0: - return "domain is empty", false - case len(domain) > 255: - return fmt.Sprintf("domain name length is %d, can't exceed 255", len(domain)), false - } - var l int - for i := 0; i < len(domain); i++ { - b := domain[i] - if b == '.' { - // check domain labels validity - switch { - case i == l: - return fmt.Sprintf("invalid character '%c' at offset %d: label can't begin with a period", b, i), false - case i-l > 63: - return fmt.Sprintf("byte length of label '%s' is %d, can't exceed 63", domain[l:i], i-l), false - case domain[l] == '-': - return fmt.Sprintf("label '%s' at offset %d begins with a hyphen", domain[l:i], l), false - case domain[i-1] == '-': - return fmt.Sprintf("label '%s' at offset %d ends with a hyphen", domain[l:i], l), false - } - l = i + 1 - continue - } - - // If wildcard domains are allowed, e.g. *.linuxfoundation.org - if allowWildcard { - // test label character validity, note: tests are ordered by decreasing validity frequency - if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b == '*' || b >= 'A' && b <= 'Z') { - // show the printable unicode character starting at byte offset i - c, _ := utf8.DecodeRuneInString(domain[i:]) - if c == utf8.RuneError { - return fmt.Sprintf("invalid character at offset %d", i), false - } - return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false - } - } else { - // test label character validity, note: tests are ordered by decreasing validity frequency - if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') { - // show the printable unicode character starting at byte offset i - c, _ := utf8.DecodeRuneInString(domain[i:]) - if c == utf8.RuneError { - return fmt.Sprintf("invalid character at offset %d", i), false - } - return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false - } - } - } - - // check top level domain validity - switch { - case l == len(domain): - return "missing top level domain, domain can't end with a period", false - case len(domain)-l > 63: - return fmt.Sprintf("byte length of top level domain '%s' is %d, can't exceed 63", domain[l:], len(domain)-l), false - case domain[l] == '-': - return fmt.Sprintf("top level domain '%s' at offset %d begins with a hyphen", domain[l:], l), false - case domain[len(domain)-1] == '-': - return fmt.Sprintf("top level domain '%s' at offset %d ends with a hyphen", domain[l:], l), false - case domain[l] >= '0' && domain[l] <= '9': - return fmt.Sprintf("top level domain '%s' at offset %d begins with a digit", domain[l:], l), false - } - - return "", true -} - -// ValidGitHubUsername tests the specified GitHub username string, returns true if valid, returns false otherwise -func ValidGitHubUsername(githubUsername string) (string, bool) { - - if len(strings.TrimSpace(githubUsername)) <= 2 { - return "github username must be 3 or more characters", false - } - - // For now, we only allow alpha numeric values - re := regexp.MustCompile("^[a-zA-Z0-9_-]*$") - valid := re.MatchString(strings.TrimSpace(githubUsername)) - if !valid { - return fmt.Sprintf("invalid GitHub username: %s", githubUsername), false - } - - return "", true -} - -// ValidGitHubOrg tests the specified GitHub Organization string, returns true if valid, returns false otherwise -func ValidGitHubOrg(githubOrg string) (string, bool) { - - if len(strings.TrimSpace(githubOrg)) <= 2 { - return "github organization must be 3 or more characters", false - } - - re := regexp.MustCompile("^[a-zA-Z0-9._-]*$") - valid := re.MatchString(strings.TrimSpace(githubOrg)) - if !valid { - return fmt.Sprintf("invalid GitHub organization: %s", githubOrg), false - } - - return "", true -} - -// IsUUIDv4 returns true if the specified ID is in the UUIDv4 format, otherwise returns false -func IsUUIDv4(id string) bool { - value, err := uuid.FromString(id) - if err != nil { - return false - } - - return value.Version() == uuid.V4 -} - -// IsSalesForceID returns true if the specified ID is a SalesForce formatted ID, otherwise returns false -func IsSalesForceID(id string) bool { - regExp := regexp.MustCompile("^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$") - return regExp.MatchString(strings.TrimSpace(id)) -} diff --git a/cla-backend-go/utils/validators.go b/cla-backend-go/utils/validators.go new file mode 100644 index 000000000..a8a42312c --- /dev/null +++ b/cla-backend-go/utils/validators.go @@ -0,0 +1,171 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "fmt" + "regexp" + "strings" + "unicode/utf8" + + "github.com/gofrs/uuid" +) + +// ValidEmail tests the specified email string, returns true if email is valid, returns false otherwise +func ValidEmail(email string) bool { + emailRegexp := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + return emailRegexp.MatchString(strings.TrimSpace(email)) +} + +// ValidDomain tests the specified domain string, returns true if domain is valid, returns false otherwise +func ValidDomain(domain string, allowWildcard bool) (string, bool) { // nolint + domain = strings.TrimSpace(domain) + + switch { + case len(domain) == 0: + return "domain is empty", false + case len(domain) > 255: + return fmt.Sprintf("domain name length is %d, can't exceed 255", len(domain)), false + } + var l int + for i := 0; i < len(domain); i++ { + b := domain[i] + if b == '.' { + // check domain labels validity + switch { + case i == l: + return fmt.Sprintf("invalid character '%c' at offset %d: label can't begin with a period", b, i), false + case i-l > 63: + return fmt.Sprintf("byte length of label '%s' is %d, can't exceed 63", domain[l:i], i-l), false + case domain[l] == '-': + return fmt.Sprintf("label '%s' at offset %d begins with a hyphen", domain[l:i], l), false + case domain[i-1] == '-': + return fmt.Sprintf("label '%s' at offset %d ends with a hyphen", domain[l:i], l), false + } + l = i + 1 + continue + } + + // If wildcard domains are allowed, e.g. *.linuxfoundation.org + if allowWildcard { + // test label character validity, note: tests are ordered by decreasing validity frequency + if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b == '*' || b >= 'A' && b <= 'Z') { + // show the printable unicode character starting at byte offset i + c, _ := utf8.DecodeRuneInString(domain[i:]) + if c == utf8.RuneError { + return fmt.Sprintf("invalid character at offset %d", i), false + } + return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false + } + } else { + // test label character validity, note: tests are ordered by decreasing validity frequency + if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') { + // show the printable unicode character starting at byte offset i + c, _ := utf8.DecodeRuneInString(domain[i:]) + if c == utf8.RuneError { + return fmt.Sprintf("invalid character at offset %d", i), false + } + return fmt.Sprintf("invalid character '%c' at offset %d", c, i), false + } + } + } + + // check top level domain validity + switch { + case l == len(domain): + return "missing top level domain, domain can't end with a period", false + case len(domain)-l > 63: + return fmt.Sprintf("byte length of top level domain '%s' is %d, can't exceed 63", domain[l:], len(domain)-l), false + case domain[l] == '-': + return fmt.Sprintf("top level domain '%s' at offset %d begins with a hyphen", domain[l:], l), false + case domain[len(domain)-1] == '-': + return fmt.Sprintf("top level domain '%s' at offset %d ends with a hyphen", domain[l:], l), false + case domain[l] >= '0' && domain[l] <= '9': + return fmt.Sprintf("top level domain '%s' at offset %d begins with a digit", domain[l:], l), false + } + + return "", true +} + +// ValidGitHubUsername tests the specified GitHub username string, returns true if valid, returns false otherwise +func ValidGitHubUsername(githubUsername string) (string, bool) { + + if len(strings.TrimSpace(githubUsername)) <= 2 { + return "github username must be 3 or more characters", false + } + + // For now, we only allow alphanumeric values + re := regexp.MustCompile("^[a-zA-Z0-9_-]*$") + valid := re.MatchString(strings.TrimSpace(githubUsername)) + if !valid { + return fmt.Sprintf("invalid GitHub username: %s", githubUsername), false + } + + return "", true +} + +// ValidGitlabUsername tests the specified Gitlab username string, returns true if valid, returns false otherwise +func ValidGitlabUsername(gitlabUsername string) (string, bool) { + + if len(strings.TrimSpace(gitlabUsername)) <= 2 { + return "gitlab username must be 3 or more characters", false + } + + // For now, we only allow alphanumeric values + re := regexp.MustCompile("^[a-zA-Z0-9_-]*$") + valid := re.MatchString(strings.TrimSpace(gitlabUsername)) + if !valid { + return fmt.Sprintf("invalid Gitlab username: %s", gitlabUsername), false + } + + return "", true +} + +// ValidGitHubOrg tests the specified GitHub Organization string, returns true if valid, returns false otherwise +func ValidGitHubOrg(githubOrg string) (string, bool) { + + if len(strings.TrimSpace(githubOrg)) <= 2 { + return "github organization must be 3 or more characters", false + } + + re := regexp.MustCompile("^[a-zA-Z0-9._-]*$") + valid := re.MatchString(strings.TrimSpace(githubOrg)) + if !valid { + return fmt.Sprintf("invalid GitHub organization: %s", githubOrg), false + } + + return "", true +} + +// ValidGitlabOrg tests the specified Gitlab Organization string, returns true if valid, returns false otherwise +func ValidGitlabOrg(gitlabOrg string) (string, bool) { + + if len(strings.TrimSpace(gitlabOrg)) <= 2 { + return "gitlab organization must be 3 or more characters", false + } + + re := regexp.MustCompile("^[a-zA-Z0-9._-]*$") + valid := re.MatchString(strings.TrimSpace(gitlabOrg)) + if !valid { + return fmt.Sprintf("invalid Gitlab organization: %s", gitlabOrg), false + } + + return "", true +} + +// IsUUIDv4 returns true if the specified ID is in the UUIDv4 format, otherwise returns false +func IsUUIDv4(id string) bool { + value, err := uuid.FromString(id) + if err != nil { + return false + } + + return value.Version() == uuid.V4 +} + +// IsSalesForceID returns true if the specified ID is a SalesForce formatted ID, otherwise returns false +func IsSalesForceID(id string) bool { + regExp := regexp.MustCompile("^[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}$") + return regExp.MatchString(strings.TrimSpace(id)) +} diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 1cd59dbf1..505056e73 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -204,7 +204,7 @@ func (s *service) CreateCLAManager(ctx context.Context, authUser *auth.User, cla claUserModel := &v1Models.User{ CompanyID: v1CompanyModel.CompanyID, UserExternalID: v1CompanyModel.CompanyExternalID, - LfEmail: *user.Emails[0].EmailAddress, + LfEmail: strfmt.Email(*user.Emails[0].EmailAddress), Admin: true, LfUsername: user.Username, DateCreated: currentTimeString, diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 127403fba..7d958b663 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -1128,7 +1128,7 @@ func (s *service) GetCompanyCLAGroupManagers(ctx context.Context, companyID, cla // DB doesn't have approved_on value - just use sig created date/time ApprovedOn: sigModel.SignatureCreated, LfUsername: user.LfUsername, - Email: strfmt.Email(user.LfEmail), + Email: user.LfEmail, Name: user.Username, UserSfid: user.UserExternalID, ProjectID: sigModel.ProjectID, diff --git a/cla-backend-go/v2/dynamo_events/cla_manager.go b/cla-backend-go/v2/dynamo_events/cla_manager.go index bc17275c9..519e9d849 100644 --- a/cla-backend-go/v2/dynamo_events/cla_manager.go +++ b/cla-backend-go/v2/dynamo_events/cla_manager.go @@ -67,7 +67,7 @@ func (s *service) SetInitialCLAManagerACSPermissions(ctx context.Context, signat log.WithFields(f).Debugf("searching user by email: %s", sig.SignatureACL[0].LfEmail) if sig.SignatureACL[0].LfEmail != "" { - claManager, err = userServiceClient.SearchUserByEmail(sig.SignatureACL[0].LfEmail) + claManager, err = userServiceClient.SearchUserByEmail(sig.SignatureACL[0].LfEmail.String()) if err != nil || claManager == nil { log.WithFields(f).Warnf("unable to lookup user by email: %s, error: %+v", sig.SignatureACL[0].LfEmail, err) diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index fc9791eff..819bd85ea 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -108,15 +108,11 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", params.CompanyID, err) log.Warn(msg) if _, ok := err.(*utils.CompanyNotFound); ok { - return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 404 Not Found - error getting company - " + msg, - Code: "404", - }) + return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("company not found - unable to locate company by ID: %s", params.CompanyID), err)) } - return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, - Code: "400", - }) + return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, fmt.Sprintf("unable to locate company by ID: %s", params.CompanyID), err)) } // Must be in the Project|Organization Scope to see this - signature ACL is double-checked in the service level when the signature is loaded diff --git a/cla-backend-go/v2/signatures/validators.go b/cla-backend-go/v2/signatures/validators.go index 47ba14750..862abcf66 100644 --- a/cla-backend-go/v2/signatures/validators.go +++ b/cla-backend-go/v2/signatures/validators.go @@ -31,7 +31,9 @@ func hasApprovalListUpdates(params signatures.UpdateApprovalListParams) bool { if len(params.Body.AddEmailApprovalList) > 0 || len(params.Body.RemoveEmailApprovalList) > 0 || len(params.Body.AddDomainApprovalList) > 0 || len(params.Body.RemoveDomainApprovalList) > 0 || len(params.Body.AddGithubUsernameApprovalList) > 0 || len(params.Body.RemoveGithubUsernameApprovalList) > 0 || - len(params.Body.AddGithubOrgApprovalList) > 0 || len(params.Body.RemoveGithubOrgApprovalList) > 0 { + len(params.Body.AddGithubOrgApprovalList) > 0 || len(params.Body.RemoveGithubOrgApprovalList) > 0 || + len(params.Body.AddGitlabUsernameApprovalList) > 0 || len(params.Body.RemoveGitlabUsernameApprovalList) > 0 || + len(params.Body.AddGitlabOrgApprovalList) > 0 || len(params.Body.RemoveGitlabOrgApprovalList) > 0 { return true } @@ -72,7 +74,7 @@ func entriesAreValid(params signatures.UpdateApprovalListParams) (string, bool) } } - // Ensure the github usernames are valid + // Ensure the GitHub usernames are valid for _, githubUsername := range params.Body.AddGithubUsernameApprovalList { msg, valid := utils.ValidGitHubUsername(githubUsername) if !valid { @@ -88,7 +90,7 @@ func entriesAreValid(params signatures.UpdateApprovalListParams) (string, bool) } } - // Ensure the github Organization values are valid + // Ensure the GitHub Organization values are valid for _, githubOrg := range params.Body.AddGithubOrgApprovalList { msg, valid := utils.ValidGitHubOrg(githubOrg) if !valid { @@ -104,5 +106,37 @@ func entriesAreValid(params signatures.UpdateApprovalListParams) (string, bool) } } + // Ensure the Gitlab usernames are valid + for _, githubUsername := range params.Body.AddGitlabUsernameApprovalList { + msg, valid := utils.ValidGitlabUsername(githubUsername) + if !valid { + isValid = false + listOfErrors = append(listOfErrors, fmt.Sprintf("invalid add approval list Gitlab Username %s - %s", githubUsername, msg)) + } + } + for _, githubUsername := range params.Body.RemoveGitlabUsernameApprovalList { + msg, valid := utils.ValidGitlabUsername(githubUsername) + if !valid { + isValid = false + listOfErrors = append(listOfErrors, fmt.Sprintf("invalid remove approval list Gitlab Username %s - %s", githubUsername, msg)) + } + } + + // Ensure the Gitlab Organization values are valid + for _, githubOrg := range params.Body.AddGitlabOrgApprovalList { + msg, valid := utils.ValidGitlabOrg(githubOrg) + if !valid { + isValid = false + listOfErrors = append(listOfErrors, fmt.Sprintf("invalid add approval list Gitlab Org %s - %s", githubOrg, msg)) + } + } + for _, githubOrg := range params.Body.RemoveGitlabOrgApprovalList { + msg, valid := utils.ValidGitlabOrg(githubOrg) + if !valid { + isValid = false + listOfErrors = append(listOfErrors, fmt.Sprintf("invalid remove approval list Gitlab Org %s - %s", githubOrg, msg)) + } + } + return strings.Join(listOfErrors, ", "), isValid } diff --git a/cla-backend-go/v2/user-service/client.go b/cla-backend-go/v2/user-service/client.go index 45b95930f..d6b494632 100644 --- a/cla-backend-go/v2/user-service/client.go +++ b/cla-backend-go/v2/user-service/client.go @@ -28,8 +28,8 @@ import ( "github.com/go-openapi/strfmt" ) -// errors var ( + // ErrUserNotFound is an error for users not found ErrUserNotFound = errors.New("user not found") ) diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index b216dcdc0..d35d1d310 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -230,8 +230,10 @@ provider: Resource: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" From 2f1f45c9d5f155959039e4a2af5682eb84f906e7 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 29 Jul 2021 04:36:31 +0300 Subject: [PATCH 0374/1276] [Snyk] Security upgrade url-parse from 1.5.0 to 1.5.2 (#3072) --- cla-frontend-project-console/src/package.json | 2 +- cla-frontend-project-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-project-console/src/package.json b/cla-frontend-project-console/src/package.json index 5fecf5de1..5e690d08e 100644 --- a/cla-frontend-project-console/src/package.json +++ b/cla-frontend-project-console/src/package.json @@ -54,7 +54,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.5.0", + "url-parse": "^1.5.2", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index ad1b1bdba..6fe68d0dd 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -4197,10 +4197,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" - integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== +url-parse@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From 01c2a3406cad0778ee616a23d780151cd5535430 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 29 Jul 2021 05:08:24 +0300 Subject: [PATCH 0375/1276] [Snyk] Security upgrade url-parse from 1.4.7 to 1.5.2 (#3067) --- cla-landing-page/package.json | 2 +- cla-landing-page/yarn.lock | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 6966dabe5..e19fa14e6 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -64,7 +64,7 @@ "serverless-plugin-tracing": "^2.0.0", "serverless-pseudo-parameters": "^2.5.0", "tslib": "^1.10.0", - "url-parse": "^1.4.7", + "url-parse": "^1.5.2", "zone.js": "~0.10.2" }, "devDependencies": { diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 06b3c8a96..59f47ffde 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -11909,7 +11909,7 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3, url-parse@^1.4.7: +url-parse@^1.4.3: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== @@ -11917,6 +11917,14 @@ url-parse@^1.4.3, url-parse@^1.4.7: querystringify "^2.1.1" requires-port "^1.0.0" +url-parse@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" From 0b82ed7f49c53b8d052299113162fa3a47c74d34 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 29 Jul 2021 05:21:27 +0300 Subject: [PATCH 0376/1276] [Snyk] Security upgrade url-parse from 1.5.0 to 1.5.2 (#3057) --- cla-frontend-contributor-console/src/package.json | 2 +- cla-frontend-contributor-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-contributor-console/src/package.json b/cla-frontend-contributor-console/src/package.json index b8d825f9f..db7f27eab 100644 --- a/cla-frontend-contributor-console/src/package.json +++ b/cla-frontend-contributor-console/src/package.json @@ -52,7 +52,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.5.0", + "url-parse": "^1.5.2", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index f40734a66..e1fc3cdc2 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -4043,10 +4043,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" - integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== +url-parse@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From c5ef980ec53e5626b70b225220180d3b460e08be Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 29 Jul 2021 06:01:40 +0300 Subject: [PATCH 0377/1276] fix: cla-frontend-corporate-console/src/package.json & cla-frontend-corporate-console/src/yarn.lock to reduce vulnerabilities (#3058) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-URLPARSE-1533425 --- cla-frontend-corporate-console/src/package.json | 2 +- cla-frontend-corporate-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-corporate-console/src/package.json b/cla-frontend-corporate-console/src/package.json index d6c33e8a6..407ed94e9 100644 --- a/cla-frontend-corporate-console/src/package.json +++ b/cla-frontend-corporate-console/src/package.json @@ -50,7 +50,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.5.0", + "url-parse": "^1.5.2", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index 9e2359380..111cea165 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -4053,10 +4053,10 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url-parse@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.0.tgz#90aba6c902aeb2d80eac17b91131c27665d5d828" - integrity sha512-9iT6N4s93SMfzunOyDPe4vo4nLcSu1yq0IQK1gURmjm8tQNlM6loiuCRrKG1hHGXfB2EWd6H4cGi7tGdaygMFw== +url-parse@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From 14085e83a43a05194aa6e10ea0821933408ff18d Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 30 Jul 2021 09:13:57 -0700 Subject: [PATCH 0378/1276] Added Additional Debug for GitHub comments (#3084) Signed-off-by: David Deal --- cla-backend/cla/models/github_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 6101367b1..1165b56eb 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -322,7 +322,8 @@ def process_easycla_command_comment(self, data): raise ValueError("missing comment body, ignoring the message") if "/easycla" not in comment_str.split(): - raise ValueError("unsupported comment supplied, currently only /easycla command is supported") + raise ValueError(f'unsupported comment supplied: {comment_str.split()}, ' + 'currently only the \'/easycla\' command is supported') github_repository_id = data.get('repository', {}).get('id', None) if not github_repository_id: From e59b808480c1d53021834216269bdcce4fde824f Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 30 Jul 2021 11:59:39 -0700 Subject: [PATCH 0379/1276] Updated Project Parent Logic in Python (#3085) - Updated is stanalone and parent checks for SF projects Signed-off-by: David Deal --- cla-backend/cla/config.py | 3 +++ cla-backend/cla/project_service.py | 30 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cla-backend/cla/config.py b/cla-backend/cla/config.py index 8208f019d..0a58daeba 100644 --- a/cla-backend/cla/config.py +++ b/cla-backend/cla/config.py @@ -52,6 +52,9 @@ def get_ssm_key(region, key): # The linux foundation is the parent for many SF projects THE_LINUX_FOUNDATION = 'The Linux Foundation' +# LF Projects LLC is the parent for many SF projects +LF_PROJECTS_LLC = 'LF Projects, LLC' + # Base URL used for callbacks and OAuth2 redirects. API_BASE_URL = os.environ.get('CLA_API_BASE', '') diff --git a/cla-backend/cla/project_service.py b/cla-backend/cla/project_service.py index dd9fa3895..9a012cb74 100644 --- a/cla-backend/cla/project_service.py +++ b/cla-backend/cla/project_service.py @@ -3,12 +3,13 @@ import datetime import json import os +from typing import Optional import requests import cla from cla import log -from cla.config import THE_LINUX_FOUNDATION +from cla.config import THE_LINUX_FOUNDATION, LF_PROJECTS_LLC STAGE = os.environ.get('STAGE', '') REGION = 'us-east-1' @@ -35,9 +36,12 @@ def is_standalone(self, project_sfid) -> bool: """ project = self.get_project_by_id(project_sfid) if project: - parent_sf_id = project.get('Parent', None) - if not self.has_parent(project) and (parent_sf_id is None or parent_sf_id == THE_LINUX_FOUNDATION): + parent_name = self.get_parent_name(project) + if parent_name is None or (parent_name == THE_LINUX_FOUNDATION or parent_name == LF_PROJECTS_LLC) \ + and not project.get('Projects'): return True + else: + return False return False def is_lf_supported(self, project_sfid) -> bool: @@ -50,9 +54,10 @@ def is_lf_supported(self, project_sfid) -> bool: """ project = self.get_project_by_id(project_sfid) if project: + parent_name = self.get_parent_name(project) return (project.get('Funding', None) == 'Unfunded' or project.get('Funding', None) == 'Supported By Parent') and \ - project.get('Parent', None) == THE_LINUX_FOUNDATION + (parent_name == THE_LINUX_FOUNDATION or parent_name == LF_PROJECTS_LLC) return False def has_parent(self, project) -> bool: @@ -60,14 +65,25 @@ def has_parent(self, project) -> bool: fn = 'project_service.has_parent' try: log.info(f"{fn} - Checking if {project['Name']} has parent project") - parent = project['Parent'] - if parent: + if project and project['Foundation']['ID'] != '' and project['Foundation']['Name'] != '': return True except KeyError as err: - log.debug(f"{fn} - Failed to find parent for {project['Name']} , error: {err}") + log.debug(f"{fn} - Failed to find parent for {project['Name']}, error: {err}") return False return False + def get_parent_name(self, project) -> Optional[str]: + """ returns the project parent name if exists, otherwise returns None """ + fn = 'project_service.get_parent_name' + try: + log.info(f"{fn} - Checking if {project['Name']} has parent project") + if project and project['Foundation']['ID'] != '' and project['Foundation']['Name'] != '': + return project['Foundation']['Name'] + except KeyError as err: + log.debug(f"{fn} - Failed to find parent for {project['Name']}, error: {err}") + return None + return None + def is_parent(self, project) -> bool: """ checks whether salesforce project is a parent From 74863a69c25b66fea710dcb0fc8bff69f0dea726 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 30 Jul 2021 14:11:01 -0700 Subject: [PATCH 0380/1276] Resolved GitHub User Index Name Issue (#3086) Signed-off-by: David Deal --- cla-backend/cla/models/dynamo_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index f50f85ff6..9c34d1897 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -95,7 +95,7 @@ class GitHubUserIndex(GlobalSecondaryIndex): class Meta: """Meta class for GitHub User index.""" - index_name = "github-user-index" + index_name = "github-username-index" write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) # All attributes are projected - not sure if this is necessary. From 8990757900cf7e7241ca6f5314934be7467400dd Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 30 Jul 2021 14:59:20 -0700 Subject: [PATCH 0381/1276] Resolved User Index Issue (#3087) Signed-off-by: David Deal --- cla-backend/cla/models/dynamo_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 9c34d1897..3afc741b1 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -95,7 +95,7 @@ class GitHubUserIndex(GlobalSecondaryIndex): class Meta: """Meta class for GitHub User index.""" - index_name = "github-username-index" + index_name = "github-id-index" write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) # All attributes are projected - not sure if this is necessary. From 73ae04b30e3e55d257edc1ddf06b82f4a83483da Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 2 Aug 2021 18:24:16 +0300 Subject: [PATCH 0382/1276] Feature/Ecla username update (#3089) --- cla-backend-go/docraptor/client.go | 1 + cla-backend-go/signatures/repository.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/docraptor/client.go b/cla-backend-go/docraptor/client.go index 16a0ed202..7b8503a54 100644 --- a/cla-backend-go/docraptor/client.go +++ b/cla-backend-go/docraptor/client.go @@ -72,5 +72,6 @@ func (dc Client) CreatePDF(html string, claType string) (io.ReadCloser, error) { log.WithFields(f).WithError(err).Warn("problem with API call to docraptor") return nil, err } + defer resp.Body.Close() return resp.Body, nil } diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 777a195e2..fc0e05047 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -3462,11 +3462,21 @@ func (repo repository) GetClaGroupCorporateContributors(ctx context.Context, cla } signatureVersion := fmt.Sprintf("v%s.%s", sig.SignatureDocumentMajorVersion, sig.SignatureDocumentMinorVersion) + + sigName := sig.UserName + user, userErr := repo.usersRepo.GetUser(sig.SignatureReferenceID) + if userErr != nil { + log.WithFields(f).Warnf("unable to get user for id: %s, error: %v ", sig.SignatureReferenceID, userErr) + } + if user != nil && sigName == "" { + sigName = user.Username + } + out.List = append(out.List, &models.CorporateContributor{ SignatureID: sig.SignatureID, GithubID: sig.UserGithubUsername, LinuxFoundationID: sig.UserLFUsername, - Name: sig.UserName, + Name: sigName, SignatureVersion: signatureVersion, Email: sig.UserEmail, Timestamp: sigCreatedTime, From e33f2d3a78280f499791d43db935d480a4f0f5a1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 2 Aug 2021 08:44:26 -0700 Subject: [PATCH 0383/1276] Forced GitHub ID to be Int (#3091) - cast github ID to an integer value for the index query call - previously, string was accepted, but after the index update it now only accepts an integer value Signed-off-by: David Deal --- cla-backend-go/go.mod | 2 ++ cla-backend-go/go.sum | 6 ++++++ cla-backend-go/users/repository.go | 16 ++++++++-------- cla-backend-go/users/service.go | 7 ++----- cla-backend/cla/models/dynamo_models.py | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index d584c184d..c93e34994 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -45,7 +45,9 @@ require ( github.com/mattn/go-isatty v0.0.13 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/mozillazg/request v0.8.0 // indirect + github.com/myitcv/gobin v0.0.14 // indirect github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc + github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/rs/cors v1.7.0 github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 23f30f8c3..bf9282154 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -607,6 +607,8 @@ github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9r github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/myitcv/gobin v0.0.14 h1:YkTUz0IeRspEJlly/+AXRBMA3GN7ArRVbsLJ1uYFwRk= +github.com/myitcv/gobin v0.0.14/go.mod h1:GvHEiYCWroKI2KrMT+xQkHC3FC551wigVWeR4Sgg5P4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= @@ -628,6 +630,7 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -660,6 +663,9 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= diff --git a/cla-backend-go/users/repository.go b/cla-backend-go/users/repository.go index 24123b118..350e98781 100644 --- a/cla-backend-go/users/repository.go +++ b/cla-backend-go/users/repository.go @@ -43,7 +43,7 @@ type UserRepository interface { GetUserByEmail(userEmail string) (*models.User, error) GetUserByGitHubID(gitHubID string) (*models.User, error) GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) - GetUserByGitlabID(gitlabID string) (*models.User, error) + GetUserByGitlabID(gitlabID int) (*models.User, error) GetUserByGitlabUsername(gitlabUsername string) (*models.User, error) SearchUsers(searchField string, searchTerm string, fullMatch bool) (*models.Users, error) } @@ -444,7 +444,7 @@ func (repo repository) GetUser(userID string) (*models.User, error) { return convertDBUserModel(dbUserModels[0]), nil } -// GetuserByLFUserName returns the user record associated with the LF Username value +// GetUserByLFUserName returns the user record associated with the LF Username value func (repo repository) GetUserByLFUserName(lfUserName string) (*models.User, error) { f := logrus.Fields{ "functionName": "users.repository.GetUserByLFUserName", @@ -564,7 +564,7 @@ func (repo repository) GetUserByUserName(userName string, fullMatch bool) (*mode var condition expression.KeyConditionBuilder if strings.Contains(userName, "github:") { - indexName = "github-user-index" + indexName = "github-id-index" // Username for GitHub comes in as github:123456, so we want to remove the initial string githubID, err := strconv.Atoi(strings.Replace(userName, "github:", "", 1)) if err != nil { @@ -768,7 +768,7 @@ func (repo repository) GetUserByGitHubID(gitHubID string) (*models.User, error) // GetUserByGitHubUsername fetches the user record by github username func (repo repository) GetUserByGitHubUsername(gitHubUsername string) (*models.User, error) { f := logrus.Fields{ - "functionName": "users.repository.GetUserByGitlabUsername", + "functionName": "users.repository.GetUserByGitHubUsername", "gitHubUsername": gitHubUsername, } // This is the key we want to match @@ -820,7 +820,7 @@ func (repo repository) GetUserByGitHubUsername(gitHubUsername string) (*models.U } // GetUserByGitlabID fetches the user record by gitlab ID -func (repo repository) GetUserByGitlabID(gitlabID string) (*models.User, error) { +func (repo repository) GetUserByGitlabID(gitlabID int) (*models.User, error) { f := logrus.Fields{ "functionName": "users.repository.GetUserByGitlabID", "gitlabID": gitlabID, @@ -834,7 +834,7 @@ func (repo repository) GetUserByGitlabID(gitlabID string) (*models.User, error) // Use the nice builder to create the expression expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(projection).Build() if err != nil { - log.WithFields(f).WithError(err).Warnf("error building expression for user_gitlab_id : %s, error: %v", gitlabID, err) + log.WithFields(f).WithError(err).Warnf("error building expression for user_gitlab_id : %d, error: %v", gitlabID, err) return nil, err } @@ -851,7 +851,7 @@ func (repo repository) GetUserByGitlabID(gitlabID string) (*models.User, error) // Make the DynamoDB Query API call result, err := repo.dynamoDBClient.Query(queryInput) if err != nil { - log.WithFields(f).WithError(err).Warnf("error retrieving user by user_gitlab_id: %s, error: %+v", gitlabID, err) + log.WithFields(f).WithError(err).Warnf("error retrieving user by user_gitlab_id: %d, error: %+v", gitlabID, err) return nil, err } @@ -860,7 +860,7 @@ func (repo repository) GetUserByGitlabID(gitlabID string) (*models.User, error) err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &dbUserModels) if err != nil { - log.WithFields(f).WithError(err).Warnf("error unmarshalling user record from database for user_gitlab_id: %s, error: %+v", gitlabID, err) + log.WithFields(f).WithError(err).Warnf("error unmarshalling user record from database for user_gitlab_id: %d, error: %+v", gitlabID, err) return nil, err } diff --git a/cla-backend-go/users/service.go b/cla-backend-go/users/service.go index f2c2c0a20..b245955c1 100644 --- a/cla-backend-go/users/service.go +++ b/cla-backend-go/users/service.go @@ -22,7 +22,7 @@ type Service interface { GetUserByEmail(userEmail string) (*models.User, error) GetUserByGitHubID(gitHubID string) (*models.User, error) GetUserByGitHubUsername(gitlabUsername string) (*models.User, error) - GetUserByGitlabID(gitHubID string) (*models.User, error) + GetUserByGitlabID(gitHubID int) (*models.User, error) GetUserByGitlabUsername(gitlabUsername string) (*models.User, error) SearchUsers(field string, searchTerm string, fullMatch bool) (*models.Users, error) } @@ -153,10 +153,7 @@ func (s service) GetUserByGitHubUsername(gitHubUsername string) (*models.User, e } // GetUserByGitlabID fetches the user by Gitlab ID -func (s service) GetUserByGitlabID(gitlabID string) (*models.User, error) { - if gitlabID == "" { - return nil, errors.New("gitlabID is empty") - } +func (s service) GetUserByGitlabID(gitlabID int) (*models.User, error) { return s.repo.GetUserByGitlabID(gitlabID) } diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 3afc741b1..2b83cdaad 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -1735,13 +1735,13 @@ def get_user_by_email(self, user_email) -> Optional[List[User]]: else: return None - def get_user_by_github_id(self, user_github_id) -> Optional[List[User]]: + def get_user_by_github_id(self, user_github_id: int) -> Optional[List[User]]: if user_github_id is None: cla.log.warning("Unable to lookup user by github id - id is empty") return None users = [] - for user_model in self.model.user_github_id_index.query(user_github_id): + for user_model in self.model.user_github_id_index.query(int(user_github_id)): user = User() user.model = user_model users.append(user) From 00eb1f5bda71c9919ad37a748a77e160e78c3329 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 2 Aug 2021 19:39:47 -0700 Subject: [PATCH 0384/1276] Added Additional Error Output for Approval List Errors (#3098) - Added error details to approval list errors Signed-off-by: David Deal --- cla-backend-go/v2/signatures/handlers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 819bd85ea..0abcc1592 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -162,10 +162,10 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj if updateErr != nil || updatedSig == nil { msg := fmt.Sprintf("unable to update signature approval list using CLA Group ID: %s", params.ClaGroupID) log.WithFields(f).Warn(msg) - if err, ok := err.(*signatureService.ForbiddenError); ok { - return signatures.NewUpdateApprovalListForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbiddenWithError(reqID, msg, err)) + if _, ok := err.(*signatureService.ForbiddenError); ok { + return signatures.NewUpdateApprovalListForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbiddenWithError(reqID, msg, updateErr)) } - return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + return signatures.NewUpdateApprovalListBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, updateErr)) } // Convert the v1 output model to a v2 response model From 3d734e7d6560c0fbf34ad352cfe54c9fd55018ed Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 2 Aug 2021 19:55:03 -0700 Subject: [PATCH 0385/1276] Optimize Go Build (#3099) - updated circleci configuration to build go backend once Signed-off-by: David Deal --- .circleci/config.yml | 80 ++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ffb4c24c..b325f41a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -280,7 +280,7 @@ jobs: - cla-backend-go/zipbuilder-lambda - cla-backend-go/functional-tests - buildGoBackendDev: + buildGoBackendCommon: <<: *buildGoBackendAnchor environment: STAGE: dev @@ -290,25 +290,25 @@ jobs: AWS_REGION: us-east-1 DYNAMODB_AWS_REGION: us-east-1 - buildGoBackendStaging: - <<: *buildGoBackendAnchor - environment: - STAGE: staging - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - DYNAMODB_AWS_REGION: us-east-1 - - buildGoBackendProd: - <<: *buildGoBackendAnchor - environment: - STAGE: prod - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - DYNAMODB_AWS_REGION: us-east-1 +# buildGoBackendStaging: +# <<: *buildGoBackendAnchor +# environment: +# STAGE: staging +# AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING +# AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING +# AWS_PROFILE: lf-cla +# AWS_REGION: us-east-1 +# DYNAMODB_AWS_REGION: us-east-1 +# +# buildGoBackendProd: +# <<: *buildGoBackendAnchor +# environment: +# STAGE: prod +# AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD +# AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD +# AWS_PROFILE: lf-cla +# AWS_REGION: us-east-1 +# DYNAMODB_AWS_REGION: us-east-1 # Deploys deployBackend: &deployBackendAnchor @@ -971,7 +971,7 @@ workflows: filters: tags: only: /.*/ - - buildGoBackendDev: + - buildGoBackendCommon: filters: tags: only: /.*/ @@ -992,7 +992,7 @@ workflows: - deployBackendDev: requires: - buildBackendDev - - buildGoBackendDev + - buildGoBackendCommon filters: tags: ignore: /.*/ @@ -1054,18 +1054,18 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - buildGoBackendStaging: - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ +# - buildGoBackendStaging: +# filters: +# branches: +# ignore: /.*/ +# tags: +# # see semver examples https://regex101.com/r/Ly7O1x/201/ +# only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - approve_staging: type: approval requires: - buildBackendStaging - - buildGoBackendStaging + - buildGoBackendCommon filters: branches: ignore: /.*/ @@ -1076,7 +1076,7 @@ workflows: requires: - approve_staging - buildBackendStaging - - buildGoBackendStaging + - buildGoBackendCommon filters: branches: ignore: /.*/ @@ -1128,18 +1128,18 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - buildGoBackendProd: - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ +# - buildGoBackendProd: +# filters: +# branches: +# ignore: /.*/ +# tags: +# # see semver examples https://regex101.com/r/Ly7O1x/201/ +# only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - approve_prod: type: approval requires: - buildBackendProd - - buildGoBackendProd + - buildGoBackendCommon filters: branches: ignore: /.*/ @@ -1150,7 +1150,7 @@ workflows: requires: - approve_prod - buildBackendProd - - buildGoBackendProd + - buildGoBackendCommon filters: branches: ignore: /.*/ From b8b43e25b1b66f1021e47ca40936eef00bf68a9c Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 3 Aug 2021 14:16:49 -0700 Subject: [PATCH 0386/1276] Resolved Concurrency Issue (#3102) - Added a locking mutex around access to a shared cache/map used by multiple go routines Signed-off-by: David Deal --- cla-backend-go/v2/project-service/client.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index ee3c5d353..87c7a9e9c 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "strings" + "sync" "github.com/sirupsen/logrus" @@ -37,6 +38,8 @@ type Client struct { var ( projectServiceClient *Client + // mutex is an object to allow us to lock access to the shared project service map while used by multiple go routines + mutex = &sync.Mutex{} // Short term cache - only for the lifetime of this lambda projectServiceModels = make(map[string]*models.ProjectOutputDetailed) apiGWHost string @@ -78,7 +81,10 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed } // Lookup in cache first + mutex.Lock() // exclusive lock to the shared project service model map existingModel, exists := projectServiceModels[projectSFID] + mutex.Lock() + if exists { log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) return existingModel, nil @@ -100,8 +106,10 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed } // Update our cache for next time + mutex.Lock() // exclusive lock to the shared project service model map projectServiceModels[projectSFID] = projectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + mutex.Unlock() return projectModel, nil } @@ -169,7 +177,9 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut var existingParentModel *models.ProjectOutputDetailed // Current project in the cache? + mutex.Lock() // exclusive lock to the shared project service model map existingModel, exists = projectServiceModels[projectSFID] + mutex.Unlock() if exists { log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) @@ -187,7 +197,9 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut projectParentSFID := utils.GetProjectParentSFID(existingModel) // Parent SFID in the cache? + mutex.Lock() // exclusive lock to the shared project service model map existingParentModel, exists = projectServiceModels[projectParentSFID] + mutex.Unlock() if exists { return existingParentModel, nil } @@ -200,8 +212,10 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut } // Save/Update our cache for next time + mutex.Lock() // exclusive lock to the shared project service model map projectServiceModels[projectParentSFID] = parentProjectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + mutex.Unlock() return parentProjectModel, nil } @@ -217,8 +231,10 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut } // Save/Update our cache for next time + mutex.Lock() // exclusive lock to the shared project service model map projectServiceModels[projectSFID] = projectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + mutex.Unlock() // No parent if !utils.IsProjectHaveParent(projectModel) { @@ -235,7 +251,9 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut projectParentSFID := utils.GetProjectParentSFID(projectModel) // Parent in the cache? + mutex.Lock() // exclusive lock to the shared project service model map existingParentModel, exists = projectServiceModels[projectParentSFID] + mutex.Unlock() // exclusive lock to the shared project service model map if exists { return existingParentModel, nil } @@ -248,8 +266,10 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut } // Save/Update our cache for next time + mutex.Lock() // exclusive lock to the shared project service model map projectServiceModels[projectParentSFID] = parentProjectModel log.WithFields(f).Debugf("added project model to cache - cache size: %d", len(projectServiceModels)) + mutex.Unlock() // exclusive lock to the shared project service model map return parentProjectModel, nil } From f43ffd665d0ca47b41b330119cfc03977f39480c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:48:30 -0700 Subject: [PATCH 0387/1276] Bump tar from 4.4.13 to 4.4.15 in /cla-frontend-contributor-console (#3103) Bumps [tar](https://github.com/npm/node-tar) from 4.4.13 to 4.4.15. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v4.4.13...v4.4.15) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 561c1188b..d5cee53c5 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -6124,9 +6124,9 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^4.0.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + version "4.4.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" + integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" From f3e9cf6aad84bfc4f0eb24dc8da641f16b8a36b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:48:41 -0700 Subject: [PATCH 0388/1276] Bump tar from 6.1.0 to 6.1.3 in /cla-backend (#3104) Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.3. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.3) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 19bc7ae7d..2112e51ab 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -5864,9 +5864,9 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + version "6.1.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.3.tgz#e44b97ee7d6cc7a4c574e8b01174614538291825" + integrity sha512-3rUqwucgVZXTeyJyL2jqtUau8/8r54SioM1xj3AmTX3HnWQdj2AydfJ2qYYayPyIIznSplcvU9mhBb7dR2XF3w== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From a625c5a4b9182570da474fdf78c68361fac8c38c Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 3 Aug 2021 15:49:23 -0700 Subject: [PATCH 0389/1276] Resolved Minor Lock/Unlock Issue (#3105) Signed-off-by: David Deal --- cla-backend-go/v2/project-service/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 87c7a9e9c..601627266 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -83,7 +83,7 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed // Lookup in cache first mutex.Lock() // exclusive lock to the shared project service model map existingModel, exists := projectServiceModels[projectSFID] - mutex.Lock() + mutex.Unlock() if exists { log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) From 34c9e9e767ceca1cd207b8758475eb86f820f1f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:51:27 -0700 Subject: [PATCH 0390/1276] Bump tar from 6.1.0 to 6.1.4 in /cla-backend-go (#3106) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index d9354dd2e..55e94ac56 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -4753,9 +4753,9 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + version "6.1.4" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.4.tgz#9f0722b772a5e00dba7d52e1923b37a7ec3799b3" + integrity sha512-kcPWrO8S5ABjuZ/v1xQHP8xCEvj1dQ1d9iAb6Qs4jLYzaAIYWwST2IQpz7Ud8VNYRI+fGhFjrnzRKmRggKWg3g== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From 16c61450b453561a4eb75a797e275b805f62312f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:52:00 -0700 Subject: [PATCH 0391/1276] Bump tar from 4.4.13 to 4.4.15 in /cla-frontend-corporate-console (#3107) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 8064dde49..4094dd7cd 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -6087,9 +6087,9 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^4.0.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + version "4.4.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" + integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" From 482cdd55ee9409f2b10500195ccb807eef6efd49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:53:28 -0700 Subject: [PATCH 0392/1276] Bump tar from 6.0.5 to 6.1.4 (#3108) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 97d085948..ecc42d4b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4555,9 +4555,9 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== + version "6.1.4" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.4.tgz#9f0722b772a5e00dba7d52e1923b37a7ec3799b3" + integrity sha512-kcPWrO8S5ABjuZ/v1xQHP8xCEvj1dQ1d9iAb6Qs4jLYzaAIYWwST2IQpz7Ud8VNYRI+fGhFjrnzRKmRggKWg3g== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From 546d52df935cbafba870a8b23e8dff044b7d8cf6 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 4 Aug 2021 11:25:46 -0700 Subject: [PATCH 0393/1276] Resolved Template Preview Issue (#3109) --- cla-backend-go/docraptor/client.go | 19 +++++++++++-- cla-backend-go/go.mod | 26 +++++++++++++++++- cla-backend-go/go.sum | 43 ++++++++++++++++++++++++++++++ cla-backend-go/template/service.go | 26 +++++++++++------- 4 files changed, 102 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/docraptor/client.go b/cla-backend-go/docraptor/client.go index 7b8503a54..f7df123c0 100644 --- a/cla-backend-go/docraptor/client.go +++ b/cla-backend-go/docraptor/client.go @@ -51,6 +51,8 @@ func (dc Client) CreatePDF(html string, claType string) (io.ReadCloser, error) { f := logrus.Fields{ "functionName": "v1.docraptor.client.CreatePDF", "claType": claType, + "testMode": dc.testMode, + "url": dc.url, } document := map[string]interface{}{ @@ -69,9 +71,22 @@ func (dc Client) CreatePDF(html string, claType string) (io.ReadCloser, error) { log.WithFields(f).Debug("Generating PDF using docraptor...") resp, err := http.Post(dc.url, "application/json", bytes.NewBuffer(documentBytes)) if err != nil { - log.WithFields(f).WithError(err).Warn("problem with API call to docraptor") + log.WithFields(f).WithError(err).Warnf("problem with API call to docraptor url: %s", dc.url) return nil, err } - defer resp.Body.Close() + // Do not close - rely on the caller to close the reader otherwise we will get the read from Response.Body after Close error + //defer func() { + // closeErr := resp.Body.Close() + // if closeErr != nil { + // log.WithFields(f).WithError(closeErr).Warn("problem closing docraptor response") + // } + //}() + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unexpected http response code from docraptor url: %s, status code: %d", dc.url, resp.StatusCode) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + log.WithFields(f).Debugf("successful response from docraptor url: %s, status code: %d", dc.url, resp.StatusCode) + return resp.Body, nil } diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index c93e34994..1c4bfd89f 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -16,10 +16,16 @@ require ( github.com/bitly/go-simplejson v0.5.0 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 github.com/bradleyfalzon/ghinstallation v1.1.1 + github.com/coreos/bbolt v1.3.2 // indirect + github.com/coreos/etcd v3.3.13+incompatible // indirect + github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fnproject/fdk-go v0.0.2 github.com/gin-gonic/gin v1.7.2 + github.com/go-delve/delve v1.7.0 // indirect github.com/go-openapi/errors v0.19.6 github.com/go-openapi/loads v0.19.5 github.com/go-openapi/runtime v0.19.19 @@ -34,33 +40,50 @@ require ( github.com/google/go-github/v37 v37.0.0 github.com/google/uuid v1.1.4 github.com/gorilla/sessions v1.2.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/imroc/req v0.3.0 github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 + github.com/jonboulle/clockwork v0.1.0 // indirect github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f // indirect github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 github.com/kr/pretty v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.1 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/mozillazg/request v0.8.0 // indirect github.com/myitcv/gobin v0.0.14 // indirect github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc + github.com/peterh/liner v1.2.1 // indirect + github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0 // indirect + github.com/prometheus/client_golang v0.9.3 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/rs/cors v1.7.0 github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.1.1 + github.com/soheilhy/cmux v0.1.4 // indirect + github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 + github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/ugorji/go v1.2.6 // indirect github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a github.com/xanzy/go-gitlab v0.50.1 + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + go.etcd.io/bbolt v1.3.2 // indirect + go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a // indirect go.uber.org/ratelimit v0.1.0 + golang.org/x/arch v0.0.0-20210727222714-28578f966459 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 @@ -69,4 +92,5 @@ require ( golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/resty.v1 v1.12.0 // indirect ) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index bf9282154..52c78f441 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -137,9 +137,15 @@ github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzA github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= +github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -183,6 +189,8 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7a github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-delve/delve v1.7.0 h1:MaWAD3LtvjE/LL98urSHPjaMT+OubpQ2sqF3R2Uj1rc= +github.com/go-delve/delve v1.7.0/go.mod h1:2DpgGoHOW7r7MXyykmT7axp9IEEIc8EV/swa5m8rkbo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -371,6 +379,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-dap v0.5.0 h1:RMHAVn5xeunBakYk65ggHXttk6qjZVdbmi+xhAoL2wY= +github.com/google/go-dap v0.5.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3bOQ/tM= @@ -460,6 +470,8 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= @@ -530,6 +542,7 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= @@ -565,14 +578,20 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSW github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -630,11 +649,15 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= +github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -656,6 +679,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzr github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -668,8 +693,12 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= @@ -688,6 +717,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -704,12 +734,16 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -787,6 +821,9 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= +go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a h1:wDtSCWGrX9tusypq2Qq9xzaA3Tf/+4D2KaWO+HQvGZE= +go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -798,6 +835,9 @@ go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= +golang.org/x/arch v0.0.0-20210727222714-28578f966459 h1:ECTRghTMeoUryGydSc+nr1o4M2i73DwlP4LFEDJb3II= +golang.org/x/arch v0.0.0-20210727222714-28578f966459/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -969,6 +1009,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1032,6 +1073,7 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1218,6 +1260,7 @@ honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= diff --git a/cla-backend-go/template/service.go b/cla-backend-go/template/service.go index dd2a040f3..f364f7ea2 100644 --- a/cla-backend-go/template/service.go +++ b/cla-backend-go/template/service.go @@ -142,17 +142,25 @@ func (s Service) CreateTemplatePreview(ctx context.Context, claGroupFields *mode return nil, errors.New("invalid value of template_for") } - pdf, err := s.docRaptorClient.CreatePDF(templateHTML, templateFor) + ioReader, err := s.docRaptorClient.CreatePDF(templateHTML, templateFor) if err != nil { + log.WithFields(f).WithError(err).Warn("problem with API call to docraptor service") return nil, err } defer func() { - closeErr := pdf.Close() + closeErr := ioReader.Close() if closeErr != nil { log.WithFields(f).WithError(closeErr).Warn("error closing PDF") } }() - return ioutil.ReadAll(pdf) + + bytes, err := ioutil.ReadAll(ioReader) + if err != nil { + log.WithFields(f).WithError(err).Warn("error reading PDF bytes from the generated template") + return nil, err + } + + return bytes, err } // CreateCLAGroupTemplate service method @@ -201,19 +209,19 @@ func (s Service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, // Invoke the go routine - any errors will be handled below eg.Go(func() error { log.WithFields(f).Debugf("Creating PDF for %s", claTypeICLA) - iclaPdf, iclaErr := s.docRaptorClient.CreatePDF(iclaTemplateHTML, claTypeICLA) + ioReader, iclaErr := s.docRaptorClient.CreatePDF(iclaTemplateHTML, claTypeICLA) if iclaErr != nil { log.WithFields(f).WithError(iclaErr).Warn("Problem generating ICLA template via docraptor client - returning empty template PDFs") return err } defer func() { - closeErr := iclaPdf.Close() + closeErr := ioReader.Close() if closeErr != nil { log.WithFields(f).WithError(closeErr).Warn("error closing ICLA PDF") } }() iclaFileName := s.generateTemplateS3FilePath(claGroupID, claTypeICLA) - iclaFileURL, err = s.SaveTemplateToS3(bucket, iclaFileName, iclaPdf) + iclaFileURL, err = s.SaveTemplateToS3(bucket, iclaFileName, ioReader) if err != nil { log.WithFields(f).WithError(err).Warnf("Problem uploading ICLA PDF: %s to s3 - returning empty template PDFs", iclaFileName) return err @@ -228,19 +236,19 @@ func (s Service) CreateCLAGroupTemplate(ctx context.Context, claGroupID string, // Invoke the go routine - any errors will be handled below eg.Go(func() error { log.WithFields(f).Debugf("Creating PDF for %s", claTypeCCLA) - cclaPdf, cclaErr := s.docRaptorClient.CreatePDF(cclaTemplateHTML, claTypeCCLA) + ioReader, cclaErr := s.docRaptorClient.CreatePDF(cclaTemplateHTML, claTypeCCLA) if cclaErr != nil { log.WithFields(f).WithError(cclaErr).Warn("Problem generating CCLA template via docraptor client - returning empty template PDFs") return err } defer func() { - closeErr := cclaPdf.Close() + closeErr := ioReader.Close() if closeErr != nil { log.WithFields(f).WithError(closeErr).Warn("error closing CCLA PDF") } }() cclaFileName := s.generateTemplateS3FilePath(claGroupID, claTypeCCLA) - cclaFileURL, err = s.SaveTemplateToS3(bucket, cclaFileName, cclaPdf) + cclaFileURL, err = s.SaveTemplateToS3(bucket, cclaFileName, ioReader) if err != nil { log.WithFields(f).Warnf("Problem uploading CCLA PDF: %s to s3, error: %v - returning empty template PDFs", cclaFileName, err) return err From b1fcf91d90a4510cbdea07810fc3df29ea7764a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Aug 2021 17:05:07 -0700 Subject: [PATCH 0394/1276] Bump tar from 4.4.10 to 4.4.15 in /cla-frontend-project-console (#3110) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 62 +++++++++++--------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index c58c94dd5..30230a8eb 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -1481,16 +1481,11 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1: +chownr@^1.0.1, chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== - chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -2748,11 +2743,11 @@ fs-extra@^9.0.1: universalify "^1.0.0" fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: - minipass "^2.2.1" + minipass "^2.6.0" fs-minipass@^2.0.0: version "2.1.0" @@ -4177,15 +4172,15 @@ minimatch@^3.0.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.2.1, minipass@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" @@ -4198,11 +4193,11 @@ minipass@^3.0.0: yallist "^4.0.0" minizlib@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: - minipass "^2.2.1" + minipass "^2.9.0" minizlib@^2.1.1: version "2.1.2" @@ -4229,11 +4224,11 @@ mixin-object@^2.0.1: is-extendable "^0.1.1" mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "0.0.8" + minimist "^1.2.5" mkdirp@^1.0.3: version "1.0.4" @@ -5326,12 +5321,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -safe-buffer@^5.0.1, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6022,13 +6012,13 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^4, tar@^4.0.2: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== + version "4.4.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" + integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.5" + minipass "^2.8.6" minizlib "^1.2.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" @@ -6539,9 +6529,9 @@ yallist@^2.1.2: integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= yallist@^3.0.0, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" From 4145e9c9c46ce27039339a81e6f47397eb0c4395 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 07:14:54 -0700 Subject: [PATCH 0395/1276] Bump path-parse from 1.0.6 to 1.0.7 in /cla-frontend-contributor-console/edge (#3133) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/edge/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock index 77b6a1c18..907fac2cf 100644 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ b/cla-frontend-contributor-console/edge/yarn.lock @@ -2391,9 +2391,9 @@ path-key@^2.0.0: integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^1.0.0: version "1.1.0" From e959221bd78b7feeadd5a483763853ec27999f66 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 11 Aug 2021 20:11:40 +0300 Subject: [PATCH 0396/1276] gitlab activity callback (#3140) --- cla-backend-go/cmd/server.go | 4 + cla-backend-go/gitlab/mr.go | 162 ++++++++ cla-backend-go/gitlab/organization.go | 52 +++ cla-backend-go/gitlab/repository.go | 4 + cla-backend-go/go.sum | 4 + cla-backend-go/signatures/repository.go | 5 + cla-backend-go/swagger/cla.v2.yaml | 38 +- cla-backend-go/v2/gitlab-activity/handlers.go | 81 ++++ cla-backend-go/v2/gitlab-activity/service.go | 381 ++++++++++++++++++ .../v2/gitlab-activity/service_test.go | 104 +++++ .../v2/gitlab_organizations/handlers.go | 7 +- .../v2/gitlab_organizations/repository.go | 1 + 12 files changed, 839 insertions(+), 4 deletions(-) create mode 100644 cla-backend-go/gitlab/mr.go create mode 100644 cla-backend-go/gitlab/repository.go create mode 100644 cla-backend-go/v2/gitlab-activity/handlers.go create mode 100644 cla-backend-go/v2/gitlab-activity/service.go create mode 100644 cla-backend-go/v2/gitlab-activity/service_test.go diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index d143fb378..28c0dab40 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" + gitlab_activity "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab-activity" + "github.com/go-openapi/strfmt" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" @@ -302,6 +304,7 @@ func server(localMode bool) http.Handler { v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo) gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v1ProjectClaGroupRepo) + gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, repositoriesRepo, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, githubOrganizationsRepo, eventsService, autoEnableService, emailService) @@ -342,6 +345,7 @@ func server(localMode bool) http.Handler { github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) + gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) diff --git a/cla-backend-go/gitlab/mr.go b/cla-backend-go/gitlab/mr.go new file mode 100644 index 000000000..0811613a1 --- /dev/null +++ b/cla-backend-go/gitlab/mr.go @@ -0,0 +1,162 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "fmt" + "strings" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/xanzy/go-gitlab" +) + +// FetchMrInfo is responsible for fetching the MR info for given project +func FetchMrInfo(client *gitlab.Client, projectID int, mergeID int) (*gitlab.MergeRequest, error) { + m, _, err := client.MergeRequests.GetMergeRequest(projectID, mergeID, &gitlab.GetMergeRequestsOptions{}) + if err != nil { + return nil, fmt.Errorf("fetching merge request : %d for project : %v failed : %v", mergeID, projectID, err) + } + + return m, nil +} + +// FetchMrParticipants is responsible to get unique mr participants +func FetchMrParticipants(client *gitlab.Client, projectID int, mergeID int, unique bool) ([]*gitlab.User, error) { + commits, _, err := client.MergeRequests.GetMergeRequestCommits(projectID, mergeID, &gitlab.GetMergeRequestCommitsOptions{}) + if err != nil { + return nil, fmt.Errorf("fetching gitlab participants for project : %d and merge id : %d, failed : %v", projectID, mergeID, err) + } + + if len(commits) == 0 { + return nil, nil + } + + var results []*gitlab.User + uniqueUsers := map[int]bool{} + + for _, commit := range commits { + authorEmail := commit.AuthorEmail + authorName := commit.AuthorName + + log.Debugf("user email found : %s, user name : %s, searching in gitlab ...", authorEmail, authorName) + + var user *gitlab.User + if authorName != "" { + user, err = searchForUser(client, authorEmail) + if err != nil { + return nil, fmt.Errorf("searching for author email : %s, failed : %v", authorEmail, err) + } + } + + if authorName != "" && user == nil { + user, err = searchForUser(client, authorName) + if err != nil { + return nil, fmt.Errorf("searching for author name : %s, failed : %v", authorName, err) + } + } + + if user == nil { + return nil, fmt.Errorf("no users found for commit author email : %s, name : %s", authorEmail, authorName) + } + + if uniqueUsers[user.ID] { + continue + } + + results = append(results, user) + uniqueUsers[user.ID] = true + } + + return results, nil +} + +// SetCommitStatus is responsible for setting the MR status for commit sha +func SetCommitStatus(client *gitlab.Client, projectID int, commitSha string, state gitlab.BuildStateValue, message string) error { + options := &gitlab.SetCommitStatusOptions{ + State: state, + Name: gitlab.String("easyCLA Bot"), + Description: gitlab.String(message), + } + + if state == gitlab.Failed { + options.TargetURL = gitlab.String("http://localhost:8080/gitlab/sign") + } + + _, _, err := client.Commits.SetCommitStatus(projectID, commitSha, options) + if err != nil { + return fmt.Errorf("setting commit status for the sha : %s and project id : %d failed : %v", commitSha, projectID, err) + } + + return nil +} + +// SetMrComment is responsible for setting the comment body for project and merge id +func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitlab.BuildStateValue, message string) error { + covered := ` + covered
    ` + failed := ` +covered
    ` + + var body string + if state == gitlab.Failed { + body = failed + } else { + body = covered + } + + notes, _, err := client.Notes.ListMergeRequestNotes(projectID, mergeID, &gitlab.ListMergeRequestNotesOptions{}) + if err != nil { + return fmt.Errorf("fetching comments for project id : %d and merge id : %d : failed %v", projectID, mergeID, err) + } + + var previousNote *gitlab.Note + if len(notes) > 0 { + for _, n := range notes { + if strings.Contains(n.Body, "cla-signed.svg") || strings.Contains(n.Body, "cla-not-signed.svg") { + previousNote = n + break + } + } + } + + if previousNote == nil { + log.Debugf("no previous comments found for project id : %d and merge id : %d", projectID, mergeID) + _, _, err = client.Notes.CreateMergeRequestNote(projectID, mergeID, &gitlab.CreateMergeRequestNoteOptions{ + Body: &body, + }) + if err != nil { + return fmt.Errorf("creating comment for project id : %d and merge id : %d : failed %v", projectID, mergeID, err) + } + } else { + log.Debugf("previous comments found for project id : %d and merge id : %d", projectID, mergeID) + _, _, err = client.Notes.UpdateMergeRequestNote(projectID, mergeID, previousNote.ID, &gitlab.UpdateMergeRequestNoteOptions{ + Body: &body, + }) + if err != nil { + return fmt.Errorf("updtae comment for project id : %d and merge id : %d : failed %v", projectID, mergeID, err) + } + } + + return nil +} + +func searchForUser(client *gitlab.Client, search string) (*gitlab.User, error) { + users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{ + Search: gitlab.String(search), + }) + + if err != nil { + return nil, fmt.Errorf("searching for user string : %s failed : %v", search, err) + } + + if len(users) == 0 { + return nil, nil + } + + if len(users) > 1 { + return nil, fmt.Errorf("found more than one gitlab user for search string : %s", search) + } + + return users[0], nil +} diff --git a/cla-backend-go/gitlab/organization.go b/cla-backend-go/gitlab/organization.go index 5144bd4ad..f09b096dc 100644 --- a/cla-backend-go/gitlab/organization.go +++ b/cla-backend-go/gitlab/organization.go @@ -6,9 +6,17 @@ package gitlab import ( "fmt" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/xanzy/go-gitlab" ) +// UserGroup represents gitlab group +type UserGroup struct { + Name string + FullPath string +} + // GetGroupByName gets a gitlab Group by the given name func GetGroupByName(client *gitlab.Client, name string) (*gitlab.Group, error) { groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{}) @@ -24,3 +32,47 @@ func GetGroupByName(client *gitlab.Client, name string) (*gitlab.Group, error) { return nil, nil } + +// ListUserProjectGroups fetches the unique groups of a gitlab users groups, +// note: it doesn't list the projects/groups the user is member of ..., it's very limited +func ListUserProjectGroups(client *gitlab.Client, userID int) ([]*UserGroup, error) { + listOptions := &gitlab.ListProjectsOptions{ + ListOptions: gitlab.ListOptions{ + PerPage: 100, + }} + + userGroupsMap := map[string]*UserGroup{} + for { + log.Debugf("fetching projects for user id : %d with options : %v", userID, listOptions.ListOptions) + projects, resp, err := client.Projects.ListUserProjects(userID, listOptions) + if err != nil { + return nil, fmt.Errorf("listing user : %d projects failed : %v", userID, err) + } + log.Printf("fetched %d projects for the user ", len(projects)) + + if len(projects) == 0 { + break + } + + for _, p := range projects { + log.Debugf("checking following project : %s", p.PathWithNamespace) + log.Debugf("fetched following namespace : %+v", p.Namespace) + userGroupsMap[p.Namespace.FullPath] = &UserGroup{ + Name: p.Namespace.Name, + FullPath: p.Namespace.FullPath, + } + } + + if listOptions.Page >= resp.NextPage { + break + } + listOptions.Page = resp.NextPage + } + + var userGroups []*UserGroup + for _, v := range userGroupsMap { + userGroups = append(userGroups, v) + } + + return userGroups, nil +} diff --git a/cla-backend-go/gitlab/repository.go b/cla-backend-go/gitlab/repository.go new file mode 100644 index 000000000..90f42708c --- /dev/null +++ b/cla-backend-go/gitlab/repository.go @@ -0,0 +1,4 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 52c78f441..690e61600 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -542,6 +542,7 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -652,11 +653,13 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0 h1:wBza4Dlm/NCQF572oSGNZ69flNFxlwIHjtwS6oy3Rvw= github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= @@ -1260,6 +1263,7 @@ honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index fc0e05047..8d6477723 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -1516,6 +1516,11 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, filter = addAndCondition(filter, expression.Name(SignatureUserGitHubUsername).Equal(expression.Value(criteria.GitHubUsername)), &filterAdded) } + if criteria != nil && criteria.GitHubUsername != "" { + log.WithFields(f).Debugf("adding Gitlabusername criteria filter for :%s ", criteria.GitlabUsername) + filter = addAndCondition(filter, expression.Name(SignatureUserGitlabUsername).Equal(expression.Value(criteria.GitlabUsername)), &filterAdded) + } + if criteria != nil && criteria.UserEmail != "" { log.WithFields(f).Debugf("adding useremail criteria filter for : %s ", criteria.UserEmail) filter = addAndCondition(filter, expression.Name("user_email").Equal(expression.Value(criteria.UserEmail)), &filterAdded) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index ac0cafbae..523fbec3d 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3820,7 +3820,7 @@ paths: get: summary: The endpoint is called after user authorizes EasyCLA bot description: The endpoint is responsible for storing the access token for the user and registering the webhooks is autoenable is on - security: [] + security: [ ] operationId: gitlabOauthCallback parameters: - name: code @@ -3853,6 +3853,35 @@ paths: tags: - gitlab-activity + /gitlab/activity: + post: + summary: Gitlab Activity Callback Handler + description: Gitlab Activity Callback Handler reacts to Gitlab events emmited. + security: [ ] + operationId: gitlabActivity + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-github-event" + - $ref: "#/parameters/x-hub-signature" + - name: gitlabActivityInput + in: body + schema: + $ref: '#/definitions/gitlab-activity-input' + responses: + '200': + description: 'Success' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-activity + + responses: unauthorized: description: Unauthorized @@ -4204,6 +4233,13 @@ definitions: type: string additionalProperties: true + gitlab-activity-input: + type: object + properties: + object_kind: + type: string + additionalProperties: true + github-repository-input: type: object required: diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go new file mode 100644 index 000000000..6f3723438 --- /dev/null +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -0,0 +1,81 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_activity + +import ( + "context" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/go-openapi/runtime/middleware" + "github.com/gofrs/uuid" + "github.com/sirupsen/logrus" + gitlabsdk "github.com/xanzy/go-gitlab" +) + +func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service) { + + api.GitlabActivityGitlabActivityHandler = gitlab_activity.GitlabActivityHandlerFunc(func(params gitlab_activity.GitlabActivityParams) middleware.Responder { + requestID, _ := uuid.NewV4() + reqID := requestID.String() + f := logrus.Fields{ + "functionName": "gitlab_activity.handlers.GitlabActivityGitlabActivityHandler", + "requestID": reqID, + } + log.WithFields(f).Debugf("handling gitlab activity callback") + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) + + jsonData, err := params.GitlabActivityInput.MarshalJSON() + if err != nil { + msg := fmt.Sprintf("unmarshall event data failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + event, err := gitlabsdk.ParseWebhook(gitlabsdk.EventTypeMergeRequest, jsonData) + if err != nil { + msg := fmt.Sprintf("parsing gitlab merge event type failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + mergeEvent, ok := event.(*gitlabsdk.MergeEvent) + if !ok { + msg := fmt.Sprintf("parsing gitlab merge event typecast failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + if mergeEvent.ObjectAttributes.State != "opened" { + msg := fmt.Sprintf("parsing gitlab merge event failed, only opened accepted") + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + err = service.ProcessMergeOpenedActivity(ctx, mergeEvent) + if err != nil { + msg := fmt.Sprintf("processing gitlab merge event failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityInternalServerError().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + return gitlab_activity.NewGitlabActivityOK() + //return gitlab_activity.NewGitlabActivityOK().WithPayload(&models.SuccessResponse{ + // Code: "200", + // Message: "oauth credentials stored successfully", + // XRequestID: reqID, + //}) + + }) + +} diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go new file mode 100644 index 000000000..8b2164cb6 --- /dev/null +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -0,0 +1,381 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_activity + +import ( + "context" + "errors" + "fmt" + "github.com/communitybridge/easycla/cla-backend-go/company" + signatures1 "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" + "regexp" + "strconv" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/repositories" + "github.com/communitybridge/easycla/cla-backend-go/signatures" + "github.com/communitybridge/easycla/cla-backend-go/users" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" + "github.com/xanzy/go-gitlab" +) + +type Service interface { + ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *gitlab.MergeEvent) error +} + +type service struct { + usersRepository users.UserRepository + gitlabRepository gitlab_organizations.RepositoryInterface + githubRepositories repositories.Repository + signaturesRepository signatures.SignatureRepository + projectsCLAGroupsRepository projects_cla_groups.Repository + companyRepository company.IRepository + signatureRepository signatures.SignatureRepository +} + +func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, githubRepositories repositories.Repository, usersRepository users.UserRepository, signaturesRepository signatures.SignatureRepository, projectsCLAGroupsRepository projects_cla_groups.Repository, + companyRepository company.IRepository, signatureRepository signatures.SignatureRepository) Service { + return &service{ + usersRepository: usersRepository, + gitlabRepository: gitlabRepository, + githubRepositories: githubRepositories, + signaturesRepository: signaturesRepository, + projectsCLAGroupsRepository: projectsCLAGroupsRepository, + companyRepository: companyRepository, + signatureRepository: signatureRepository, + } +} + +func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *gitlab.MergeEvent) error { + projectName := mergeEvent.Project.Name + projectID := mergeEvent.Project.ID + mergeID := mergeEvent.ObjectAttributes.IID + repositoryName := mergeEvent.Repository.Name + repositoryPath := mergeEvent.Project.PathWithNamespace + lastCommitSha := mergeEvent.ObjectAttributes.LastCommit.ID + + f := logrus.Fields{ + "functionName": "ProcessMergeOpenedActivity", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabProjectName": projectName, + "gitlabProjectID": projectID, + "repositoryName": repositoryName, + "repositoryPath": repositoryPath, + "mergeID": mergeID, + } + + log.WithFields(f).Debugf("looking up for gitlab org in easycla records ...") + gitlabOrg, err := s.getGitlabOrganizationFromMergeEvent(ctx, mergeEvent) + if err != nil { + return fmt.Errorf("fetching internal gitlab org for following path : %s failed : %v", repositoryPath, err) + } + + log.WithFields(f).Debugf("internal gitlab org : %s:%s is associated with external path : %s", gitlabOrg.OrganizationID, gitlabOrg.OrganizationName, repositoryPath) + + gitlabClient, err := gitlab2.NewGitlabOauthClient(gitlabOrg.AuthInfo) + if err != nil { + return fmt.Errorf("initializing gitlab client : %v", err) + } + + _, err = gitlab2.FetchMrInfo(gitlabClient, projectID, mergeID) + if err != nil { + return fmt.Errorf("fetching info for mr : %d and project : %d: %s, failed : %v", mergeID, projectID, projectName, err) + } + + // try to find the repository via the external id + gitlabRepo, err := s.getGitlabRepoByExternalID(ctx, gitlabOrg.OrganizationName, strconv.Itoa(projectID)) + if err != nil { + return fmt.Errorf("finding internal repository for gitlab org name failed : %v", err) + } + + log.WithFields(f).Debugf("internal gitlab repository found with id : %s", gitlabRepo.RepositoryID) + participants, err := gitlab2.FetchMrParticipants(gitlabClient, projectID, mergeID, true) + if err != nil { + return fmt.Errorf("fetching mr participants : %v", err) + } + + if len(participants) == 0 { + return fmt.Errorf("no participants found in gitlab mr : %d, and gitlab project : %d", mergeID, projectID) + } + + claGroup, err := s.projectsCLAGroupsRepository.GetClaGroupIDForProject(ctx, gitlabOrg.ProjectSFID) + if err != nil { + return fmt.Errorf("fetching claGroup id for gitlabOrg project sfid : %s, failed : %v", gitlabOrg.ProjectSFID, err) + } + claGroupID := claGroup.ClaGroupID + log.WithFields(f).Debugf("gitlabOrg : %s is associated with cla group id : %s", gitlabOrg.OrganizationName, claGroupID) + + log.WithFields(f).Debugf("found following participants for the MR : %d", len(participants)) + missingUsersMsg := "missing users : " + var missingUsers []string + for _, gitlabUser := range participants { + if ok, err := s.hasUserSigned(ctx, claGroupID, gitlabUser); ok { + log.WithFields(f).Infof("gitlabUser : %d:%s has signed", gitlabUser.ID, gitlabUser.Username) + } else { + missingUsers = append(missingUsers, fmt.Sprintf("gitlabUser : %d:%s hasn't signed", gitlabUser.ID, gitlabUser.Username)) + log.WithFields(f).Errorf("gitlabUser : %d:%s hasn't signed, err : %v", gitlabUser.ID, gitlabUser.Username, err) + } + } + + if len(missingUsers) > 0 { + for _, missing := range missingUsers { + missingUsersMsg += missing + } + log.WithFields(f).Errorf("mr faild with following users : %s", missingUsersMsg) + if err := gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Failed, missingUsersMsg); err != nil { + return fmt.Errorf("setting commit status failed : %v", err) + } + + if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Failed, missingUsersMsg); err != nil { + return fmt.Errorf("setting comment failed : %v", err) + } + + return nil + } + err = gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Success, "all signed passing") + if err != nil { + return fmt.Errorf("setting commit status failed : %v", err) + } + + if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Success, missingUsersMsg); err != nil { + return fmt.Errorf("setting comment failed : %v", err) + } + return err +} + +func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*gitlab_organizations.GitlabOrganization, error) { + repositoryPath := mergeEvent.Project.PathWithNamespace + parts := strings.Split(repositoryPath, "/") + organizationName := parts[0] + gitlabOrgs, err := s.gitlabRepository.GetGitlabOrganizationByName(ctx, organizationName) + if err != nil || len(gitlabOrgs.List) == 0 { + // try getting it with project name as well + gitlabOrgs, err = s.gitlabRepository.GetGitlabOrganizationByName(ctx, mergeEvent.Project.Namespace) + if err != nil { + return nil, fmt.Errorf("gitlab org : %s doesn't exist : %v", organizationName, err) + } + } + + if len(gitlabOrgs.List) == 0 { + return nil, fmt.Errorf("gitlab org : %s doesn't exist", organizationName) + } + + orgID := gitlabOrgs.List[0].OrganizationID + gitlabOrg, err := s.gitlabRepository.GetGitlabOrganization(ctx, orgID) + if err != nil { + return nil, fmt.Errorf("fetching gitlab org : %s failed : %v", orgID, err) + } + + return gitlabOrg, nil +} + +func (s service) getGitlabRepoByExternalID(ctx context.Context, orgName, gitlabRepoID string) (*models.GithubRepository, error) { + gitlabRepos, err := s.githubRepositories.GetRepositoriesByOrganizationName(ctx, orgName) + if err != nil { + return nil, fmt.Errorf("fetching gitlab repo for external id : %s, orgName : %s, failed : %v", gitlabRepoID, orgName, err) + } + + if len(gitlabRepos) == 0 { + return nil, fmt.Errorf("no repositories found for orgName : %s", orgName) + } + + for _, gitlabRepo := range gitlabRepos { + if gitlabRepo.RepositoryExternalID == gitlabRepoID && gitlabRepo.RepositoryType == "gitlab" { + return gitlabRepo, nil + } + } + + return nil, fmt.Errorf("no repositories found for orgName : %s and gitlab external id : %s", orgName, gitlabRepoID) +} + +func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUser *gitlab.User) (bool, error) { + f := logrus.Fields{ + "functionName": "hasUserSigned", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabUserID": gitlabUser.ID, + "gitlabUserName": gitlabUser.Username, + "gitlabUserEmail": gitlabUser.Email, + } + + userModel, b, err := s.findUserModelForGitlabUser(f, gitlabUser) + if err != nil { + return b, err + } + + if userModel == nil { + msg := fmt.Sprintf("gitlab user : %d:%s not found in easycla records", gitlabUser.ID, gitlabUser.Username) + log.WithFields(f).Error(msg) + return false, fmt.Errorf(msg) + } + log.WithFields(f).Debugf("found following easyCLA user for gitlab record, userID: %s, lfusername : %s", userModel.UserID, userModel.LfUsername) + + icla, err := s.signaturesRepository.GetIndividualSignature(ctx, claGroupID, userModel.UserID, aws.Bool(true), aws.Bool(true)) + if err != nil { + return false, fmt.Errorf("fetching ICLS for gitlab user : %d:%s failed : %v", gitlabUser.ID, gitlabUser.Username, err) + } + + if icla != nil { + log.WithFields(f).Infof("user has signed the following signature : %s, passing", icla.SignatureID) + return true, nil + } + + if userModel.CompanyID == "" { + log.WithFields(f).Debugf("user does not have association with any company, can't continue") + return false, fmt.Errorf("user hasn't signed yet") + } + + companyID := userModel.CompanyID + _, err = s.companyRepository.GetCompany(ctx, companyID) + if err != nil { + msg := fmt.Sprintf("can't load company record : %s for user : %s association : %v", companyID, userModel.UserID, err) + log.WithFields(f).Errorf(msg) + return false, fmt.Errorf(msg) + } + + corporateSignature, err := s.signatureRepository.GetCorporateSignature(ctx, claGroupID, companyID, aws.Bool(true), aws.Bool(true)) + if err != nil { + msg := fmt.Sprintf("can't load company signature record : %s for user : %s association : %v", companyID, userModel.UserID, err) + log.WithFields(f).Errorf(msg) + return false, fmt.Errorf(msg) + } + log.WithFields(f).Debugf("loaded signature id : %s for claGroupID : %s and companyID : %s", corporateSignature.SignatureID, claGroupID, companyID) + + approvalCriteria := &signatures.ApprovalCriteria{} + if gitlabUser.Email != "" { + approvalCriteria.UserEmail = gitlabUser.Email + } else if gitlabUser.Username != "" { + approvalCriteria.GitlabUsername = gitlabUser.Username + } else { + msg := fmt.Sprintf("gitlabUser model doesn't have enough information to fetch the employee signatures for user : %s", userModel.UserID) + log.WithFields(f).Errorf(msg) + return false, fmt.Errorf(msg) + } + + employeeSignatures, err := s.signaturesRepository.GetProjectCompanyEmployeeSignatures(ctx, signatures1.GetProjectCompanyEmployeeSignaturesParams{ + CompanyID: companyID, + ProjectID: claGroupID, + }, approvalCriteria, 100) + + if err != nil { + msg := fmt.Sprintf("can't load employee signature records : %s for user : %s association : %v", companyID, userModel.UserID, err) + log.WithFields(f).Errorf(msg) + return false, fmt.Errorf(msg) + } + + if len(employeeSignatures.Signatures) == 0 { + msg := fmt.Sprintf("no employee signature records found for company : %s user : %s association", companyID, userModel.UserID) + log.WithFields(f).Errorf(msg) + return false, fmt.Errorf(msg) + } + + if IsUserApprovedForSignature(f, corporateSignature, userModel, gitlabUser) { + log.WithFields(f).Debugf("user is approved in signature : %s", corporateSignature.SignatureID) + return true, nil + } + + log.WithFields(f).Warnf("user not in one of the approval lists") + return false, fmt.Errorf("not signed") +} + +func (s service) findUserModelForGitlabUser(f logrus.Fields, gitlabUser *gitlab.User) (*models.User, bool, error) { + log.WithFields(f).Debugf("Looking up Gitlab user via gitlabID") + userModel, err := s.usersRepository.GetUserByGitlabID(gitlabUser.ID) + if err != nil { + if !strings.Contains(err.Error(), "not found") { + return nil, false, fmt.Errorf("looking up gitlab user via gitlabID : %d failed : %v", gitlabUser.ID, err) + } + userModel = nil + } + if userModel == nil && gitlabUser.Username != "" { + log.WithFields(f).Debugf("Looking up Gitlab user via user gitlab username") + userModel, err = s.usersRepository.GetUserByGitlabUsername(gitlabUser.Username) + if !strings.Contains(err.Error(), "not found") { + return nil, false, fmt.Errorf("looking up gitlab user via gitlabUsername : %s failed : %v", gitlabUser.Username, err) + } + } + + if userModel == nil && gitlabUser.Email != "" { + log.WithFields(f).Debugf("Looking up Gitlab user via user email") + userModel, err = s.usersRepository.GetUserByEmail(gitlabUser.Email) + if err != nil { + if !errors.Is(err, &utils.UserNotFound{}) { + return nil, false, fmt.Errorf("looking up gitlab user via email : %s failed : %v", gitlabUser.Email, err) + } + } + } + return userModel, false, nil +} + +func IsUserApprovedForSignature(f logrus.Fields, corporateSignature *models.Signature, user *models.User, gitlabUser *gitlab.User) bool { + log.WithFields(f).Debugf("checking if user : %s is approved for corporate signature : %s", user.UserID, corporateSignature.SignatureID) + userEmails := user.Emails + if string(user.LfEmail) != "" { + userEmails = append(userEmails, string(user.LfEmail)) + } + + emailApprovalList := corporateSignature.EmailApprovalList + domainApprovalList := corporateSignature.DomainApprovalList + log.WithFields(f).Debugf("checking if user : %s is approved for corporate signature : %s, email approval list : %+v", user.UserID, corporateSignature.SignatureID, emailApprovalList) + + if len(userEmails) > 0 && len(emailApprovalList) > 0 { + for _, email := range userEmails { + for _, approvalEmail := range emailApprovalList { + if email == approvalEmail { + log.WithFields(f).Debugf("found user email : %s in email approval list ", email) + return true + } + } + } + } else { + log.WithFields(f).Warnf("no match for user in signature email approval list") + } + + if len(domainApprovalList) > 0 && len(userEmails) > 0 { + log.WithFields(f).Debugf("checking if emails : %+v are approved for corporate signature : %s, domain approval list : %+v", userEmails, corporateSignature.SignatureID, domainApprovalList) + for _, userEmail := range userEmails { + for _, domainApprovalPattern := range domainApprovalList { + if strings.HasPrefix(domainApprovalPattern, "*.") { + domainApprovalPattern = strings.Replace(domainApprovalPattern, "*.", ".*", 1) + } else if strings.HasPrefix(domainApprovalPattern, "*") { + domainApprovalPattern = strings.Replace(domainApprovalPattern, "*", ".*", 1) + } else if strings.HasPrefix(domainApprovalPattern, ".") { + domainApprovalPattern = strings.Replace(domainApprovalPattern, ".", ".*", 1) + } + regexpApprovalPattern := "^.*@" + domainApprovalPattern + "$" + if ok, err := regexp.MatchString(regexpApprovalPattern, userEmail); ok && err == nil { + log.WithFields(f).Debugf("found user email : %s in email approval list : %s", userEmail, domainApprovalPattern) + return true + } + } + } + } + + gitlabUserName := gitlabUser.Username + gitlabUsernameApprovalList := corporateSignature.GitlabUsernameApprovalList + if gitlabUserName != "" && len(gitlabUsernameApprovalList) > 0 { + log.WithFields(f).Debugf("checking gitlab username : %s for gitlab approval list : %+v", gitlabUserName, gitlabUsernameApprovalList) + for _, gitlabApproval := range gitlabUsernameApprovalList { + if gitlabApproval == gitlabUserName { + log.WithFields(f).Debugf("found gitlab username : %s in gitlab approval list ", gitlabUserName) + return true + } + } + + } else { + log.WithFields(f).Warnf("no match found for gitlabUser : %s in gitlab approval list : %+v", gitlabUserName, gitlabUsernameApprovalList) + } + + // todo check user against the gitlab org approval list + log.WithFields(f).Errorf("unable to find user in any approval list") + return false + +} diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go new file mode 100644 index 000000000..74eef308a --- /dev/null +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -0,0 +1,104 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_activity + +import ( + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/xanzy/go-gitlab" + "testing" +) + +func TestIsUserApprovedForSignature(t *testing.T) { + userModel := &models.User{ + Emails: []string{ + "one@example.com", + "two@bar.com", + }, + } + gitlabUser := &gitlab.User{ + Username: "one", + } + + testCases := []struct{ + name string + signature *models.Signature + expected bool + }{ + { + name: "nothing matched", + signature : &models.Signature{}, + }, + { + name: "email approval list non empty no match", + signature : &models.Signature{ + EmailApprovalList: []string{"three@example.com"}, + }, + }, + { + name: "email approval list match", + signature : &models.Signature{ + EmailApprovalList: []string{"one@example.com"}, + }, + expected: true, + }, + { + name: "domain approval list match no match", + signature : &models.Signature{ + DomainApprovalList: []string{"*.foo.com"}, + }, + expected: false, + }, + { + name: "domain approval list match domain star", + signature : &models.Signature{ + DomainApprovalList: []string{"*.example.com"}, + }, + expected: true, + }, + { + name: "domain approval list match domain star globbing", + signature : &models.Signature{ + DomainApprovalList: []string{"*example.com"}, + }, + expected: true, + }, + { + name: "domain approval list match domain star dot", + signature : &models.Signature{ + DomainApprovalList: []string{".example.com"}, + }, + expected: true, + }, + { + name: "gitlab username approval list no match", + signature : &models.Signature{ + GitlabUsernameApprovalList: []string{"two"}, + }, + expected: false, + }, + { + name: "gitlab username approval list match", + signature : &models.Signature{ + GitlabUsernameApprovalList: []string{"one"}, + }, + expected: true, + }, + } + + for _, tc := range testCases{ + t.Run(tc.name, func(tt *testing.T) { + result := IsUserApprovedForSignature(logrus.Fields{}, tc.signature, userModel, gitlabUser) + if tc.expected{ + assert.True(tt, result) + + }else{ + assert.False(tt, result) + } + }) + } + + +} \ No newline at end of file diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 63f9684a8..0c15a6f3e 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -9,13 +9,13 @@ import ( "fmt" "strings" - "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" - - "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" "github.com/communitybridge/easycla/cla-backend-go/gitlab" "github.com/gofrs/uuid" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/sirupsen/logrus" @@ -289,4 +289,5 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. XRequestID: reqID, }) }) + } diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 2ba7b52ec..27e8e032b 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -35,6 +35,7 @@ type RepositoryInterface interface { AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.CreateGitlabOrganization) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) + GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganizations, error) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error From 4ec4ee53b786e9349fa0d1c7c922b7992aa7fe74 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 11 Aug 2021 16:56:26 -0700 Subject: [PATCH 0397/1276] Added GitLab Repository Support (#3125) * Added GitLab Repository Support - Added GitLab repository v2 swagger API defintions - Added GitLab repository swagger data models - Added GitLab repository handler functions - Added GitLab repository service layer - Added GitLab repository database layer - Updated a few GitHub/GitLab modeling conventions - Added common error models - Added common constants for table columns and indices Signed-off-by: David Deal * Updated GitLab Org Endpoints and Repo Impl - Updated response swagger for add GitLab repo - Added logic to return the newly created GitLab record with the installation URL - Added GetGitlabOrganizationByName to the repo service definition - Rebased changes from main branch Signed-off-by: David Deal * GitLab Swagger Cleanup - Added separate common swagger fragments - Renamed Project Gitlab Org/Repo to GitLab Project Org/Repo so that names align with naming conventions and object/structure names Signed-off-by: David Deal * Minor Code Cleanup in SSM Config Signed-off-by: David Deal --- cla-backend-go/cmd/server.go | 23 +- cla-backend-go/config/ssm.go | 1 - cla-backend-go/github/github_repository.go | 2 +- cla-backend-go/github_organizations/mock.go | 2 +- .../github_organizations/repository.go | 4 +- .../github_organizations/service.go | 12 +- cla-backend-go/project/helpers.go | 2 +- cla-backend-go/project/repository.go | 6 +- cla-backend-go/project/service.go | 4 +- cla-backend-go/repositories/constants.go | 49 ++ .../repositories/mock/mock_repository.go | 102 ++-- .../repositories/mock/mock_service.go | 52 +- cla-backend-go/repositories/models.go | 27 +- cla-backend-go/repositories/repository.go | 195 ++++---- cla-backend-go/repositories/service.go | 42 +- cla-backend-go/signatures/repository.go | 8 +- cla-backend-go/swagger/cla.v1.yaml | 14 +- cla-backend-go/swagger/cla.v2.yaml | 254 +++++++--- ...n.yaml => github-create-organization.yaml} | 0 ...ies.yaml => github-list-repositories.yaml} | 0 .../swagger/common/github-repository.yaml | 62 ++- ...n.yaml => github-update-organization.yaml} | 0 .../swagger/common/gitlab-add-repository.yaml | 42 ++ .../common/gitlab-create-organization.yaml | 27 + .../common/gitlab-list-repositories.yaml | 9 + .../swagger/common/gitlab-organizations.yaml | 1 + .../common/gitlab-project-organization.yaml | 44 ++ .../common/gitlab-project-organizations.yaml | 10 + .../common/gitlab-project-repository.yaml | 41 ++ .../swagger/common/gitlab-repository.yaml | 57 +++ .../common/gitlab-update-organization.yaml | 17 + cla-backend-go/utils/constants.go | 21 + cla-backend-go/utils/errors.go | 120 +++++ cla-backend-go/v2/dynamo_events/autoenable.go | 22 +- .../v2/dynamo_events/autoenable_test.go | 62 +-- .../v2/dynamo_events/github_repository.go | 4 +- .../v2/dynamo_events/projects_cla_groups.go | 4 +- cla-backend-go/v2/github_activity/service.go | 81 +-- .../v2/github_activity/service_test.go | 4 +- .../v2/github_organizations/service.go | 47 +- cla-backend-go/v2/gitlab-activity/service.go | 46 +- .../v2/gitlab-activity/service_test.go | 38 +- .../v2/gitlab_organizations/handlers.go | 6 +- .../v2/gitlab_organizations/repository.go | 65 +-- .../v2/gitlab_organizations/service.go | 48 +- .../v2/repositories/gitlab_services.go | 138 ++++++ cla-backend-go/v2/repositories/handlers.go | 195 +++++++- cla-backend-go/v2/repositories/repository.go | 465 ++++++++++++++++++ cla-backend-go/v2/repositories/service.go | 141 +++--- 49 files changed, 1983 insertions(+), 633 deletions(-) create mode 100644 cla-backend-go/repositories/constants.go rename cla-backend-go/swagger/common/{create-github-organization.yaml => github-create-organization.yaml} (100%) rename cla-backend-go/swagger/common/{list-github-repositories.yaml => github-list-repositories.yaml} (100%) rename cla-backend-go/swagger/common/{update-github-organization.yaml => github-update-organization.yaml} (100%) create mode 100644 cla-backend-go/swagger/common/gitlab-add-repository.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-create-organization.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-list-repositories.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-project-organization.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-project-organizations.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-project-repository.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-repository.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-update-organization.yaml create mode 100644 cla-backend-go/v2/repositories/gitlab_services.go create mode 100644 cla-backend-go/v2/repositories/repository.go diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 28c0dab40..35e59ba7a 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -242,14 +242,15 @@ func server(localMode bool) http.Handler { // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) usersRepo := users.NewRepository(awsSession, stage) - repositoriesRepo := repositories.NewRepository(awsSession, stage) + gitV1Repository := repositories.NewRepository(awsSession, stage) + gitV2Repository := v2Repositories.NewRepository(awsSession, stage) gerritRepo := gerrits.NewRepository(awsSession, stage) templateRepo := template.NewRepository(awsSession, stage) approvalListRepo := approval_list.NewRepository(awsSession, stage) v1CompanyRepo := v1Company.NewRepository(awsSession, stage) eventsRepo := events.NewRepository(awsSession, stage) v1ProjectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) - v1CLAGroupRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, v1ProjectClaGroupRepo) + v1CLAGroupRepo := project.NewRepository(awsSession, stage, gitV1Repository, gerritRepo, v1ProjectClaGroupRepo) metricsRepo := metrics.NewRepository(awsSession, stage, configFile.APIGatewayURL, v1ProjectClaGroupRepo) githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) gitlabOrganizationRepo := gitlab_organizations.NewRepository(awsSession, stage) @@ -272,7 +273,7 @@ func server(localMode bool) http.Handler { }) // Signature repository handler - signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService, repositoriesRepo, githubOrganizationsRepo, gerritService) + signaturesRepo := signatures.NewRepository(awsSession, stage, v1CompanyRepo, usersRepo, eventsService, gitV1Repository, githubOrganizationsRepo, gerritService) // Initialize the external platform services - these are external APIs that // we download the swagger specification, generate the models, and have @@ -286,7 +287,7 @@ func server(localMode bool) http.Handler { usersService := users.NewService(usersRepo, eventsService) healthService := health.New(Version, Commit, Branch, BuildDate) templateService := template.NewService(stage, templateRepo, docraptorClient, awsSession) - v1ProjectService := project.NewService(v1CLAGroupRepo, repositoriesRepo, gerritRepo, v1ProjectClaGroupRepo, usersRepo) + v1ProjectService := project.NewService(v1CLAGroupRepo, gitV1Repository, gerritRepo, v1ProjectClaGroupRepo, usersRepo) emailTemplateService := emails.NewEmailTemplateService(v1CLAGroupRepo, v1ProjectClaGroupRepo, v1ProjectService, configFile.CorporateConsoleV1URL, configFile.CorporateConsoleV2URL) emailService := emails.NewService(emailTemplateService, v1ProjectService) v2ProjectService := v2Project.NewService(v1ProjectService, v1CLAGroupRepo, v1ProjectClaGroupRepo) @@ -296,18 +297,18 @@ func server(localMode bool) http.Handler { v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, v1ProjectClaGroupRepo, signaturesRepo, usersService) v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, v1ProjectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) - v1RepositoriesService := repositories.NewService(repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo) - v2RepositoriesService := v2Repositories.NewService(repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsRepo) + v1RepositoriesService := repositories.NewService(gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo) + v2RepositoriesService := v2Repositories.NewService(gitV1Repository, gitV2Repository, v1ProjectClaGroupRepo, githubOrganizationsRepo) v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, v1ProjectClaGroupRepo) v1ApprovalListService := approval_list.NewService(approvalListRepo, v1ProjectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, v1CLAGroupRepo, signaturesRepo, emailTemplateService, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo) - githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo) + githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo) gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v1ProjectClaGroupRepo) - gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, repositoriesRepo, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) - v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsService) - autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) - v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, githubOrganizationsRepo, eventsService, autoEnableService, emailService) + gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, gitV1Repository, gitV2Repository, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) + v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo, githubOrganizationsService) + autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) + v2GithubActivityService := v2GithubActivity.NewService(gitV1Repository, githubOrganizationsRepo, eventsService, autoEnableService, emailService) v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, v1ProjectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService) diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 4d98e3f1e..d9c4ffeac 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -25,7 +25,6 @@ type configLookupResponse struct { // getSSMString is a generic routine to fetch the specified key value func getSSMString(ssmClient *ssm.SSM, key string) (string, error) { - // log.Debugf("Loading SSM parameter: %s", key) value, err := ssmClient.GetParameter(&ssm.GetParameterInput{ Name: aws.String(key), WithDecryption: aws.Bool(false), diff --git a/cla-backend-go/github/github_repository.go b/cla-backend-go/github/github_repository.go index 7025efd3d..5783570cb 100644 --- a/cla-backend-go/github/github_repository.go +++ b/cla-backend-go/github/github_repository.go @@ -29,7 +29,7 @@ func GetRepositoryByExternalID(ctx context.Context, installationID, id int64) (* } org, resp, err := client.Repositories.GetByID(ctx, id) if err != nil { - logging.Warnf("GetRepository %v failed. error = %s", id, err.Error()) + logging.Warnf("GitHubGetRepository %v failed. error = %s", id, err.Error()) if resp.StatusCode == 404 { return nil, ErrGithubRepositoryNotFound } diff --git a/cla-backend-go/github_organizations/mock.go b/cla-backend-go/github_organizations/mock.go index 12e1014e9..f68e6a778 100644 --- a/cla-backend-go/github_organizations/mock.go +++ b/cla-backend-go/github_organizations/mock.go @@ -40,7 +40,7 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { } // AddGitHubOrganization mocks base method -func (m *MockRepository) AddGitHubOrganization(arg0 context.Context, arg1, arg2 string, arg3 *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +func (m *MockRepository) AddGitHubOrganization(arg0 context.Context, arg1, arg2 string, arg3 *models.GithubCreateOrganization) (*models.GithubOrganization, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddGitHubOrganization", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*models.GithubOrganization) diff --git a/cla-backend-go/github_organizations/repository.go b/cla-backend-go/github_organizations/repository.go index 35c104c50..42d483a24 100644 --- a/cla-backend-go/github_organizations/repository.go +++ b/cla-backend-go/github_organizations/repository.go @@ -37,7 +37,7 @@ var ( // RepositoryInterface interface defines the functions for the github organizations data model type RepositoryInterface interface { - AddGitHubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) + AddGitHubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.GithubCreateOrganization) (*models.GithubOrganization, error) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*models.GithubOrganization, error) @@ -64,7 +64,7 @@ func NewRepository(awsSession *session.Session, stage string) Repository { } // AddGitHubOrganization add github organization logic -func (repo Repository) AddGitHubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +func (repo Repository) AddGitHubOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models.GithubCreateOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index bb865139e..630e5751a 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -21,7 +21,7 @@ import ( // ServiceInterface contains functions of GithubOrganizations service type ServiceInterface interface { - AddGitHubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) + AddGitHubOrganization(ctx context.Context, projectSFID string, input *models.GithubCreateOrganization) (*models.GithubOrganization, error) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) GetGitHubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) @@ -33,12 +33,12 @@ type ServiceInterface interface { // Service object/struct type Service struct { repo RepositoryInterface - ghRepository repositories.Repository + ghRepository repositories.RepositoryInterface claRepository projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo RepositoryInterface, ghRepository repositories.Repository, claRepository projects_cla_groups.Repository) Service { +func NewService(repo RepositoryInterface, ghRepository repositories.RepositoryInterface, claRepository projects_cla_groups.Repository) Service { return Service{ repo: repo, ghRepository: ghRepository, @@ -46,8 +46,8 @@ func NewService(repo RepositoryInterface, ghRepository repositories.Repository, } } -// AddGitHubOrganization adds the github organization for the specified project -func (s Service) AddGitHubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +// AddGitHubOrganization adds the GitHub organization for the specified project +func (s Service) AddGitHubOrganization(ctx context.Context, projectSFID string, input *models.GithubCreateOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -190,7 +190,7 @@ func (s Service) DeleteGitHubOrganization(ctx context.Context, projectSFID strin return projErr } - err := s.ghRepository.DisableRepositoriesOfGithubOrganization(ctx, parentProjectSFID, githubOrgName) + err := s.ghRepository.GitHubDisableRepositoriesOfOrganization(ctx, parentProjectSFID, githubOrgName) if err != nil { log.WithFields(f).Warnf("problem disabling repositories for github organizations, error: %+v", projErr) return err diff --git a/cla-backend-go/project/helpers.go b/cla-backend-go/project/helpers.go index e09f3f4eb..98369f2f2 100644 --- a/cla-backend-go/project/helpers.go +++ b/cla-backend-go/project/helpers.go @@ -81,7 +81,7 @@ func (s service) fillRepoInfo(ctx context.Context, project *models.ClaGroup) { go func() { defer wg.Done() var err error - ghrepos, err = s.repositoriesRepo.GetCLAGroupRepositoriesGroupByOrgs(ctx, project.ProjectID, true) + ghrepos, err = s.repositoriesRepo.GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx, project.ProjectID, true) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to get github repositories for cla group ID: %s", project.ProjectID) return diff --git a/cla-backend-go/project/repository.go b/cla-backend-go/project/repository.go index 1250821ab..fa718ba9d 100644 --- a/cla-backend-go/project/repository.go +++ b/cla-backend-go/project/repository.go @@ -60,7 +60,7 @@ type ProjectRepository interface { //nolint } // NewRepository creates instance of project repository -func NewRepository(awsSession *session.Session, stage string, ghRepo repositories.Repository, gerritRepo gerrits.Repository, projectClaGroupRepo projects_cla_groups.Repository) ProjectRepository { +func NewRepository(awsSession *session.Session, stage string, ghRepo repositories.RepositoryInterface, gerritRepo gerrits.Repository, projectClaGroupRepo projects_cla_groups.Repository) ProjectRepository { return &repo{ dynamoDBClient: dynamodb.New(awsSession), stage: stage, @@ -74,7 +74,7 @@ func NewRepository(awsSession *session.Session, stage string, ghRepo repositorie type repo struct { stage string dynamoDBClient *dynamodb.DynamoDB - ghRepo repositories.Repository + ghRepo repositories.RepositoryInterface gerritRepo gerrits.Repository projectClaGroupRepo projects_cla_groups.Repository claGroupTable string @@ -856,7 +856,7 @@ func (repo *repo) buildCLAGroupModel(ctx context.Context, dbModel DBProjectModel go func() { defer wg.Done() var err error - ghOrgs, err = repo.ghRepo.GetCLAGroupRepositoriesGroupByOrgs(ctx, dbModel.ProjectID, true) + ghOrgs, err = repo.ghRepo.GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx, dbModel.ProjectID, true) if err != nil { log.Warnf("buildPCLAGroupModel - unable to load GH organizations by project ID: %s, error: %+v", dbModel.ProjectID, err) diff --git a/cla-backend-go/project/service.go b/cla-backend-go/project/service.go index a861aa0ba..757019c57 100644 --- a/cla-backend-go/project/service.go +++ b/cla-backend-go/project/service.go @@ -43,14 +43,14 @@ type Service interface { // service type service struct { repo ProjectRepository - repositoriesRepo repositories.Repository + repositoriesRepo repositories.RepositoryInterface gerritRepo gerrits.Repository projectCLAGroupRepo projects_cla_groups.Repository usersRepo users.UserRepository } // NewService returns an instance of the project service -func NewService(projectRepo ProjectRepository, repositoriesRepo repositories.Repository, gerritRepo gerrits.Repository, projectCLAGroupRepo projects_cla_groups.Repository, usersRepo users.UserRepository) Service { +func NewService(projectRepo ProjectRepository, repositoriesRepo repositories.RepositoryInterface, gerritRepo gerrits.Repository, projectCLAGroupRepo projects_cla_groups.Repository, usersRepo users.UserRepository) Service { return service{ repo: projectRepo, repositoriesRepo: repositoriesRepo, diff --git a/cla-backend-go/repositories/constants.go b/cla-backend-go/repositories/constants.go new file mode 100644 index 000000000..a525fd0ad --- /dev/null +++ b/cla-backend-go/repositories/constants.go @@ -0,0 +1,49 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package repositories + +// RepositoryNameColumn constant +const RepositoryNameColumn = "repository_name" + +// RepositoryTypeColumn constant +const RepositoryTypeColumn = "repository_type" + +// RepositoryProjectIDColumn constant +const RepositoryProjectIDColumn = "project_sfid" + +// RepositoryCLAGroupIDColumn constant +const RepositoryCLAGroupIDColumn = "repository_project_id" + +// RepositoryEnabledColumn constant +const RepositoryEnabledColumn = "enabled" + +// RepositoryNoteColumn constant +const RepositoryNoteColumn = "note" + +// RepositoryDateModifiedColumn constant +const RepositoryDateModifiedColumn = "date_modified" + +// RepositoryEnabled constant +const RepositoryEnabled = "enabled" + +// RepositoryDisabled constant +const RepositoryDisabled = "disabled" + +// RepositoryProjectIndex constant +const RepositoryProjectIndex = "project-repository-index" + +// RepositoryExternalIDIndex constant +const RepositoryExternalIDIndex = "external-repository-index" + +// RepositoryProjectSFIDIndex constant +const RepositoryProjectSFIDIndex = "project-sfid-repository-index" + +// RepositoryProjectSFIDOrganizationNameIndex constant +const RepositoryProjectSFIDOrganizationNameIndex = "project-sfid-repository-organization-name-index" + +// RepositoryOrganizationNameIndex constant +const RepositoryOrganizationNameIndex = "repository-organization-name-index" + +// RepositoryNameIndex constant +const RepositoryNameIndex = "repository-name-index" diff --git a/cla-backend-go/repositories/mock/mock_repository.go b/cla-backend-go/repositories/mock/mock_repository.go index a41593d36..4fbdfe01a 100644 --- a/cla-backend-go/repositories/mock/mock_repository.go +++ b/cla-backend-go/repositories/mock/mock_repository.go @@ -40,9 +40,9 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { } // AddGithubRepository mocks base method -func (m *MockRepository) AddGithubRepository(ctx context.Context, externalProjectID, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { +func (m *MockRepository) GitHubAddRepository(ctx context.Context, externalProjectID, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddGithubRepository", ctx, externalProjectID, projectSFID, input) + ret := m.ctrl.Call(m, "GitHubAddRepository", ctx, externalProjectID, projectSFID, input) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -51,13 +51,13 @@ func (m *MockRepository) AddGithubRepository(ctx context.Context, externalProjec // AddGithubRepository indicates an expected call of AddGithubRepository func (mr *MockRepositoryMockRecorder) AddGithubRepository(ctx, externalProjectID, projectSFID, input interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubRepository", reflect.TypeOf((*MockRepository)(nil).AddGithubRepository), ctx, externalProjectID, projectSFID, input) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubAddRepository", reflect.TypeOf((*MockRepository)(nil).GitHubAddRepository), ctx, externalProjectID, projectSFID, input) } // UpdateGithubRepository mocks base method -func (m *MockRepository) UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { +func (m *MockRepository) GitHubUpdateRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateGithubRepository", ctx, repositoryID, input) + ret := m.ctrl.Call(m, "GitHubUpdateRepository", ctx, repositoryID, input) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -66,13 +66,13 @@ func (m *MockRepository) UpdateGithubRepository(ctx context.Context, repositoryI // UpdateGithubRepository indicates an expected call of UpdateGithubRepository func (mr *MockRepositoryMockRecorder) UpdateGithubRepository(ctx, repositoryID, input interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGithubRepository", reflect.TypeOf((*MockRepository)(nil).UpdateGithubRepository), ctx, repositoryID, input) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubUpdateRepository", reflect.TypeOf((*MockRepository)(nil).GitHubUpdateRepository), ctx, repositoryID, input) } // UpdateClaGroupID mocks base method -func (m *MockRepository) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { +func (m *MockRepository) GitHubUpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateClaGroupID", ctx, repositoryID, claGroupID) + ret := m.ctrl.Call(m, "GitHubUpdateClaGroupID", ctx, repositoryID, claGroupID) ret0, _ := ret[0].(error) return ret0 } @@ -80,13 +80,13 @@ func (m *MockRepository) UpdateClaGroupID(ctx context.Context, repositoryID, cla // UpdateClaGroupID indicates an expected call of UpdateClaGroupID func (mr *MockRepositoryMockRecorder) UpdateClaGroupID(ctx, repositoryID, claGroupID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClaGroupID", reflect.TypeOf((*MockRepository)(nil).UpdateClaGroupID), ctx, repositoryID, claGroupID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubUpdateClaGroupID", reflect.TypeOf((*MockRepository)(nil).GitHubUpdateClaGroupID), ctx, repositoryID, claGroupID) } -// EnableRepository mocks base method -func (m *MockRepository) EnableRepository(ctx context.Context, repositoryID string) error { +// GitHubEnableRepository mocks base method +func (m *MockRepository) GitHubEnableRepository(ctx context.Context, repositoryID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableRepository", ctx, repositoryID) + ret := m.ctrl.Call(m, "GitHubEnableRepository", ctx, repositoryID) ret0, _ := ret[0].(error) return ret0 } @@ -94,13 +94,13 @@ func (m *MockRepository) EnableRepository(ctx context.Context, repositoryID stri // EnableRepository indicates an expected call of EnableRepository func (mr *MockRepositoryMockRecorder) EnableRepository(ctx, repositoryID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepository", reflect.TypeOf((*MockRepository)(nil).EnableRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubEnableRepository", reflect.TypeOf((*MockRepository)(nil).GitHubEnableRepository), ctx, repositoryID) } // EnableRepositoryWithCLAGroupID mocks base method -func (m *MockRepository) EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { +func (m *MockRepository) GitHubEnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableRepositoryWithCLAGroupID", ctx, repositoryID, claGroupID) + ret := m.ctrl.Call(m, "GitHubEnableRepositoryWithCLAGroupID", ctx, repositoryID, claGroupID) ret0, _ := ret[0].(error) return ret0 } @@ -108,13 +108,13 @@ func (m *MockRepository) EnableRepositoryWithCLAGroupID(ctx context.Context, rep // EnableRepositoryWithCLAGroupID indicates an expected call of EnableRepositoryWithCLAGroupID func (mr *MockRepositoryMockRecorder) EnableRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepositoryWithCLAGroupID", reflect.TypeOf((*MockRepository)(nil).EnableRepositoryWithCLAGroupID), ctx, repositoryID, claGroupID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubEnableRepositoryWithCLAGroupID", reflect.TypeOf((*MockRepository)(nil).GitHubEnableRepositoryWithCLAGroupID), ctx, repositoryID, claGroupID) } -// DisableRepository mocks base method -func (m *MockRepository) DisableRepository(ctx context.Context, repositoryID string) error { +// GitHubDisableRepository mocks base method +func (m *MockRepository) GitHubDisableRepository(ctx context.Context, repositoryID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableRepository", ctx, repositoryID) + ret := m.ctrl.Call(m, "GitHubDisableRepository", ctx, repositoryID) ret0, _ := ret[0].(error) return ret0 } @@ -122,13 +122,13 @@ func (m *MockRepository) DisableRepository(ctx context.Context, repositoryID str // DisableRepository indicates an expected call of DisableRepository func (mr *MockRepositoryMockRecorder) DisableRepository(ctx, repositoryID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableRepository", reflect.TypeOf((*MockRepository)(nil).DisableRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubDisableRepository", reflect.TypeOf((*MockRepository)(nil).GitHubDisableRepository), ctx, repositoryID) } // DisableRepositoriesByProjectID mocks base method -func (m *MockRepository) DisableRepositoriesByProjectID(ctx context.Context, projectID string) error { +func (m *MockRepository) GitHubDisableRepositoriesByProjectID(ctx context.Context, projectID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableRepositoriesByProjectID", ctx, projectID) + ret := m.ctrl.Call(m, "GitHubDisableRepositoriesByProjectID", ctx, projectID) ret0, _ := ret[0].(error) return ret0 } @@ -136,13 +136,13 @@ func (m *MockRepository) DisableRepositoriesByProjectID(ctx context.Context, pro // DisableRepositoriesByProjectID indicates an expected call of DisableRepositoriesByProjectID func (mr *MockRepositoryMockRecorder) DisableRepositoriesByProjectID(ctx, projectID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableRepositoriesByProjectID", reflect.TypeOf((*MockRepository)(nil).DisableRepositoriesByProjectID), ctx, projectID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubDisableRepositoriesByProjectID", reflect.TypeOf((*MockRepository)(nil).GitHubDisableRepositoriesByProjectID), ctx, projectID) } // DisableRepositoriesOfGithubOrganization mocks base method -func (m *MockRepository) DisableRepositoriesOfGithubOrganization(ctx context.Context, externalProjectID, githubOrgName string) error { +func (m *MockRepository) GitHubDisableRepositoriesOfOrganization(ctx context.Context, externalProjectID, githubOrgName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableRepositoriesOfGithubOrganization", ctx, externalProjectID, githubOrgName) + ret := m.ctrl.Call(m, "GitHubDisableRepositoriesOfOrganization", ctx, externalProjectID, githubOrgName) ret0, _ := ret[0].(error) return ret0 } @@ -150,13 +150,13 @@ func (m *MockRepository) DisableRepositoriesOfGithubOrganization(ctx context.Con // DisableRepositoriesOfGithubOrganization indicates an expected call of DisableRepositoriesOfGithubOrganization func (mr *MockRepositoryMockRecorder) DisableRepositoriesOfGithubOrganization(ctx, externalProjectID, githubOrgName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableRepositoriesOfGithubOrganization", reflect.TypeOf((*MockRepository)(nil).DisableRepositoriesOfGithubOrganization), ctx, externalProjectID, githubOrgName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubDisableRepositoriesOfOrganization", reflect.TypeOf((*MockRepository)(nil).GitHubDisableRepositoriesOfOrganization), ctx, externalProjectID, githubOrgName) } -// GetRepository mocks base method -func (m *MockRepository) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { +// GitHubGetRepository mocks base method +func (m *MockRepository) GitHubGetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepository", ctx, repositoryID) + ret := m.ctrl.Call(m, "GitHubGetRepository", ctx, repositoryID) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -165,13 +165,13 @@ func (m *MockRepository) GetRepository(ctx context.Context, repositoryID string) // GetRepository indicates an expected call of GetRepository func (mr *MockRepositoryMockRecorder) GetRepository(ctx, repositoryID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepository", reflect.TypeOf((*MockRepository)(nil).GetRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepository", reflect.TypeOf((*MockRepository)(nil).GitHubGetRepository), ctx, repositoryID) } -// GetRepositoryByName mocks base method -func (m *MockRepository) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { +// GitHubGetRepositoryByName mocks base method +func (m *MockRepository) GitHubGetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoryByName", ctx, repositoryName) + ret := m.ctrl.Call(m, "GitHubGetRepositoryByName", ctx, repositoryName) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -180,13 +180,13 @@ func (m *MockRepository) GetRepositoryByName(ctx context.Context, repositoryName // GetRepositoryByName indicates an expected call of GetRepositoryByName func (mr *MockRepositoryMockRecorder) GetRepositoryByName(ctx, repositoryName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryByName", reflect.TypeOf((*MockRepository)(nil).GetRepositoryByName), ctx, repositoryName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoryByName", reflect.TypeOf((*MockRepository)(nil).GitHubGetRepositoryByName), ctx, repositoryName) } // GetRepositoryByGithubID mocks base method -func (m *MockRepository) GetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { +func (m *MockRepository) GitHubGetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoryByGithubID", ctx, externalID, enabled) + ret := m.ctrl.Call(m, "GitHubGetRepositoryByGithubID", ctx, externalID, enabled) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -195,13 +195,13 @@ func (m *MockRepository) GetRepositoryByGithubID(ctx context.Context, externalID // GetRepositoryByGithubID indicates an expected call of GetRepositoryByGithubID func (mr *MockRepositoryMockRecorder) GetRepositoryByGithubID(ctx, externalID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryByGithubID", reflect.TypeOf((*MockRepository)(nil).GetRepositoryByGithubID), ctx, externalID, enabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoryByGithubID", reflect.TypeOf((*MockRepository)(nil).GitHubGetRepositoryByGithubID), ctx, externalID, enabled) } // GetRepositoriesByCLAGroup mocks base method -func (m *MockRepository) GetRepositoriesByCLAGroup(ctx context.Context, claGroup string, enabled bool) ([]*models.GithubRepository, error) { +func (m *MockRepository) GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroup string, enabled bool) ([]*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoriesByCLAGroup", ctx, claGroup, enabled) + ret := m.ctrl.Call(m, "GitHubGetRepositoriesByCLAGroup", ctx, claGroup, enabled) ret0, _ := ret[0].([]*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -210,13 +210,13 @@ func (m *MockRepository) GetRepositoriesByCLAGroup(ctx context.Context, claGroup // GetRepositoriesByCLAGroup indicates an expected call of GetRepositoriesByCLAGroup func (mr *MockRepositoryMockRecorder) GetRepositoriesByCLAGroup(ctx, claGroup, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoriesByCLAGroup", reflect.TypeOf((*MockRepository)(nil).GetRepositoriesByCLAGroup), ctx, claGroup, enabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoriesByCLAGroup", reflect.TypeOf((*MockRepository)(nil).GitHubGetRepositoriesByCLAGroup), ctx, claGroup, enabled) } // GetRepositoriesByOrganizationName mocks base method -func (m *MockRepository) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { +func (m *MockRepository) GitHubGetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoriesByOrganizationName", ctx, gitHubOrgName) + ret := m.ctrl.Call(m, "GitHubGetRepositoriesByOrganizationName", ctx, gitHubOrgName) ret0, _ := ret[0].([]*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -225,13 +225,13 @@ func (m *MockRepository) GetRepositoriesByOrganizationName(ctx context.Context, // GetRepositoriesByOrganizationName indicates an expected call of GetRepositoriesByOrganizationName func (mr *MockRepositoryMockRecorder) GetRepositoriesByOrganizationName(ctx, gitHubOrgName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoriesByOrganizationName", reflect.TypeOf((*MockRepository)(nil).GetRepositoriesByOrganizationName), ctx, gitHubOrgName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoriesByOrganizationName", reflect.TypeOf((*MockRepository)(nil).GitHubGetRepositoriesByOrganizationName), ctx, gitHubOrgName) } // GetCLAGroupRepositoriesGroupByOrgs mocks base method -func (m *MockRepository) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) { +func (m *MockRepository) GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCLAGroupRepositoriesGroupByOrgs", ctx, projectID, enabled) + ret := m.ctrl.Call(m, "GitHubGetCLAGroupRepositoriesGroupByOrgs", ctx, projectID, enabled) ret0, _ := ret[0].([]*models.GithubRepositoriesGroupByOrgs) ret1, _ := ret[1].(error) return ret0, ret1 @@ -240,14 +240,14 @@ func (m *MockRepository) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, // GetCLAGroupRepositoriesGroupByOrgs indicates an expected call of GetCLAGroupRepositoriesGroupByOrgs func (mr *MockRepositoryMockRecorder) GetCLAGroupRepositoriesGroupByOrgs(ctx, projectID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCLAGroupRepositoriesGroupByOrgs", reflect.TypeOf((*MockRepository)(nil).GetCLAGroupRepositoriesGroupByOrgs), ctx, projectID, enabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetCLAGroupRepositoriesGroupByOrgs", reflect.TypeOf((*MockRepository)(nil).GitHubGetCLAGroupRepositoriesGroupByOrgs), ctx, projectID, enabled) } -// ListProjectRepositories mocks base method -func (m *MockRepository) ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { +// GitHubListProjectRepositories mocks base method +func (m *MockRepository) GitHubListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.GithubListRepositories, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListProjectRepositories", ctx, projectSFID, enabled) - ret0, _ := ret[0].(*models.ListGithubRepositories) + ret := m.ctrl.Call(m, "GitHubListProjectRepositories", ctx, projectSFID, enabled) + ret0, _ := ret[0].(*models.GithubListRepositories) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -255,5 +255,5 @@ func (m *MockRepository) ListProjectRepositories(ctx context.Context, projectSFI // ListProjectRepositories indicates an expected call of ListProjectRepositories func (mr *MockRepositoryMockRecorder) ListProjectRepositories(ctx, projectSFID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectRepositories", reflect.TypeOf((*MockRepository)(nil).ListProjectRepositories), ctx, projectSFID, enabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubListProjectRepositories", reflect.TypeOf((*MockRepository)(nil).GitHubListProjectRepositories), ctx, projectSFID, enabled) } diff --git a/cla-backend-go/repositories/mock/mock_service.go b/cla-backend-go/repositories/mock/mock_service.go index 5363e52a2..648d30f91 100644 --- a/cla-backend-go/repositories/mock/mock_service.go +++ b/cla-backend-go/repositories/mock/mock_service.go @@ -42,7 +42,7 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder { // AddGithubRepository mocks base method func (m *MockService) AddGithubRepository(ctx context.Context, externalProjectID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddGithubRepository", ctx, externalProjectID, input) + ret := m.ctrl.Call(m, "GitHubAddRepository", ctx, externalProjectID, input) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -51,13 +51,13 @@ func (m *MockService) AddGithubRepository(ctx context.Context, externalProjectID // AddGithubRepository indicates an expected call of AddGithubRepository func (mr *MockServiceMockRecorder) AddGithubRepository(ctx, externalProjectID, input interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGithubRepository", reflect.TypeOf((*MockService)(nil).AddGithubRepository), ctx, externalProjectID, input) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubAddRepository", reflect.TypeOf((*MockService)(nil).AddGithubRepository), ctx, externalProjectID, input) } // EnableRepository mocks base method func (m *MockService) EnableRepository(ctx context.Context, repositoryID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableRepository", ctx, repositoryID) + ret := m.ctrl.Call(m, "GitHubEnableRepository", ctx, repositoryID) ret0, _ := ret[0].(error) return ret0 } @@ -65,13 +65,13 @@ func (m *MockService) EnableRepository(ctx context.Context, repositoryID string) // EnableRepository indicates an expected call of EnableRepository func (mr *MockServiceMockRecorder) EnableRepository(ctx, repositoryID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepository", reflect.TypeOf((*MockService)(nil).EnableRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubEnableRepository", reflect.TypeOf((*MockService)(nil).EnableRepository), ctx, repositoryID) } // EnableRepositoryWithCLAGroupID mocks base method func (m *MockService) EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EnableRepositoryWithCLAGroupID", ctx, repositoryID, claGroupID) + ret := m.ctrl.Call(m, "GitHubEnableRepositoryWithCLAGroupID", ctx, repositoryID, claGroupID) ret0, _ := ret[0].(error) return ret0 } @@ -79,13 +79,13 @@ func (m *MockService) EnableRepositoryWithCLAGroupID(ctx context.Context, reposi // EnableRepositoryWithCLAGroupID indicates an expected call of EnableRepositoryWithCLAGroupID func (mr *MockServiceMockRecorder) EnableRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableRepositoryWithCLAGroupID", reflect.TypeOf((*MockService)(nil).EnableRepositoryWithCLAGroupID), ctx, repositoryID, claGroupID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubEnableRepositoryWithCLAGroupID", reflect.TypeOf((*MockService)(nil).EnableRepositoryWithCLAGroupID), ctx, repositoryID, claGroupID) } // DisableRepository mocks base method func (m *MockService) DisableRepository(ctx context.Context, repositoryID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableRepository", ctx, repositoryID) + ret := m.ctrl.Call(m, "GitHubDisableRepository", ctx, repositoryID) ret0, _ := ret[0].(error) return ret0 } @@ -93,13 +93,13 @@ func (m *MockService) DisableRepository(ctx context.Context, repositoryID string // DisableRepository indicates an expected call of DisableRepository func (mr *MockServiceMockRecorder) DisableRepository(ctx, repositoryID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableRepository", reflect.TypeOf((*MockService)(nil).DisableRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubDisableRepository", reflect.TypeOf((*MockService)(nil).DisableRepository), ctx, repositoryID) } // UpdateClaGroupID mocks base method func (m *MockService) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateClaGroupID", ctx, repositoryID, claGroupID) + ret := m.ctrl.Call(m, "GitHubUpdateClaGroupID", ctx, repositoryID, claGroupID) ret0, _ := ret[0].(error) return ret0 } @@ -107,14 +107,14 @@ func (m *MockService) UpdateClaGroupID(ctx context.Context, repositoryID, claGro // UpdateClaGroupID indicates an expected call of UpdateClaGroupID func (mr *MockServiceMockRecorder) UpdateClaGroupID(ctx, repositoryID, claGroupID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClaGroupID", reflect.TypeOf((*MockService)(nil).UpdateClaGroupID), ctx, repositoryID, claGroupID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubUpdateClaGroupID", reflect.TypeOf((*MockService)(nil).UpdateClaGroupID), ctx, repositoryID, claGroupID) } // ListProjectRepositories mocks base method -func (m *MockService) ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.ListGithubRepositories, error) { +func (m *MockService) ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.GithubListRepositories, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListProjectRepositories", ctx, externalProjectID, enabled) - ret0, _ := ret[0].(*models.ListGithubRepositories) + ret := m.ctrl.Call(m, "GitHubListProjectRepositories", ctx, externalProjectID, enabled) + ret0, _ := ret[0].(*models.GithubListRepositories) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -122,13 +122,13 @@ func (m *MockService) ListProjectRepositories(ctx context.Context, externalProje // ListProjectRepositories indicates an expected call of ListProjectRepositories func (mr *MockServiceMockRecorder) ListProjectRepositories(ctx, externalProjectID, enabled interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectRepositories", reflect.TypeOf((*MockService)(nil).ListProjectRepositories), ctx, externalProjectID, enabled) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubListProjectRepositories", reflect.TypeOf((*MockService)(nil).ListProjectRepositories), ctx, externalProjectID, enabled) } // GetRepository mocks base method func (m *MockService) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepository", ctx, repositoryID) + ret := m.ctrl.Call(m, "GitHubGetRepository", ctx, repositoryID) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -137,14 +137,14 @@ func (m *MockService) GetRepository(ctx context.Context, repositoryID string) (* // GetRepository indicates an expected call of GetRepository func (mr *MockServiceMockRecorder) GetRepository(ctx, repositoryID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepository", reflect.TypeOf((*MockService)(nil).GetRepository), ctx, repositoryID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepository", reflect.TypeOf((*MockService)(nil).GetRepository), ctx, repositoryID) } // GetRepositoryByProjectSFID mocks base method -func (m *MockService) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { +func (m *MockService) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.GithubListRepositories, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRepositoryByProjectSFID", ctx, projectSFID, enabled) - ret0, _ := ret[0].(*models.ListGithubRepositories) + ret0, _ := ret[0].(*models.GithubListRepositories) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -158,7 +158,7 @@ func (mr *MockServiceMockRecorder) GetRepositoryByProjectSFID(ctx, projectSFID, // GetRepositoryByName mocks base method func (m *MockService) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoryByName", ctx, repositoryName) + ret := m.ctrl.Call(m, "GitHubGetRepositoryByName", ctx, repositoryName) ret0, _ := ret[0].(*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -167,13 +167,13 @@ func (m *MockService) GetRepositoryByName(ctx context.Context, repositoryName st // GetRepositoryByName indicates an expected call of GetRepositoryByName func (mr *MockServiceMockRecorder) GetRepositoryByName(ctx, repositoryName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoryByName", reflect.TypeOf((*MockService)(nil).GetRepositoryByName), ctx, repositoryName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoryByName", reflect.TypeOf((*MockService)(nil).GetRepositoryByName), ctx, repositoryName) } // DisableRepositoriesByProjectID mocks base method func (m *MockService) DisableRepositoriesByProjectID(ctx context.Context, projectID string) (int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DisableRepositoriesByProjectID", ctx, projectID) + ret := m.ctrl.Call(m, "GitHubDisableRepositoriesByProjectID", ctx, projectID) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 @@ -182,13 +182,13 @@ func (m *MockService) DisableRepositoriesByProjectID(ctx context.Context, projec // DisableRepositoriesByProjectID indicates an expected call of DisableRepositoriesByProjectID func (mr *MockServiceMockRecorder) DisableRepositoriesByProjectID(ctx, projectID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableRepositoriesByProjectID", reflect.TypeOf((*MockService)(nil).DisableRepositoriesByProjectID), ctx, projectID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubDisableRepositoriesByProjectID", reflect.TypeOf((*MockService)(nil).DisableRepositoriesByProjectID), ctx, projectID) } // GetRepositoriesByCLAGroup mocks base method func (m *MockService) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoriesByCLAGroup", ctx, claGroupID) + ret := m.ctrl.Call(m, "GitHubGetRepositoriesByCLAGroup", ctx, claGroupID) ret0, _ := ret[0].([]*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -197,13 +197,13 @@ func (m *MockService) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID // GetRepositoriesByCLAGroup indicates an expected call of GetRepositoriesByCLAGroup func (mr *MockServiceMockRecorder) GetRepositoriesByCLAGroup(ctx, claGroupID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoriesByCLAGroup", reflect.TypeOf((*MockService)(nil).GetRepositoriesByCLAGroup), ctx, claGroupID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoriesByCLAGroup", reflect.TypeOf((*MockService)(nil).GetRepositoriesByCLAGroup), ctx, claGroupID) } // GetRepositoriesByOrganizationName mocks base method func (m *MockService) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepositoriesByOrganizationName", ctx, gitHubOrgName) + ret := m.ctrl.Call(m, "GitHubGetRepositoriesByOrganizationName", ctx, gitHubOrgName) ret0, _ := ret[0].([]*models.GithubRepository) ret1, _ := ret[1].(error) return ret0, ret1 @@ -212,7 +212,7 @@ func (m *MockService) GetRepositoriesByOrganizationName(ctx context.Context, git // GetRepositoriesByOrganizationName indicates an expected call of GetRepositoriesByOrganizationName func (mr *MockServiceMockRecorder) GetRepositoriesByOrganizationName(ctx, gitHubOrgName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepositoriesByOrganizationName", reflect.TypeOf((*MockService)(nil).GetRepositoriesByOrganizationName), ctx, gitHubOrgName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitHubGetRepositoriesByOrganizationName", reflect.TypeOf((*MockService)(nil).GetRepositoriesByOrganizationName), ctx, gitHubOrgName) } // MockGithubOrgRepo is a mock of GithubOrgRepo interface diff --git a/cla-backend-go/repositories/models.go b/cla-backend-go/repositories/models.go index 38398f3a4..ddde2b9e7 100644 --- a/cla-backend-go/repositories/models.go +++ b/cla-backend-go/repositories/models.go @@ -3,7 +3,12 @@ package repositories -import "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" +import ( + "strconv" + + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) // RepositoryDBModel represent repositories table type RepositoryDBModel struct { @@ -13,7 +18,7 @@ type RepositoryDBModel struct { RepositoryID string `dynamodbav:"repository_id" json:"repository_id,omitempty"` RepositoryName string `dynamodbav:"repository_name" json:"repository_name,omitempty"` RepositoryOrganizationName string `dynamodbav:"repository_organization_name" json:"repository_organization_name,omitempty"` - RepositoryProjectID string `dynamodbav:"repository_project_id" json:"repository_project_id,omitempty"` + RepositoryCLAGroupID string `dynamodbav:"repository_project_id" json:"repository_project_id,omitempty"` RepositorySfdcID string `dynamodbav:"repository_sfdc_id" json:"repository_sfdc_id,omitempty"` RepositoryType string `dynamodbav:"repository_type" json:"repository_type,omitempty"` RepositoryURL string `dynamodbav:"repository_url" json:"repository_url,omitempty"` @@ -26,25 +31,31 @@ type RepositoryDBModel struct { func convertModels(dbModels []*RepositoryDBModel) []*models.GithubRepository { var responseModels []*models.GithubRepository for _, dbModel := range dbModels { - responseModels = append(responseModels, dbModel.toModel()) + responseModels = append(responseModels, dbModel.ToGitHubModel()) } return responseModels } -func (gr *RepositoryDBModel) toModel() *models.GithubRepository { +// ToGitHubModel returns the database model to a GitHub repository model suitable for marshalling to the client +func (gr *RepositoryDBModel) ToGitHubModel() *models.GithubRepository { + gitLabExternalID, err := strconv.ParseInt(gr.RepositoryExternalID, 10, 64) + if err != nil { + log.WithError(err).Warnf("unable to convert repository external ID to an int64 value: %s", gr.RepositoryExternalID) + return nil + } + return &models.GithubRepository{ DateCreated: gr.DateCreated, DateModified: gr.DateModified, - RepositoryExternalID: gr.RepositoryExternalID, + RepositoryExternalID: gitLabExternalID, RepositoryID: gr.RepositoryID, RepositoryName: gr.RepositoryName, RepositoryOrganizationName: gr.RepositoryOrganizationName, - RepositoryProjectID: gr.RepositoryProjectID, - RepositorySfdcID: gr.RepositorySfdcID, + RepositoryProjectSfid: gr.RepositorySfdcID, RepositoryType: gr.RepositoryType, RepositoryURL: gr.RepositoryURL, - ProjectSFID: gr.ProjectSFID, + RepositoryClaGroupID: gr.RepositoryCLAGroupID, Enabled: gr.Enabled, Note: gr.Note, Version: gr.Version, diff --git a/cla-backend-go/repositories/repository.go b/cla-backend-go/repositories/repository.go index 33212de84..3289d2a1c 100644 --- a/cla-backend-go/repositories/repository.go +++ b/cla-backend-go/repositories/repository.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" "github.com/sirupsen/logrus" @@ -30,60 +31,48 @@ import ( // index const ( repositoryEnabledColumn = "enabled" - - // RepositoryEnabled flag - RepositoryEnabled = "enabled" - - // RepositoryDisabled flag - RepositoryDisabled = "disabled" - - ProjectRepositoryIndex = "project-repository-index" - SFDCRepositoryIndex = "sfdc-repository-index" - ExternalRepositoryIndex = "external-repository-index" - ProjectSFIDRepositoryOrganizationNameIndex = "project-sfid-repository-organization-name-index" - RepositoryOrganizationNameIndex = "repository-organization-name-index" - RepositoryNameIndex = "repository-name-index" ) // ErrRepositoryDoesNotExist ... var ErrRepositoryDoesNotExist = errors.New("repository does not exist") -// Repository defines functions of V3Repositories -type Repository interface { - AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) - UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) - UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error - EnableRepository(ctx context.Context, repositoryID string) error - EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error - DisableRepository(ctx context.Context, repositoryID string) error - DisableRepositoriesByProjectID(ctx context.Context, projectID string) error - DisableRepositoriesOfGithubOrganization(ctx context.Context, externalProjectID, githubOrgName string) error - GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) - GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) - GetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) - GetRepositoriesByCLAGroup(ctx context.Context, claGroup string, enabled bool) ([]*models.GithubRepository, error) - GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) - GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) - ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) +// RepositoryInterface contains functions of the repositories service +type RepositoryInterface interface { + GitHubAddRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) + GitHubUpdateRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) + GitHubUpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error + GitHubEnableRepository(ctx context.Context, repositoryID string) error + GitHubEnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error + GitHubDisableRepository(ctx context.Context, repositoryID string) error + GitHubDisableRepositoriesByProjectID(ctx context.Context, projectID string) error + GitHubDisableRepositoriesOfOrganization(ctx context.Context, externalProjectID, githubOrgName string) error + GitHubGetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) + GitHubGetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) + GitHubGetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) + GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroup string, enabled bool) ([]*models.GithubRepository, error) + GitHubGetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) + GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) + GitHubListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.GithubListRepositories, error) } // NewRepository create new Repository -func NewRepository(awsSession *session.Session, stage string) Repository { - return &repo{ +func NewRepository(awsSession *session.Session, stage string) *Repository { + return &Repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), repositoryTableName: fmt.Sprintf("cla-%s-repositories", stage), } } -type repo struct { +// Repository structure +type Repository struct { stage string dynamoDBClient *dynamodb.DynamoDB repositoryTableName string } -// AddGithubRepository adds the specified repository -func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { +// GitHubAddRepository adds the specified repository +func (r *Repository) GitHubAddRepository(ctx context.Context, externalProjectID string, projectSFID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { f := logrus.Fields{ "functionName": "v1.repositories.repository.AddGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -96,7 +85,7 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, } // Check first to see if the repository already exists - _, err := r.GetRepositoryByGithubID(ctx, utils.StringValue(input.RepositoryExternalID), true) + _, err := r.GitHubGetRepositoryByGithubID(ctx, utils.StringValue(input.RepositoryExternalID), true) if err != nil { // Expecting Not found - no issue if not found - all other error we throw if _, ok := err.(*utils.GitHubRepositoryNotFound); !ok { @@ -119,7 +108,7 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, RepositoryID: repoID.String(), RepositoryName: utils.StringValue(input.RepositoryName), RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), - RepositoryProjectID: utils.StringValue(input.RepositoryProjectID), + RepositoryCLAGroupID: utils.StringValue(input.RepositoryProjectID), RepositorySfdcID: externalProjectID, RepositoryType: utils.StringValue(input.RepositoryType), RepositoryURL: utils.StringValue(input.RepositoryURL), @@ -144,11 +133,11 @@ func (r repo) AddGithubRepository(ctx context.Context, externalProjectID string, return nil, err } - return repository.toModel(), nil + return repository.ToGitHubModel(), nil } -// UpdateGithubRepository updates the repository record for given ID -func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { +// GitHubUpdateRepository updates the repository record for given ID +func (r *Repository) GitHubUpdateRepository(ctx context.Context, repositoryID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { externalID := utils.StringValue(input.RepositoryExternalID) repositoryName := utils.StringValue(input.RepositoryName) @@ -170,14 +159,14 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, log.WithFields(f).Debugf("updating CombinedRepository : %s... ", repositoryID) - repoModel, repoErr := r.GetRepository(ctx, repositoryID) + repoModel, repoErr := r.GitHubGetRepository(ctx, repositoryID) if repoErr != nil { log.WithFields(f).Warnf("update error locating the repository ID : %s , error: %+v ", repositoryID, repoErr) return nil, repoErr } if repoModel == nil { - log.WithFields(f).Warnf("CombinedRepository does not exist for repo: %s ", repositoryID) + log.WithFields(f).Warnf("CombinedRepository does not exist for *Repository: %s ", repositoryID) return nil, ErrRepositoryDoesNotExist } @@ -185,7 +174,9 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, expressionAttributeValues := map[string]*dynamodb.AttributeValue{} updateExpression := "SET " - if externalID != "" && repoModel.RepositoryExternalID != externalID { + // Convert the numeric value to a string for the DB + externalIDStr := strconv.FormatInt(repoModel.RepositoryExternalID, 10) + if externalID != "" && externalIDStr != externalID { log.WithFields(f).Debugf("adding externalID : %s ", externalID) expressionAttributeNames["#E"] = aws.String("repository_external_id") expressionAttributeValues[":e"] = &dynamodb.AttributeValue{S: aws.String(externalID)} @@ -271,30 +262,31 @@ func (r *repo) UpdateGithubRepository(ctx context.Context, repositoryID string, return nil, updateErr } - return r.GetRepository(ctx, repositoryID) + return r.GitHubGetRepository(ctx, repositoryID) } -// UpdateClaGroupID updates the claGroupID of the repository -func (r *repo) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { +// GitHubUpdateClaGroupID updates the claGroupID of the repository +func (r *Repository) GitHubUpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { return r.setClaGroupIDGithubRepository(ctx, repositoryID, claGroupID) } -// EnableRepository enables the repository entry -func (r *repo) EnableRepository(ctx context.Context, repositoryID string) error { +// GitHubEnableRepository enables the repository entry +func (r *Repository) GitHubEnableRepository(ctx context.Context, repositoryID string) error { return r.enableGithubRepository(ctx, repositoryID) } -// EnableRepositoryWithCLAGroupID enables the repository entry with the specified CLA Group ID -func (r *repo) EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { +// GitHubEnableRepositoryWithCLAGroupID enables the repository entry with the specified CLA Group ID +func (r *Repository) GitHubEnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { return r.enableGithubRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID) } -// DisableRepository disables the repository entry (we don't delete) -func (r *repo) DisableRepository(ctx context.Context, repositoryID string) error { +// GitHubDisableRepository disables the repository entry (we don't delete) +func (r *Repository) GitHubDisableRepository(ctx context.Context, repositoryID string) error { return r.disableGithubRepository(ctx, repositoryID) } -func (r *repo) DisableRepositoriesByProjectID(ctx context.Context, projectID string) error { +// GitHubDisableRepositoriesByProjectID disables the repository by the project ID +func (r *Repository) GitHubDisableRepositoriesByProjectID(ctx context.Context, projectID string) error { repoModels, err := r.getProjectRepositories(ctx, projectID, true) if err != nil { return err @@ -302,7 +294,7 @@ func (r *repo) DisableRepositoriesByProjectID(ctx context.Context, projectID str // For each model... for _, repoModel := range repoModels { - disableErr := r.DisableRepository(ctx, repoModel.RepositoryID) + disableErr := r.GitHubDisableRepository(ctx, repoModel.RepositoryID) if disableErr != nil { return disableErr } @@ -311,14 +303,14 @@ func (r *repo) DisableRepositoriesByProjectID(ctx context.Context, projectID str return nil } -// DisableRepositoriesOfGithubOrganization disables the repositories under the GitHub organization -func (r repo) DisableRepositoriesOfGithubOrganization(ctx context.Context, externalProjectID, githubOrgName string) error { +// GitHubDisableRepositoriesOfOrganization disables the repositories under the GitHub organization +func (r *Repository) GitHubDisableRepositoriesOfOrganization(ctx context.Context, projectSFID, githubOrgName string) error { repoModels, err := r.getRepositoriesByGithubOrg(ctx, githubOrgName) if err != nil { return err } for _, repoModel := range repoModels { - if repoModel.RepositoryExternalID == externalProjectID || repoModel.RepositorySfdcID == externalProjectID { + if repoModel.RepositoryProjectSfid == projectSFID { err = r.disableGithubRepository(ctx, repoModel.RepositoryID) if err != nil { return err @@ -328,10 +320,10 @@ func (r repo) DisableRepositoriesOfGithubOrganization(ctx context.Context, exter return nil } -// GetRepository by repository id -func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { +// GitHubGetRepository by repository id +func (r *Repository) GitHubGetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "v1.repositories.repository.GetRepository", + "functionName": "v1.repositories.repository.GitHubGetRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryID": repositoryID, } @@ -362,13 +354,13 @@ func (r *repo) GetRepository(ctx context.Context, repositoryID string) (*models. return nil, err } - return out.toModel(), nil + return out.ToGitHubModel(), nil } -// GetRepositoryByName fetches the repository by repository name -func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { +// GitHubGetRepositoryByName fetches the repository by repository name +func (r *Repository) GitHubGetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "v1.repositories.repository.GetRepositoryByName", + "functionName": "v1.repositories.repository.GitHubGetRepositoryByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "repositoryName": repositoryName, } @@ -412,15 +404,15 @@ func (r *repo) GetRepositoryByName(ctx context.Context, repositoryName string) ( return nil, err } - if len(repositories) > 0 { + if len(repositories) > 1 { log.WithFields(f).Warn("multiple repositories records with the same repository name") } - return repositories[0].toModel(), nil + return repositories[0].ToGitHubModel(), nil } -// GetRepositoryByCLAGroup gets the list of repositories based on the CLA Group ID -func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) ([]*models.GithubRepository, error) { +// GitHubGetRepositoriesByCLAGroup gets the list of repositories based on the CLA Group ID +func (r *Repository) GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) ([]*models.GithubRepository, error) { f := logrus.Fields{ "functionName": "v1.repositories.repository.GetRepositoryByCLAGroup", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -444,7 +436,7 @@ func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(r.repositoryTableName), - IndexName: aws.String(ProjectRepositoryIndex), + IndexName: aws.String(RepositoryProjectIndex), } results, err := r.dynamoDBClient.Query(queryInput) @@ -471,9 +463,10 @@ func (r *repo) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, return convertModels(repositories), nil } -func (r *repo) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { +// GitHubGetRepositoriesByOrganizationName gets the repositories by organization name +func (r *Repository) GitHubGetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "v1.repositories.repository.GetRepositoriesByOrganizationName", + "functionName": "v1.repositories.repository.GitHubGetRepositoriesByOrganizationName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitHubOrgName": gitHubOrgName, } @@ -522,8 +515,8 @@ func (r *repo) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgN return convertModels(repositories), nil } -// GetCLAGroupRepositoriesGroupByOrgs returns a list of GH organizations by CLA Group - enabled flag indicates that we search the enabled repositories list -func (r repo) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) { +// GitHubGetCLAGroupRepositoriesGroupByOrgs returns a list of GH organizations by CLA Group - enabled flag indicates that we search the enabled repositories list +func (r *Repository) GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepositoriesGroupByOrgs, error) { out := make([]*models.GithubRepositoriesGroupByOrgs, 0) outMap := make(map[string]*models.GithubRepositoriesGroupByOrgs) ghrepos, err := r.getProjectRepositories(ctx, projectID, enabled) @@ -544,16 +537,16 @@ func (r repo) GetCLAGroupRepositoriesGroupByOrgs(ctx context.Context, projectID return out, nil } -// List github repositories of project by external/salesforce project id -func (r repo) ListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { +// GitHubListProjectRepositories lists GitHub repositories of project by external/salesforce project id +func (r *Repository) GitHubListProjectRepositories(ctx context.Context, projectSFID string, enabled *bool) (*models.GithubListRepositories, error) { f := logrus.Fields{ - "functionName": "v1.repositories.repository.ListProjectRepositories", + "functionName": "v1.repositories.repository.GitHubListProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "enabled": utils.BoolValue(enabled), } - out := &models.ListGithubRepositories{ + out := &models.GithubListRepositories{ List: make([]*models.GithubRepository, 0), } @@ -576,7 +569,7 @@ func (r repo) ListProjectRepositories(ctx context.Context, projectSFID string, e ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(r.repositoryTableName), - IndexName: aws.String(ProjectSFIDRepositoryOrganizationNameIndex), + IndexName: aws.String(RepositoryProjectSFIDOrganizationNameIndex), } results, err := r.dynamoDBClient.Query(queryInput) @@ -593,13 +586,13 @@ func (r repo) ListProjectRepositories(ctx context.Context, projectSFID string, e return nil, err } for _, gr := range result { - out.List = append(out.List, gr.toModel()) + out.List = append(out.List, gr.ToGitHubModel()) } return out, nil } // getProjectRepositories returns an array of GH repositories for the specified project ID -func (r repo) getProjectRepositories(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepository, error) { +func (r *Repository) getProjectRepositories(ctx context.Context, projectID string, enabled bool) ([]*models.GithubRepository, error) { f := logrus.Fields{ "functionName": "v1.repositories.repository.getProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -624,7 +617,7 @@ func (r repo) getProjectRepositories(ctx context.Context, projectID string, enab ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(r.repositoryTableName), - IndexName: aws.String(ProjectRepositoryIndex), + IndexName: aws.String(RepositoryProjectIndex), } results, err := r.dynamoDBClient.Query(queryInput) @@ -641,13 +634,13 @@ func (r repo) getProjectRepositories(ctx context.Context, projectID string, enab return nil, err } for _, gr := range result { - out = append(out, gr.toModel()) + out = append(out, gr.ToGitHubModel()) } return out, nil } // getRepositoriesByGithubOrg returns an array of GH repositories for the specified project ID -func (r repo) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName string) ([]*models.GithubRepository, error) { +func (r *Repository) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName string) ([]*models.GithubRepository, error) { f := logrus.Fields{ "functionName": "v1.repositories.repository.getRepositoriesByGitHubOrg", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -686,13 +679,13 @@ func (r repo) getRepositoriesByGithubOrg(ctx context.Context, githubOrgName stri return nil, err } for _, gr := range result { - out = append(out, gr.toModel()) + out = append(out, gr.ToGitHubModel()) } return out, nil } -// GetRepositoryByGithubID fetches the repository model by its external github id -func (r repo) GetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { +// GitHubGetRepositoryByGithubID fetches the repository model by its external GitHub id +func (r *Repository) GitHubGetRepositoryByGithubID(ctx context.Context, externalID string, enabled bool) (*models.GithubRepository, error) { f := logrus.Fields{ "functionName": "v1.repositories.repository.GetRepositoryByGitHubID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -719,7 +712,7 @@ func (r repo) GetRepositoryByGithubID(ctx context.Context, externalID string, en ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(r.repositoryTableName), - IndexName: aws.String(ExternalRepositoryIndex), + IndexName: aws.String(RepositoryExternalIDIndex), } results, err := r.dynamoDBClient.Query(queryInput) @@ -739,23 +732,23 @@ func (r repo) GetRepositoryByGithubID(ctx context.Context, externalID string, en return nil, err } - return result.toModel(), nil + return result.ToGitHubModel(), nil } -func (r repo) enableGithubRepository(ctx context.Context, repositoryID string) error { +func (r *Repository) enableGithubRepository(ctx context.Context, repositoryID string) error { return r.setEnabledGithubRepository(ctx, repositoryID, true) } -func (r repo) enableGithubRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { +func (r *Repository) enableGithubRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { return r.setEnabledGithubRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID, true) } -func (r repo) disableGithubRepository(ctx context.Context, repositoryID string) error { +func (r *Repository) disableGithubRepository(ctx context.Context, repositoryID string) error { return r.setEnabledGithubRepository(ctx, repositoryID, false) } // setEnabledGithubRepository updates the existing repository record by setting the enabled flag to false -func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID string, enabled bool) error { +func (r *Repository) setEnabledGithubRepository(ctx context.Context, repositoryID string, enabled bool) error { f := logrus.Fields{ "functionName": "v1.repositories.repository.setEnabledGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -763,8 +756,8 @@ func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID strin "enabled": enabled, } - // Load the existing model - need to fetch the old note value, if available - existingModel, getErr := r.GetRepository(ctx, repositoryID) + // Load the existing model - need to fetch the old values, if available + existingModel, getErr := r.GitHubGetRepository(ctx, repositoryID) if getErr != nil { log.WithFields(f).WithError(getErr).Warn("unable to load repository by repository id") return getErr @@ -814,7 +807,7 @@ func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID strin if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { case dynamodb.ErrCodeConditionalCheckFailedException: - return errors.New("github repository entry does not exist or repository_sfdc_id does not match with specified project id") + return errors.New("github repository entry does not exist or *Repositorysitory_sfdc_id does not match with specified project id") } } log.WithFields(f).WithError(err).Warn("error disabling github repository") @@ -825,7 +818,7 @@ func (r repo) setEnabledGithubRepository(ctx context.Context, repositoryID strin } // setEnabledGithubRepositoryWithCLAGroupID updates the existing repository record by setting the enabled flag to false -func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string, enabled bool) error { +func (r *Repository) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string, enabled bool) error { f := logrus.Fields{ "functionName": "v1.repositories.repository.setEnabledGitHubRepositoryWithCLAGroupID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -835,7 +828,7 @@ func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repo } // Load the existing model - need to fetch the old note value, if available - existingModel, getErr := r.GetRepository(ctx, repositoryID) + existingModel, getErr := r.GitHubGetRepository(ctx, repositoryID) if getErr != nil { log.WithFields(f).WithError(getErr).Warn("unable to load repository by repository id") return getErr @@ -889,7 +882,7 @@ func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repo if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { case dynamodb.ErrCodeConditionalCheckFailedException: - return errors.New("github repository entry does not exist or repository_sfdc_id does not match with specified project id") + return errors.New("github repository entry does not exist or *Repositorysitory_sfdc_id does not match with specified project id") } } log.WithFields(f).WithError(err).Warn("error disabling github repository") @@ -900,7 +893,7 @@ func (r repo) setEnabledGithubRepositoryWithCLAGroupID(ctx context.Context, repo } // setEnabledGithubRepository updates the existing repository record by setting the enabled flag to false -func (r repo) setClaGroupIDGithubRepository(ctx context.Context, repositoryID, claGroupID string) error { +func (r *Repository) setClaGroupIDGithubRepository(ctx context.Context, repositoryID, claGroupID string) error { f := logrus.Fields{ "functionName": "v1.repositories.repository.setClaGroupIDGitHubRepository", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -909,7 +902,7 @@ func (r repo) setClaGroupIDGithubRepository(ctx context.Context, repositoryID, c } // Load the existing model - need to fetch the old note value, if available - existingModel, getErr := r.GetRepository(ctx, repositoryID) + existingModel, getErr := r.GitHubGetRepository(ctx, repositoryID) if getErr != nil { log.WithFields(f).WithError(getErr).Warn("unable to load repository by repository id") return getErr @@ -956,7 +949,7 @@ func (r repo) setClaGroupIDGithubRepository(ctx context.Context, repositoryID, c if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { case dynamodb.ErrCodeConditionalCheckFailedException: - return errors.New("github repository entry does not exist or repository_sfdc_id does not match with specified project id") + return errors.New("github repository entry does not exist or *Repositorysitory_sfdc_id does not match with specified project id") } } log.WithFields(f).WithError(err).Warn("error disabling github repository") diff --git a/cla-backend-go/repositories/service.go b/cla-backend-go/repositories/service.go index ba971d2f9..285cc8446 100644 --- a/cla-backend-go/repositories/service.go +++ b/cla-backend-go/repositories/service.go @@ -27,9 +27,9 @@ type Service interface { EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error DisableRepository(ctx context.Context, repositoryID string) error UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error - ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.ListGithubRepositories, error) + ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.GithubListRepositories, error) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) - GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) + GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.GithubListRepositories, error) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) DisableRepositoriesByProjectID(ctx context.Context, projectID string) (int, error) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*models.GithubRepository, error) @@ -44,13 +44,13 @@ type GithubOrgRepo interface { } type service struct { - repo Repository + repo RepositoryInterface ghOrgRepo GithubOrgRepo projectsClaGroupsRepo projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo Repository, ghOrgRepo GithubOrgRepo, pcgRepo projects_cla_groups.Repository) Service { +func NewService(repo RepositoryInterface, ghOrgRepo GithubOrgRepo, pcgRepo projects_cla_groups.Repository) Service { return &service{ repo: repo, ghOrgRepo: ghOrgRepo, @@ -60,7 +60,7 @@ func NewService(repo Repository, ghOrgRepo GithubOrgRepo, pcgRepo projects_cla_g // UpdateClaGroupID updates the claGroupID func (s *service) UpdateClaGroupID(ctx context.Context, repositoryID, claGroupID string) error { - return s.repo.UpdateClaGroupID(ctx, repositoryID, claGroupID) + return s.repo.GitHubUpdateClaGroupID(ctx, repositoryID, claGroupID) } func (s *service) AddGithubRepository(ctx context.Context, externalProjectID string, input *models.GithubRepositoryInput) (*models.GithubRepository, error) { @@ -132,36 +132,36 @@ func (s *service) AddGithubRepository(ctx context.Context, externalProjectID str return nil, enableErr } - return s.repo.GetRepository(ctx, existingModel.RepositoryID) + return s.repo.GitHubGetRepository(ctx, existingModel.RepositoryID) } // Doesn't exist - create it - return s.repo.AddGithubRepository(ctx, externalProjectID, projectSFID, input) + return s.repo.GitHubAddRepository(ctx, externalProjectID, projectSFID, input) } func (s *service) EnableRepository(ctx context.Context, repositoryID string) error { - return s.repo.EnableRepository(ctx, repositoryID) + return s.repo.GitHubEnableRepository(ctx, repositoryID) } func (s *service) EnableRepositoryWithCLAGroupID(ctx context.Context, repositoryID, claGroupID string) error { - return s.repo.EnableRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID) + return s.repo.GitHubEnableRepositoryWithCLAGroupID(ctx, repositoryID, claGroupID) } func (s *service) DisableRepository(ctx context.Context, repositoryID string) error { - return s.repo.DisableRepository(ctx, repositoryID) + return s.repo.GitHubDisableRepository(ctx, repositoryID) } -func (s *service) ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.ListGithubRepositories, error) { - return s.repo.ListProjectRepositories(ctx, externalProjectID, enabled) +func (s *service) ListProjectRepositories(ctx context.Context, externalProjectID string, enabled *bool) (*models.GithubListRepositories, error) { + return s.repo.GitHubListProjectRepositories(ctx, externalProjectID, enabled) } func (s *service) GetRepository(ctx context.Context, repositoryID string) (*models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "v1.repository.GetRepository", + "functionName": "v1.repository.GitHubGetRepository", "repositoryID": repositoryID, } log.WithFields(f).Debug("Searching for repository...") - ghRepo, err := s.repo.GetRepository(ctx, repositoryID) + ghRepo, err := s.repo.GitHubGetRepository(ctx, repositoryID) if err != nil || ghRepo != nil { log.WithFields(f).WithError(err).Debug("unable to get repository") return nil, err @@ -172,20 +172,20 @@ func (s *service) GetRepository(ctx context.Context, repositoryID string) (*mode return ghRepo, nil } -func (s *service) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.ListGithubRepositories, error) { - return s.repo.ListProjectRepositories(ctx, projectSFID, enabled) +func (s *service) GetRepositoryByProjectSFID(ctx context.Context, projectSFID string, enabled *bool) (*models.GithubListRepositories, error) { + return s.repo.GitHubListProjectRepositories(ctx, projectSFID, enabled) } // GetRepositoryByName returns the repository by name: project-level/cla-project func (s *service) GetRepositoryByName(ctx context.Context, repositoryName string) (*models.GithubRepository, error) { - return s.repo.GetRepositoryByName(ctx, repositoryName) + return s.repo.GitHubGetRepositoryByName(ctx, repositoryName) } // DisableRepositoriesByProjectID disables the repositories by project ID func (s *service) DisableRepositoriesByProjectID(ctx context.Context, projectID string) (int, error) { var deleteErr error // Return the list of GitHub repositories by CLA Group for those that are currently enabled - ghOrgs, err := s.repo.GetCLAGroupRepositoriesGroupByOrgs(ctx, projectID, true) + ghOrgs, err := s.repo.GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx, projectID, true) if err != nil { return 0, err } @@ -193,7 +193,7 @@ func (s *service) DisableRepositoriesByProjectID(ctx context.Context, projectID log.Debugf("Deleting repositories for project :%s", projectID) for _, ghOrg := range ghOrgs { for _, item := range ghOrg.List { - deleteErr = s.repo.DisableRepository(ctx, item.RepositoryID) + deleteErr = s.repo.GitHubDisableRepository(ctx, item.RepositoryID) if deleteErr != nil { log.Warnf("Unable to remove repository: %s for project :%s error :%v", item.RepositoryID, projectID, deleteErr) } @@ -207,10 +207,10 @@ func (s *service) DisableRepositoriesByProjectID(ctx context.Context, projectID // GetRepositoriesByCLAGroup returns the list of repositories for the specified CLA Group func (s *service) GetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*models.GithubRepository, error) { // Return the list of github repositories that are enabled - return s.repo.GetRepositoriesByCLAGroup(ctx, claGroupID, true) + return s.repo.GitHubGetRepositoriesByCLAGroup(ctx, claGroupID, true) } // GetRepositoriesByOrganizationName get repositories by organization name func (s *service) GetRepositoriesByOrganizationName(ctx context.Context, gitHubOrgName string) ([]*models.GithubRepository, error) { - return s.repo.GetRepositoriesByOrganizationName(ctx, gitHubOrgName) + return s.repo.GitHubGetRepositoriesByOrganizationName(ctx, gitHubOrgName) } diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 8d6477723..87329aa03 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -107,14 +107,14 @@ type repository struct { companyRepo company.IRepository usersRepo users.UserRepository eventsService events.Service - repositoriesRepo repositories.Repository + repositoriesRepo repositories.RepositoryInterface ghOrgRepo github_organizations.RepositoryInterface gerritService gerrits.Service signatureTableName string } // NewRepository creates a new instance of the signature repository service -func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.Repository, ghOrgRepo github_organizations.RepositoryInterface, gerritService gerrits.Service) SignatureRepository { +func NewRepository(awsSession *session.Session, stage string, companyRepo company.IRepository, usersRepo users.UserRepository, eventsService events.Service, repositoriesRepo repositories.RepositoryInterface, ghOrgRepo github_organizations.RepositoryInterface, gerritService gerrits.Service) SignatureRepository { return repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), @@ -2457,7 +2457,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Action = utils.RemoveApprovals approvalList.Version = claGroupModel.Version // Get repositories by CLAGroup - repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) + repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GitHubGetRepositoriesByCLAGroup(ctx, projectID, true) if getRepoByCLAGroupErr != nil { msg := fmt.Sprintf("unable to fetch repositories for cla group ID: %s ", projectID) log.WithFields(f).WithError(getRepoByCLAGroupErr).Warn(msg) @@ -2609,7 +2609,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model approvalList.Action = utils.RemoveApprovals approvalList.Version = claGroupModel.Version // Get repositories by CLAGroup - repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GetRepositoriesByCLAGroup(ctx, projectID, true) + repositories, getRepoByCLAGroupErr := repo.repositoriesRepo.GitHubGetRepositoriesByCLAGroup(ctx, projectID, true) if getRepoByCLAGroupErr != nil { msg := fmt.Sprintf("unable to fetch repositories for cla group ID: %s ", projectID) log.WithFields(f).WithError(getRepoByCLAGroupErr).Warn(msg) diff --git a/cla-backend-go/swagger/cla.v1.yaml b/cla-backend-go/swagger/cla.v1.yaml index dd822172b..5b57cca9d 100644 --- a/cla-backend-go/swagger/cla.v1.yaml +++ b/cla-backend-go/swagger/cla.v1.yaml @@ -2068,7 +2068,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/create-github-organization' + $ref: '#/definitions/github-create-organization' required: true responses: 200: @@ -2198,7 +2198,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/list-github-repositories' + $ref: '#/definitions/github-list-repositories' 400: $ref: '#/responses/invalid-request' 401: @@ -2857,7 +2857,7 @@ definitions: $ref: './common/create-cla-group-template.yaml' update-github-organization: - $ref: './common/update-github-organization.yaml' + $ref: './common/github-update-organization.yaml' template-pdfs: $ref: './common/template-pdfs.yaml' @@ -3103,8 +3103,8 @@ definitions: github-organizations: $ref: './common/github-organizations.yaml' - create-github-organization: - $ref: './common/create-github-organization.yaml' + github-create-organization: + $ref: './common/github-create-organization.yaml' github-organization: $ref: './common/github-organization.yaml' @@ -3112,8 +3112,8 @@ definitions: github-repository-info: $ref: './common/github-repository-info.yaml' - list-github-repositories: - $ref: './common/list-github-repositories.yaml' + github-list-repositories: + $ref: './common/github-list-repositories.yaml' org-list: $ref: './common/org-list.yaml' diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 523fbec3d..93dd7f870 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1253,7 +1253,9 @@ paths: tags: - template - + # --------------------------------------------------------------------------- + # GitHub Endpoint Definitions + # --------------------------------------------------------------------------- /project/{projectSFID}/github/organizations: post: summary: Add new GitHub Oranization in the project @@ -1271,7 +1273,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/create-github-organization' + $ref: '#/definitions/github-create-organization' required: true responses: '200': @@ -1350,7 +1352,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/update-github-organization' + $ref: '#/definitions/github-update-organization' required: true responses: '200': @@ -1433,7 +1435,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/list-github-repositories' + $ref: '#/definitions/github-list-repositories' '400': $ref: '#/responses/invalid-request' '401': @@ -1467,7 +1469,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/list-github-repositories' + $ref: '#/definitions/github-list-repositories' '400': $ref: '#/responses/invalid-request' '401': @@ -1604,6 +1606,9 @@ paths: tags: - github-repositories + # --------------------------------------------------------------------------- + # GitLab Endpoint Definitions + # --------------------------------------------------------------------------- /project/{projectSFID}/gitlab/organizations: post: summary: Add new Gitlab Organization in the project @@ -1621,7 +1626,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/create-gitlab-organization' + $ref: '#/definitions/gitlab-create-organization' required: true responses: '200': @@ -1631,7 +1636,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/gitlab-organization' + $ref: '#/definitions/gitlab-project-organizations' '400': $ref: '#/responses/invalid-request' '401': @@ -1665,7 +1670,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/project-gitlab-organizations' + $ref: '#/definitions/gitlab-project-organizations' '400': $ref: '#/responses/invalid-request' '401': @@ -1700,7 +1705,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/update-gitlab-organization' + $ref: '#/definitions/gitlab-update-organization' required: true responses: '200': @@ -1755,6 +1760,117 @@ paths: tags: - gitlab-organizations + /project/{projectSFID}/gitlab/repositories: + post: + summary: Add a GitLab repository to the project + description: Endpoint to add a GitLab repository for the project + operationId: addProjectGitLabRepository + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + - in: body + name: gitlab-add-repository + schema: + $ref: '#/definitions/gitlab-add-repository' + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gitlab-repository' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '409': + $ref: '#/responses/conflict' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-repositories + get: + summary: Get the GitLab repositories of the project + description: Endpoint to fetch the list of GitLab repositories for the project + operationId: getProjectGitLabRepositories + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gitlab-list-repositories' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-repositories + + /project/{projectSFID}/gitlab/repositories/{repositoryID}: + delete: + summary: Remove the GitLab repository from the project + description: Endpoint to remove a GitLab repository from a project + operationId: deleteProjectGitLabRepository + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/x-acl" + - $ref: "#/parameters/x-username" + - $ref: "#/parameters/x-email" + - name: projectSFID + in: path + type: string + required: true + - name: repositoryID + in: path + type: string + required: true + responses: + '204': + description: 'Resource Deleted' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + tags: + - gitlab-repositories + /cla-group/{claGroupID}/icla/signatures: get: summary: List individual signatures for CLA Group @@ -4224,6 +4340,9 @@ definitions: event: $ref: './common/event.yaml' + # --------------------------------------------------------------------------- + # GitHub Definitions + # --------------------------------------------------------------------------- github-activity-input: type: object required: @@ -4310,12 +4429,45 @@ definitions: items: $ref: '#/definitions/github-repository-branch-protection-status-checks' + github-organization: + $ref: './common/github-organization.yaml' + github-repository: $ref: './common/github-repository.yaml' - list-github-repositories: - $ref: './common/list-github-repositories.yaml' + github-create-organization: + $ref: './common/github-create-organization.yaml' + + github-update-organization: + $ref: './common/github-update-organization.yaml' + + github-list-repositories: + $ref: './common/github-list-repositories.yaml' + + # --------------------------------------------------------------------------- + # GitLab Definitions + # --------------------------------------------------------------------------- + gitlab-organization: + $ref: './common/gitlab-organization.yaml' + + gitlab-repository: + $ref: './common/github-repository.yaml' + + gitlab-create-organization: + $ref: './common/gitlab-create-organization.yaml' + + gitlab-update-organization: + $ref: './common/github-update-organization.yaml' + + gitlab-list-repositories: + $ref: './common/gitlab-list-repositories.yaml' + gitlab-add-repository: + $ref: './common/gitlab-add-repository.yaml' + + # --------------------------------------------------------------------------- + # CLA Group Definitions + # --------------------------------------------------------------------------- cla-groups: $ref: './common/cla-groups.yaml' @@ -4325,6 +4477,9 @@ definitions: sf-project-summary: $ref: './common/sf-project-summary.yaml' + # --------------------------------------------------------------------------- + # CLA Template Definitions + # --------------------------------------------------------------------------- template: $ref: './common/template.yaml' @@ -4334,24 +4489,6 @@ definitions: template-pdfs: $ref: './common/template-pdfs.yaml' - github-organization: - $ref: './common/github-organization.yaml' - - create-github-organization: - $ref: './common/create-github-organization.yaml' - - update-github-organization: - $ref: './common/update-github-organization.yaml' - - gitlab-organization: - $ref: './common/gitlab-organization.yaml' - - create-gitlab-organization: - $ref: './common/create-github-organization.yaml' - - update-gitlab-organization: - $ref: './common/update-github-organization.yaml' - user: $ref: './common/user.yaml' @@ -5426,56 +5563,6 @@ definitions: items: $ref: '#/definitions/project-github-repository' - project-gitlab-organizations: - type: object - properties: - list: - type: array - items: - $ref: '#/definitions/project-gitlab-organization' - - project-gitlab-organization: - type: object - properties: - auto_enabled: - type: boolean - description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. - x-omitempty: false - autoEnableCLAGroupID: - type: string - description: The CLA Group ID which is attached to the auto-enabled flag - autoEnabledCLAGroupName: - type: string - description: The CLA Group name which is attached to the auto-enabled flag - branchProtectionEnabled: - type: boolean - description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. - x-omitempty: false - installationURL: - type: string - x-nullable: true - format: uri - gitlab_organization_name: - type: string - description: The Gitlab Organization name - example: "kubernetes" - # Pattern aligns with UI and other platform services including Org Service - # \w Any word character (alphanumeric & underscore), dashes, periods - pattern: '^([\w\-\.]+){2,255}$' - minLength: 2 - maxLength: 255 - connection_status: - type: string - enum: - - connected - - partial_connection - - connection_failure - - no_connection - repositories: - type: array - items: - $ref: '#/definitions/project-github-repository' - project-github-repository: type: object properties: @@ -5505,6 +5592,15 @@ definitions: - connected - connection_failure + gitlab-project-organizations: + $ref: './common/gitlab-project-organizations.yaml' + + gitlab-project-organization: + $ref: './common/gitlab-project-organization.yaml' + + gitlab-project-repository: + $ref: './common/gitlab-project-repository.yaml' + url-object: type: object properties: diff --git a/cla-backend-go/swagger/common/create-github-organization.yaml b/cla-backend-go/swagger/common/github-create-organization.yaml similarity index 100% rename from cla-backend-go/swagger/common/create-github-organization.yaml rename to cla-backend-go/swagger/common/github-create-organization.yaml diff --git a/cla-backend-go/swagger/common/list-github-repositories.yaml b/cla-backend-go/swagger/common/github-list-repositories.yaml similarity index 100% rename from cla-backend-go/swagger/common/list-github-repositories.yaml rename to cla-backend-go/swagger/common/github-list-repositories.yaml diff --git a/cla-backend-go/swagger/common/github-repository.yaml b/cla-backend-go/swagger/common/github-repository.yaml index 9aebb8eb5..786e330dc 100644 --- a/cla-backend-go/swagger/common/github-repository.yaml +++ b/cla-backend-go/swagger/common/github-repository.yaml @@ -3,45 +3,55 @@ type: object properties: - dateCreated: - type: string - description: Created date/time - dateModified: - type: string - description: Last modified date/time - repositoryExternalID: - type: string - description: The repository ID from the external service - repositoryID: - type: string + repository_id: description: The internal repository ID - repositoryName: + $ref: './common/properties/internal-id.yaml' + repository_external_id: + type: integer + description: The repository ID from the external service, such as GitHub or GitLab + minimum: 1 + example: 7 + repository_project_sfid: + description: Project SFID + $ref: './common/properties/external-id.yaml' + repository_cla_group_id: + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' + repository_name: type: string description: The repository name - repositoryOrganizationName: + example: 'easycla-test-repo-4' + repository_organization_name: type: string description: The organization name associated with this repository - repositoryProjectID: - type: string - description: The CLA Group ID associated with this repository - repositorySfdcID: - type: string - repositoryType: - type: string - description: The repository type - typically github, gerrit or possibly gitlab - repositoryUrl: + example: 'The Linux Foundation/product/EasyCLA' + repository_url: type: string description: The external repository URL + example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' + repository_type: + type: string + description: the repository type + example: 'gitlab' enabled: type: boolean - description: Flag to indicate if this repository is enabled or not. Repositories may become disabled if they have been moved or deleted from GitHub. + description: Flag to indicate if this repository is enabled or not. Repositories may become disabled if they have been moved or deleted from GitHub or GitLab. x-omitempty: false + date_created: + type: string + example: "2020-02-06T09:31:49.245630+0000" + minLength: 18 + maxLength: 64 + date_modified: + type: string + example: "2020-02-06T09:31:49.245646+0000" + minLength: 18 + maxLength: 64 note: type: string description: An optional note field to store any additional information about this record. Helpful for auditing. + example: 'optional note about the repository - migrated on MM/DD/YYYY' version: type: string description: The version identifier for this repository record - projectSFID: - type: string - description: The project SFID associated with this repository + example: 'v1' diff --git a/cla-backend-go/swagger/common/update-github-organization.yaml b/cla-backend-go/swagger/common/github-update-organization.yaml similarity index 100% rename from cla-backend-go/swagger/common/update-github-organization.yaml rename to cla-backend-go/swagger/common/github-update-organization.yaml diff --git a/cla-backend-go/swagger/common/gitlab-add-repository.yaml b/cla-backend-go/swagger/common/gitlab-add-repository.yaml new file mode 100644 index 000000000..7e908ead9 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-add-repository.yaml @@ -0,0 +1,42 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +required: + - repository_external_id + - repository_project_sfid + - repository_cla_group_id + - repository_name + - repository_organization_name + - repository_url +properties: + repository_external_id: + type: integer + description: The repository ID from the external service, such as GitHub or GitLab + minimum: 1 + example: 7 + repository_project_sfid: + description: Project SFID + $ref: './common/properties/external-id.yaml' + repository_cla_group_id: + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' + repository_name: + type: string + description: The repository name + example: 'easycla-test-repo-4' + repository_organization_name: + type: string + description: The organization name associated with this repository + example: 'The Linux Foundation/product/EasyCLA' + repository_url: + type: string + description: The external repository URL + example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' + enabled: + type: boolean + description: Flag to indicate if this repository is enabled or not. Repositories may become disabled if they have been moved or deleted from GitHub or GitLab. + x-omitempty: false + note: + description: optional note added to the record + type: string diff --git a/cla-backend-go/swagger/common/gitlab-create-organization.yaml b/cla-backend-go/swagger/common/gitlab-create-organization.yaml new file mode 100644 index 000000000..0a9ea81c4 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-create-organization.yaml @@ -0,0 +1,27 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +required: + - organizationName +properties: + organizationName: + type: string + description: The GitLab Group/Organization name + example: "kubernetes" + # Pattern aligns with UI and other platform services including Org Service + # \w Any word character (alphanumeric & underscore), dashes, periods + pattern: '^([\w\-\.]+){2,255}$' + minLength: 2 + maxLength: 255 + autoEnabled: + type: boolean + description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. + default: false + autoEnabledClaGroupID: + type: string + description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. + branchProtectionEnabled: + type: boolean + description: Flag to indicate if this GitLab Group/Organization is configured to automatically setup branch protection on CLA enabled repositories. + default: false diff --git a/cla-backend-go/swagger/common/gitlab-list-repositories.yaml b/cla-backend-go/swagger/common/gitlab-list-repositories.yaml new file mode 100644 index 000000000..1e9440adc --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-list-repositories.yaml @@ -0,0 +1,9 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + list: + type: array + items: + $ref: '#/definitions/gitlab-repository' diff --git a/cla-backend-go/swagger/common/gitlab-organizations.yaml b/cla-backend-go/swagger/common/gitlab-organizations.yaml index 42a292a78..52aafb588 100644 --- a/cla-backend-go/swagger/common/gitlab-organizations.yaml +++ b/cla-backend-go/swagger/common/gitlab-organizations.yaml @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT type: object +description: GitLab Organizations properties: list: type: array diff --git a/cla-backend-go/swagger/common/gitlab-project-organization.yaml b/cla-backend-go/swagger/common/gitlab-project-organization.yaml new file mode 100644 index 000000000..e1701acef --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-project-organization.yaml @@ -0,0 +1,44 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +description: GitLab Project Organization +properties: + auto_enabled: + type: boolean + description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. + x-omitempty: false + autoEnableCLAGroupID: + type: string + description: The CLA Group ID which is attached to the auto-enabled flag + autoEnabledCLAGroupName: + type: string + description: The CLA Group name which is attached to the auto-enabled flag + branchProtectionEnabled: + type: boolean + description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. + x-omitempty: false + installationURL: + type: string + x-nullable: true + format: uri + gitlab_organization_name: + type: string + description: The Gitlab Organization name + example: "kubernetes" + # Pattern aligns with UI and other platform services including Org Service + # \w Any word character (alphanumeric & underscore), dashes, periods + pattern: '^([\w\-\.]+){2,255}$' + minLength: 2 + maxLength: 255 + connection_status: + type: string + enum: + - connected + - partial_connection + - connection_failure + - no_connection + repositories: + type: array + items: + $ref: '#/definitions/gitlab-project-repository' diff --git a/cla-backend-go/swagger/common/gitlab-project-organizations.yaml b/cla-backend-go/swagger/common/gitlab-project-organizations.yaml new file mode 100644 index 000000000..152611ecd --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-project-organizations.yaml @@ -0,0 +1,10 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +description: GitLab Project Organizations +properties: + list: + type: array + items: + $ref: '#/definitions/gitlab-project-organization' diff --git a/cla-backend-go/swagger/common/gitlab-project-repository.yaml b/cla-backend-go/swagger/common/gitlab-project-repository.yaml new file mode 100644 index 000000000..7441f2eb2 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-project-repository.yaml @@ -0,0 +1,41 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +description: GitLab Project Repository +properties: + repository_id: + description: Repository Internal ID + $ref: './common/properties/internal-id.yaml' + x-omitempty: false + repository_gitlab_id: + type: integer + description: 'Repository GitLab ID value' + minimum: 1 + example: 2292 + repository_name: + type: string + description: 'GitLab Repository/Project name' + x-omitempty: false + cla_group_id: + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' + x-omitempty: false + project_id: + description: Project SFID + $ref: './common/properties/external-id.yaml' + x-omitempty: false + parent_project_id: + description: Parent Project SFID + $ref: './common/properties/external-id.yaml' + x-omitempty: false + enabled: + type: boolean + description: 'Enabled flag' + x-omitempty: false + connection_status: + type: string + description: 'Connection status for the repository, one of the supported values connected or connection_failure' + enum: + - connected + - connection_failure diff --git a/cla-backend-go/swagger/common/gitlab-repository.yaml b/cla-backend-go/swagger/common/gitlab-repository.yaml new file mode 100644 index 000000000..6af477008 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-repository.yaml @@ -0,0 +1,57 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + repositoryID: + description: The internal repository ID + $ref: './common/properties/internal-id.yaml' + repository_external_id: + type: integer + description: The repository ID from the external service, such as GitHub or GitLab + minimum: 1 + example: 7 + repository_cla_group_id: + type: string + description: The CLA Group ID associated with this repository + repository_project_sfid: + description: Project SFID + $ref: './common/properties/external-id.yaml' + repository_name: + type: string + description: The repository name + example: 'easycla-test-repo-4' + repository_organization_name: + type: string + description: The organization name associated with this repository + example: 'The Linux Foundation/product/EasyCLA' + repository_url: + type: string + description: The external repository URL + example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' + repository_type: + type: string + description: the repository type + example: 'gitlab' + enabled: + type: boolean + description: Flag to indicate if this repository is enabled or not. Repositories may become disabled if they have been moved or deleted from GitHub or GitLab. + x-omitempty: false + date_created: + type: string + example: "2020-02-06T09:31:49.245630+0000" + minLength: 18 + maxLength: 64 + date_modified: + type: string + example: "2020-02-06T09:31:49.245646+0000" + minLength: 18 + maxLength: 64 + note: + type: string + description: An optional note field to store any additional information about this record. Helpful for auditing. + example: 'optional note about the repository - migrated on MM/DD/YYYY' + version: + type: string + description: The version identifier for this repository record + example: 'v1' diff --git a/cla-backend-go/swagger/common/gitlab-update-organization.yaml b/cla-backend-go/swagger/common/gitlab-update-organization.yaml new file mode 100644 index 000000000..e9875eca5 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-update-organization.yaml @@ -0,0 +1,17 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +required: + - autoEnabled +properties: + autoEnabled: + type: boolean + description: Flag to indicate if auto-enabled flag should be enabled. Group/Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. + autoEnabledClaGroupID: + type: string + description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the GitLab Group/Organization. If autoEnabled is on this field needs to be set as well. + branchProtectionEnabled: + type: boolean + description: Flag to indicate if this Group/Organization is configured to automatically setup branch protection on CLA enabled repositories. + x-omitempty: true diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index a95b42785..1f29cf8bd 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -144,6 +144,27 @@ const GitHubEmailLabel = "GitHub Email Address" //GitHubUserLabel represents the GH username Label used for email const GitHubUserLabel = "GitHub Username" +// GitLab is the GitLab spelled out with the proper case +const GitLab = "GitLab" + +// GitLabLower is the GitLab spelled out in lower case +const GitLabLower = "gitlab" + +//GitLabRepoNotFound is a string that indicates the GitLab repository is not found +const GitLabRepoNotFound = "GitLab repository not found" + +//GitLabDuplicateRepoFound is a string that indicates that duplicate GitLab repositories were found +const GitLabDuplicateRepoFound = "Duplicate GitLab repositories were found" + +//GitLabRepoExists is a string that indicates the GitLab repository already exists +const GitLabRepoExists = "GitLab repository exists" + +//GitLabEmailLabel represents the GitLab Email label used for email +const GitLabEmailLabel = "GitLab Email Address" + +//GitLabUserLabel represents the GitLab username Label used for email +const GitLabUserLabel = "GitLab Username" + //EmailLabel represents LF/EasyCLA Email address const EmailLabel = "Email Address" diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index 62ff6d6dc..ea5f35a30 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -268,6 +268,34 @@ func (e *CompanyNotFound) Unwrap() error { return e.Err } +// InvalidRepositoryTypeError is an error model for an invalid repository type +type InvalidRepositoryTypeError struct { + RepositoryType string + RepositoryName string + Err error +} + +// Error is an error string function for the InvalidRepositoryTypeError model +func (e *InvalidRepositoryTypeError) Error() string { + msg := "Invalid repository type" + if e.RepositoryType != "" { + msg = fmt.Sprintf("%s - type: %s ", msg, e.RepositoryType) + } + if e.RepositoryName != "" { + msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *InvalidRepositoryTypeError) Unwrap() error { + return e.Err +} + // GitHubRepositoryNotFound is an error model for a GitHub repository not found type GitHubRepositoryNotFound struct { Message string @@ -324,6 +352,98 @@ func (e *GitHubRepositoryExists) Unwrap() error { return e.Err } +// GitLabRepositoryNotFound is an error model for a GitLab repository not found +type GitLabRepositoryNotFound struct { + Message string + RepositoryName string + ProjectSFID string + CLAGroupID string + Err error +} + +// Error is an error string function for the GitHubRepositoryNotFound model +func (e *GitLabRepositoryNotFound) Error() string { + msg := GitLabRepoNotFound + if e.Message != "" { + msg = e.Message + } + if e.RepositoryName != "" { + msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) + } + if e.ProjectSFID != "" { + msg = fmt.Sprintf("%s - project SFID: %s ", msg, e.ProjectSFID) + } + if e.CLAGroupID != "" { + msg = fmt.Sprintf("%s - CLA Group ID: %s ", msg, e.CLAGroupID) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *GitLabRepositoryNotFound) Unwrap() error { + return e.Err +} + +// GitLabDuplicateRepositoriesFound is an error model for a GitLab duplicate repositories found +type GitLabDuplicateRepositoriesFound struct { + Message string + RepositoryName string + Err error +} + +// Error is an error string function for the GitLabDuplicateRepositoriesFound model +func (e *GitLabDuplicateRepositoriesFound) Error() string { + msg := GitLabDuplicateRepoFound + if e.Message != "" { + msg = e.Message + } + if e.RepositoryName != "" { + msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *GitLabDuplicateRepositoriesFound) Unwrap() error { + return e.Err +} + +// GitLabRepositoryExists is an error model for when a GitHub repository already exists +type GitLabRepositoryExists struct { + Message string + RepositoryName string + Err error +} + +// Error is an error string function for the GitLabRepositoryExists model +func (e *GitLabRepositoryExists) Error() string { + msg := GitLabRepoNotFound + if e.Message != "" { + msg = e.Message + } + if e.RepositoryName != "" { + msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) + } + if e.Err != nil { + msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) + } + + return strings.TrimSpace(msg) +} + +// Unwrap method returns its contained error +func (e *GitLabRepositoryExists) Unwrap() error { + return e.Err +} + // CLAManagerError is an error model for when a CLA Manager error occurs type CLAManagerError struct { Message string diff --git a/cla-backend-go/v2/dynamo_events/autoenable.go b/cla-backend-go/v2/dynamo_events/autoenable.go index 1db0a8b03..0a53e1c6b 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable.go +++ b/cla-backend-go/v2/dynamo_events/autoenable.go @@ -40,14 +40,14 @@ type AutoEnableService interface { // NewAutoEnableService creates a new AutoEnableService func NewAutoEnableService(repositoryService repositories.Service, - githubRepo repositories.Repository, + githubRepo repositories.RepositoryInterface, githubOrgRepo github_organizations.RepositoryInterface, claRepository projects_cla_groups.Repository, claService project.Service, ) AutoEnableService { return &autoEnableServiceProvider{ repositoryService: repositoryService, - githubRepo: githubRepo, + gitV1Repository: githubRepo, githubOrgRepo: githubOrgRepo, claRepository: claRepository, claService: claService, @@ -58,7 +58,7 @@ func NewAutoEnableService(repositoryService repositories.Service, // having it separated in its own struct makes testing easier. type autoEnableServiceProvider struct { repositoryService repositories.Service - githubRepo repositories.Repository + gitV1Repository repositories.RepositoryInterface githubOrgRepo github_organizations.RepositoryInterface claRepository projects_cla_groups.Repository claService project.Service @@ -119,7 +119,7 @@ func (a *autoEnableServiceProvider) CreateAutoEnabledRepository(repo *github.Rep externalProjectID := claGroupModel.ProjectExternalID - repoModel, err := a.githubRepo.AddGithubRepository(ctx, externalProjectID, projectSFID, &models.GithubRepositoryInput{ + repoModel, err := a.gitV1Repository.GitHubAddRepository(ctx, externalProjectID, projectSFID, &models.GithubRepositoryInput{ RepositoryProjectID: swag.String(claGroupID), RepositoryName: swag.String(repositoryFullName), RepositoryType: swag.String("github"), @@ -161,11 +161,11 @@ func (a *autoEnableServiceProvider) AutoEnabledForGithubOrg(f logrus.Fields, git } for _, repo := range repos.List { - if repo.RepositoryProjectID == claGroupID { + if repo.RepositoryClaGroupID == claGroupID { continue } - repo.RepositoryProjectID = claGroupID + repo.RepositoryClaGroupID = claGroupID if err := a.repositoryService.UpdateClaGroupID(context.Background(), repo.RepositoryID, claGroupID); err != nil { log.WithFields(f).Warnf("updating claGroupID for repository : %s failed : %v", repo.RepositoryID, err) return err @@ -278,7 +278,7 @@ See: GitHub CombinedRepository -> Settings -> Branches -> Branch Protection Rule // DetermineClaGroupID checks if AutoEnabledClaGroupID is set then returns it (high precedence) otherwise tries to determine // the autoEnabled claGroupID by guessing from existing repos -func DetermineClaGroupID(f logrus.Fields, gitHubOrg *models.GithubOrganization, repos *models.ListGithubRepositories) (string, error) { +func DetermineClaGroupID(f logrus.Fields, gitHubOrg *models.GithubOrganization, repos *models.GithubListRepositories) (string, error) { if gitHubOrg.AutoEnabledClaGroupID != "" { return gitHubOrg.AutoEnabledClaGroupID, nil } @@ -289,12 +289,12 @@ func DetermineClaGroupID(f logrus.Fields, gitHubOrg *models.GithubOrganization, // check if any of the repos is member to more than one cla group, in general shouldn't happen var claGroupID string for _, repo := range repos.List { - if repo.RepositoryProjectID == "" || repo.ProjectSFID == "" { + if repo.RepositoryClaGroupID == "" || repo.RepositoryProjectSfid == "" { continue } - claGroupSet[repo.RepositoryProjectID] = true - sfidSet[repo.ProjectSFID] = true - claGroupID = repo.RepositoryProjectID + claGroupID = repo.RepositoryClaGroupID + claGroupSet[repo.RepositoryClaGroupID] = true + sfidSet[repo.RepositoryProjectSfid] = true } if len(claGroupSet) == 0 && len(sfidSet) == 0 { diff --git a/cla-backend-go/v2/dynamo_events/autoenable_test.go b/cla-backend-go/v2/dynamo_events/autoenable_test.go index 061408561..fda3ce198 100644 --- a/cla-backend-go/v2/dynamo_events/autoenable_test.go +++ b/cla-backend-go/v2/dynamo_events/autoenable_test.go @@ -58,7 +58,7 @@ func TestAutoEnableServiceProvider_AutoEnabledForGithubOrg(t *testing.T) { m. EXPECT(). ListProjectRepositories(gomock.Any(), externalProjectID, &enabled). - Return(&models.ListGithubRepositories{}, nil) + Return(&models.GithubListRepositories{}, nil) }, }, { @@ -71,15 +71,15 @@ func TestAutoEnableServiceProvider_AutoEnabledForGithubOrg(t *testing.T) { m. EXPECT(). ListProjectRepositories(gomock.Any(), externalProjectID, &enabled). - Return(&models.ListGithubRepositories{ + Return(&models.GithubListRepositories{ List: []*models.GithubRepository{ { - RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", - ProjectSFID: externalProjectID, + RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", + RepositoryProjectSfid: externalProjectID, }, { - RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", - ProjectSFID: externalProjectID, + RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", + RepositoryProjectSfid: externalProjectID, }, }, }, nil) @@ -96,17 +96,17 @@ func TestAutoEnableServiceProvider_AutoEnabledForGithubOrg(t *testing.T) { m. EXPECT(). ListProjectRepositories(gomock.Any(), externalProjectID, &enabled). - Return(&models.ListGithubRepositories{ + Return(&models.GithubListRepositories{ List: []*models.GithubRepository{ { - RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", - RepositoryProjectID: claGroupID, - ProjectSFID: externalProjectID, + RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", + RepositoryClaGroupID: claGroupID, + RepositoryProjectSfid: externalProjectID, }, { - RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", - RepositoryProjectID: "anotherclagroup", - ProjectSFID: externalProjectID, + RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", + RepositoryClaGroupID: "anotherclagroup", + RepositoryProjectSfid: externalProjectID, }, }, }, nil) @@ -124,15 +124,15 @@ func TestAutoEnableServiceProvider_AutoEnabledForGithubOrg(t *testing.T) { m. EXPECT(). ListProjectRepositories(gomock.Any(), externalProjectID, &enabled). - Return(&models.ListGithubRepositories{ + Return(&models.GithubListRepositories{ List: []*models.GithubRepository{ { - RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", - ProjectSFID: externalProjectID, + RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", + RepositoryProjectSfid: externalProjectID, }, { - RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", - ProjectSFID: externalProjectID, + RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", + RepositoryProjectSfid: externalProjectID, }, }, }, nil) @@ -156,17 +156,17 @@ func TestAutoEnableServiceProvider_AutoEnabledForGithubOrg(t *testing.T) { m. EXPECT(). ListProjectRepositories(gomock.Any(), externalProjectID, &enabled). - Return(&models.ListGithubRepositories{ + Return(&models.GithubListRepositories{ List: []*models.GithubRepository{ { - RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", - ProjectSFID: externalProjectID, - RepositoryProjectID: claGroupID, + RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", + RepositoryProjectSfid: externalProjectID, + RepositoryClaGroupID: claGroupID, }, { - RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", - ProjectSFID: externalProjectID, - RepositoryProjectID: claGroupID, + RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", + RepositoryProjectSfid: externalProjectID, + RepositoryClaGroupID: claGroupID, }, }, }, nil) @@ -182,16 +182,16 @@ func TestAutoEnableServiceProvider_AutoEnabledForGithubOrg(t *testing.T) { m. EXPECT(). ListProjectRepositories(gomock.Any(), externalProjectID, &enabled). - Return(&models.ListGithubRepositories{ + Return(&models.GithubListRepositories{ List: []*models.GithubRepository{ { - RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", - ProjectSFID: externalProjectID, + RepositoryID: "d7c1050b-2f32-44ea-bad2-3c8ff980ccd4", + RepositoryProjectSfid: externalProjectID, }, { - RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", - ProjectSFID: externalProjectID, - RepositoryProjectID: claGroupID, + RepositoryID: "b42216b4-8f6d-41c0-8cde-7b2acbf0656a", + RepositoryProjectSfid: externalProjectID, + RepositoryClaGroupID: claGroupID, }, }, }, nil) diff --git a/cla-backend-go/v2/dynamo_events/github_repository.go b/cla-backend-go/v2/dynamo_events/github_repository.go index 3044b8504..dc2e16988 100644 --- a/cla-backend-go/v2/dynamo_events/github_repository.go +++ b/cla-backend-go/v2/dynamo_events/github_repository.go @@ -41,7 +41,7 @@ func (s *service) GithubRepoModifyAddEvent(event events.DynamoDBEventRecord) err log.WithFields(f).Warnf("problem unmarshalling old repository model event, error: %+v", err) return err } - claGroupID = oldRepoModel.RepositoryProjectID + claGroupID = oldRepoModel.RepositoryCLAGroupID projectSFID = oldRepoModel.ProjectSFID parentProjectSFID = oldRepoModel.RepositorySfdcID } else { @@ -51,7 +51,7 @@ func (s *service) GithubRepoModifyAddEvent(event events.DynamoDBEventRecord) err log.WithFields(f).Warnf("problem unmarshalling the new repository model event, error: %+v", err) return err } - claGroupID = newRepoModel.RepositoryProjectID + claGroupID = newRepoModel.RepositoryCLAGroupID projectSFID = newRepoModel.ProjectSFID parentProjectSFID = newRepoModel.RepositorySfdcID } diff --git a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go index ce536e51b..dbff5ff47 100644 --- a/cla-backend-go/v2/dynamo_events/projects_cla_groups.go +++ b/cla-backend-go/v2/dynamo_events/projects_cla_groups.go @@ -209,8 +209,8 @@ func (s *service) ProjectUnenrolledDisableRepositoryHandler(event events.DynamoD // For each GitHub repository... for _, gitHubRepo := range gitHubRepos.List { - log.WithFields(f).Debugf("disabling github repository: %s with id: %s for project with sfid: %s", - gitHubRepo.RepositoryName, gitHubRepo.RepositoryID, gitHubRepo.ProjectSFID) + log.WithFields(f).Debugf("disabling github repository: %s with id: %s for project with sfid: %s for CLA Group: %s", + gitHubRepo.RepositoryName, gitHubRepo.RepositoryID, gitHubRepo.RepositoryProjectSfid, gitHubRepo.RepositoryClaGroupID) disableErr := s.repositoryService.DisableRepository(ctx, gitHubRepo.RepositoryID) if disableErr != nil { log.WithFields(f).WithError(disableErr).Warnf("problem disabling github repository: %s with id: %s", gitHubRepo.RepositoryName, gitHubRepo.RepositoryID) diff --git a/cla-backend-go/v2/github_activity/service.go b/cla-backend-go/v2/github_activity/service.go index 5214cca3a..cdf95aa1d 100644 --- a/cla-backend-go/v2/github_activity/service.go +++ b/cla-backend-go/v2/github_activity/service.go @@ -36,7 +36,7 @@ type Service interface { } type eventHandlerService struct { - githubRepo repositories.Repository + gitV1Repository repositories.RepositoryInterface githubOrgRepo v1GithubOrg.RepositoryInterface eventService events.Service autoEnableService dynamo_events.AutoEnableService @@ -45,23 +45,23 @@ type eventHandlerService struct { } // NewService creates a new instance of the Event Handler Service -func NewService(githubRepo repositories.Repository, +func NewService(gitV1Repository repositories.RepositoryInterface, githubOrgRepo v1GithubOrg.RepositoryInterface, eventService events.Service, autoEnableService dynamo_events.AutoEnableService, emailService emails.Service) Service { - return newService(githubRepo, githubOrgRepo, eventService, autoEnableService, emailService, true) + return newService(gitV1Repository, githubOrgRepo, eventService, autoEnableService, emailService, true) } -func newService(githubRepo repositories.Repository, +func newService(gitV1Repository repositories.RepositoryInterface, githubOrgRepo v1GithubOrg.RepositoryInterface, eventService events.Service, autoEnableService dynamo_events.AutoEnableService, emailService emails.Service, sendEmail bool) Service { return &eventHandlerService{ - githubRepo: githubRepo, + gitV1Repository: gitV1Repository, githubOrgRepo: githubOrgRepo, eventService: eventService, autoEnableService: autoEnableService, @@ -134,8 +134,8 @@ func (s *eventHandlerService) handleRepositoryAddedAction(ctx context.Context, s return err } - if err := s.autoEnableService.NotifyCLAManagerForRepos(repoModel.RepositoryProjectID, []*models.GithubRepository{repoModel}); err != nil { - log.WithFields(f).Warnf("notifyCLAManager for autoEnabled repo : %s for claGroup : %s failed : %v", repoModel.RepositoryName, repoModel.RepositoryProjectID, err) + if err := s.autoEnableService.NotifyCLAManagerForRepos(repoModel.RepositoryClaGroupID, []*models.GithubRepository{repoModel}); err != nil { + log.WithFields(f).Warnf("notifyCLAManager for autoEnabled repo : %s for claGroup : %s failed : %v", repoModel.RepositoryName, repoModel.RepositoryClaGroupID, err) } if sender == nil || sender.Login == nil || *sender.Login == "" { @@ -146,9 +146,10 @@ func (s *eventHandlerService) handleRepositoryAddedAction(ctx context.Context, s // sending the log event for the added repository log.Debugf("handleRepositoryAddedAction sending RepositoryAdded Event for repo %s", *repo.FullName) s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryAdded, - ProjectID: repoModel.RepositoryProjectID, - UserID: *sender.Login, + EventType: events.RepositoryAdded, + ProjectSFID: repoModel.RepositoryProjectSfid, + CLAGroupID: repoModel.RepositoryClaGroupID, + UserID: *sender.Login, EventData: &events.RepositoryAddedEventData{ RepositoryName: *repo.FullName, }, @@ -167,7 +168,7 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, return fmt.Errorf("missing repo id") } repositoryExternalID := strconv.FormatInt(*repo.ID, 10) - repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + repoModel, err := s.gitV1Repository.GitHubGetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) @@ -178,16 +179,17 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, log.WithFields(f).Infof("disabling repo : %s", repoModel.RepositoryID) - if err := s.githubRepo.DisableRepository(context.Background(), repoModel.RepositoryID); err != nil { + if err := s.gitV1Repository.GitHubDisableRepository(context.Background(), repoModel.RepositoryID); err != nil { log.WithFields(f).Warnf("disabling repo : %s failed : %v", *repo.FullName, err) return err } // sending event for the action s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryDisabled, - ProjectID: repoModel.RepositoryProjectID, - UserID: *sender.Login, + EventType: events.RepositoryDisabled, + ProjectSFID: repoModel.RepositoryProjectSfid, + CLAGroupID: repoModel.RepositoryClaGroupID, + UserID: *sender.Login, EventData: &events.RepositoryDisabledEventData{ RepositoryName: *repo.FullName, }, @@ -195,7 +197,7 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, if s.sendEmail { subject := fmt.Sprintf("EasyCLA: Github Repository Was Removed") - body, err := emails.RenderGithubRepositoryDisabledTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryDisabledTemplateParams{ + body, err := emails.RenderGithubRepositoryDisabledTemplate(s.emailService, repoModel.RepositoryClaGroupID, emails.GithubRepositoryDisabledTemplateParams{ GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: "CLA Manager", @@ -210,7 +212,7 @@ func (s *eventHandlerService) handleRepositoryRemovedAction(ctx context.Context, return nil } - if err := s.emailService.NotifyClaManagersForClaGroupID(context.Background(), repoModel.RepositoryProjectID, subject, body); err != nil { + if err := s.emailService.NotifyClaManagersForClaGroupID(context.Background(), repoModel.RepositoryClaGroupID, subject, body); err != nil { log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) } @@ -230,7 +232,7 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, return fmt.Errorf("missing repo id") } repositoryExternalID := strconv.FormatInt(*repo.ID, 10) - repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + repoModel, err := s.gitV1Repository.GitHubGetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) @@ -241,7 +243,7 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, log.WithFields(f).Infof("renaming Github Repository from : %s to : %s", repoModel.RepositoryName, *repo.Name) - if _, err := s.githubRepo.UpdateGithubRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ + if _, err := s.gitV1Repository.GitHubUpdateRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ RepositoryName: repo.Name, Note: "repository was renamed externally", }); err != nil { @@ -255,9 +257,10 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, // sending event for the action s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryRenamed, - ProjectID: repoModel.RepositoryProjectID, - UserID: *sender.Login, + EventType: events.RepositoryRenamed, + ProjectSFID: repoModel.RepositoryProjectSfid, + CLAGroupID: repoModel.RepositoryClaGroupID, + UserID: *sender.Login, EventData: &events.RepositoryRenamedEventData{ NewRepositoryName: *repo.Name, OldRepositoryName: repoModel.RepositoryName, @@ -266,7 +269,7 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, if s.sendEmail { subject := fmt.Sprintf("EasyCLA: Github Repository Was Renamed") - body, err := emails.RenderGithubRepositoryRenamedTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryRenamedTemplateParams{ + body, err := emails.RenderGithubRepositoryRenamedTemplate(s.emailService, repoModel.RepositoryClaGroupID, emails.GithubRepositoryRenamedTemplateParams{ GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: "CLA Manager", @@ -282,7 +285,7 @@ func (s *eventHandlerService) handleRepositoryRenamedAction(ctx context.Context, return nil } - if err := s.emailService.NotifyClaManagersForClaGroupID(context.Background(), repoModel.RepositoryProjectID, subject, body); err != nil { + if err := s.emailService.NotifyClaManagersForClaGroupID(context.Background(), repoModel.RepositoryClaGroupID, subject, body); err != nil { log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) } @@ -314,7 +317,7 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont } repositoryExternalID := strconv.FormatInt(*repo.ID, 10) - repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + repoModel, err := s.gitV1Repository.GitHubGetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", repoName) @@ -361,7 +364,7 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont return fmt.Errorf("aborting the repository : %s transfer, new githubOrg : %s doesn't have claGroupID set", repoModel.RepositoryName, newGithubOrg.OrganizationName) } - _, err = s.githubRepo.UpdateGithubRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ + _, err = s.gitV1Repository.GitHubUpdateRepository(ctx, repoModel.RepositoryID, &models.GithubRepositoryInput{ Note: fmt.Sprintf("repository was transferred from org : %s to : %s", oldGithubOrg.OrganizationName, newGithubOrg.OrganizationName), RepositoryOrganizationName: aws.String(newGithubOrg.OrganizationName), RepositoryURL: repo.HTMLURL, @@ -373,9 +376,10 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont // sending event for the action s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryTransferred, - ProjectID: repoModel.RepositoryProjectID, - UserID: *sender.Login, + EventType: events.RepositoryTransferred, + ProjectSFID: repoModel.RepositoryProjectSfid, + CLAGroupID: repoModel.RepositoryClaGroupID, + UserID: *sender.Login, EventData: &events.RepositoryTransferredEventData{ RepositoryName: repoModel.RepositoryName, OldGithubOrgName: oldGithubOrg.OrganizationName, @@ -394,15 +398,16 @@ func (s *eventHandlerService) handleRepositoryTransferredAction(ctx context.Cont func (s *eventHandlerService) disableFailedTransferRepo(ctx context.Context, sender *github.User, f logrus.Fields, repoModel *models.GithubRepository, oldGithubOrg *models.GithubOrganization, newGithubOrg *models.GithubOrganization) error { log.WithFields(f).Warnf("can't proceed with repo transfer operation because the new org doesn't have autoenabled=true, disabling the repo : %s", repoModel.RepositoryName) - if err := s.githubRepo.DisableRepository(ctx, repoModel.RepositoryID); err != nil { + if err := s.gitV1Repository.GitHubDisableRepository(ctx, repoModel.RepositoryID); err != nil { return fmt.Errorf("disabling the repo : %s failed : %v", repoModel.RepositoryID, err) } // send event for the disabled repository. s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryDisabled, - ProjectID: repoModel.RepositoryProjectID, - UserID: *sender.Login, + EventType: events.RepositoryDisabled, + ProjectSFID: repoModel.RepositoryProjectSfid, + CLAGroupID: repoModel.RepositoryClaGroupID, + UserID: *sender.Login, EventData: &events.RepositoryDisabledEventData{ RepositoryName: repoModel.RepositoryName, }, @@ -418,7 +423,7 @@ func (s *eventHandlerService) disableFailedTransferRepo(ctx context.Context, sen func (s *eventHandlerService) notifyForGithubRepositoryTransferred(ctx context.Context, repoModel *models.GithubRepository, oldGithubOrg *models.GithubOrganization, newGithubOrg *models.GithubOrganization, success bool) error { subject := fmt.Sprintf("EasyCLA: Github Repository Was Transferred") - body, err := emails.RenderGithubRepositoryTransferredTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryTransferredTemplateParams{ + body, err := emails.RenderGithubRepositoryTransferredTemplate(s.emailService, repoModel.RepositoryClaGroupID, emails.GithubRepositoryTransferredTemplateParams{ GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: "CLA Manager", @@ -433,7 +438,7 @@ func (s *eventHandlerService) notifyForGithubRepositoryTransferred(ctx context.C return fmt.Errorf("rendering email template failed : %v", err) } - err = s.emailService.NotifyClaManagersForClaGroupID(ctx, repoModel.RepositoryProjectID, subject, body) + err = s.emailService.NotifyClaManagersForClaGroupID(ctx, repoModel.RepositoryClaGroupID, subject, body) return err } @@ -447,7 +452,7 @@ func (s *eventHandlerService) handleRepositoryArchivedAction(ctx context.Context return fmt.Errorf("missing repo id") } repositoryExternalID := strconv.FormatInt(*repo.ID, 10) - repoModel, err := s.githubRepo.GetRepositoryByGithubID(context.Background(), repositoryExternalID, true) + repoModel, err := s.gitV1Repository.GitHubGetRepositoryByGithubID(context.Background(), repositoryExternalID, true) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { log.WithFields(f).Warnf("event for non existing local repo : %s, nothing to do", *repo.FullName) @@ -460,7 +465,7 @@ func (s *eventHandlerService) handleRepositoryArchivedAction(ctx context.Context if s.sendEmail { subject := fmt.Sprintf("EasyCLA: Github Repository Was Archived") - body, err := emails.RenderGithubRepositoryArchivedTemplate(s.emailService, repoModel.RepositoryProjectID, emails.GithubRepositoryArchivedTemplateParams{ + body, err := emails.RenderGithubRepositoryArchivedTemplate(s.emailService, repoModel.RepositoryClaGroupID, emails.GithubRepositoryArchivedTemplateParams{ GithubRepositoryActionTemplateParams: emails.GithubRepositoryActionTemplateParams{ CommonEmailParams: emails.CommonEmailParams{ RecipientName: "CLA Manager", @@ -474,7 +479,7 @@ func (s *eventHandlerService) handleRepositoryArchivedAction(ctx context.Context return nil } - if err := s.emailService.NotifyClaManagersForClaGroupID(ctx, repoModel.RepositoryProjectID, subject, body); err != nil { + if err := s.emailService.NotifyClaManagersForClaGroupID(ctx, repoModel.RepositoryClaGroupID, subject, body); err != nil { log.WithFields(f).Warnf("notifying cla managers via email failed : %v", err) } diff --git a/cla-backend-go/v2/github_activity/service_test.go b/cla-backend-go/v2/github_activity/service_test.go index aa40e8352..18daf241d 100644 --- a/cla-backend-go/v2/github_activity/service_test.go +++ b/cla-backend-go/v2/github_activity/service_test.go @@ -30,7 +30,7 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryRenamedActio GetRepositoryByGithubID(gomock.Any(), "1", true). Return(&models.GithubRepository{ Enabled: true, - RepositoryExternalID: "1", + RepositoryExternalID: 1, RepositoryID: repoID, RepositoryName: repoName, RepositoryOrganizationName: "org1", @@ -109,7 +109,7 @@ func TestEventHandlerService_ProcessRepositoryEvent_HandleRepositoryTransferredA GetRepositoryByGithubID(gomock.Any(), "1", true). Return(&models.GithubRepository{ Enabled: true, - RepositoryExternalID: "1", + RepositoryExternalID: 1, RepositoryID: repoID, RepositoryName: repoName, RepositoryOrganizationName: oldOrgName, diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index e4ccf784b..f95f88046 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -8,7 +8,6 @@ import ( "fmt" "net/url" "sort" - "strconv" "strings" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -23,7 +22,7 @@ import ( v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" v1GithubOrg "github.com/communitybridge/easycla/cla-backend-go/github_organizations" - v1Repositories "github.com/communitybridge/easycla/cla-backend-go/repositories" + gitV1Repository "github.com/communitybridge/easycla/cla-backend-go/repositories" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/jinzhu/copier" ) @@ -40,23 +39,23 @@ func v2GithubOrganizationModel(in *v1Models.GithubOrganization) (*models.GithubO // Service contains functions of GithubOrganizations service type Service interface { GetGithubOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGithubOrganizations, error) - AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) + AddGithubOrganization(ctx context.Context, projectSFID string, input *models.GithubCreateOrganization) (*models.GithubOrganization, error) DeleteGithubOrganization(ctx context.Context, projectSFID string, githubOrgName string) error UpdateGithubOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error } type service struct { repo v1GithubOrg.RepositoryInterface - ghRepository v1Repositories.Repository + gitV1Repository gitV1Repository.RepositoryInterface ghService v1GithubOrg.ServiceInterface projectsCLAGroupService projects_cla_groups.Repository } // NewService creates a new githubOrganizations service -func NewService(repo v1GithubOrg.RepositoryInterface, ghRepository v1Repositories.Repository, projectsCLAGroupService projects_cla_groups.Repository, ghService v1GithubOrg.ServiceInterface) Service { +func NewService(repo v1GithubOrg.RepositoryInterface, gitV1Repository gitV1Repository.RepositoryInterface, projectsCLAGroupService projects_cla_groups.Repository, ghService v1GithubOrg.ServiceInterface) Service { return service{ repo: repo, - ghRepository: ghRepository, + gitV1Repository: gitV1Repository, projectsCLAGroupService: projectsCLAGroupService, ghService: ghService, } @@ -170,7 +169,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) log.WithFields(f).Debugf("loading github repositories from %d organizations for projectSFID: %s...", len(orgs.List), projectSFID) var repoList []*v1Models.GithubRepository for _, org := range orgs.List { - orgRepos, orgReposErr := s.ghRepository.GetRepositoriesByOrganizationName(ctx, org.OrganizationName) + orgRepos, orgReposErr := s.gitV1Repository.GitHubGetRepositoriesByOrganizationName(ctx, org.OrganizationName) if orgReposErr != nil || orgRepos == nil { if _, ok := orgReposErr.(*utils.GitHubRepositoryNotFound); ok { log.WithFields(f).Debug(orgReposErr) @@ -191,21 +190,26 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) continue } key := fmt.Sprintf("%s#%v", repo.RepositoryOrganizationName, repo.RepositoryExternalID) + + parentProjectModel, projectModelErr := v2ProjectService.GetClient().GetParentProjectModel(repo.RepositoryProjectSfid) + if projectModelErr != nil || parentProjectModel == nil { + log.WithFields(f).Warnf("unable to load parent for project: %s", repo.RepositoryProjectSfid) + return nil, projectModelErr + } + if _, ok := connectedRepo[key]; ok { - repoGithubID, err := strconv.ParseInt(repo.RepositoryExternalID, 10, 64) - if err != nil { - log.WithFields(f).WithError(err).Warn("repository github id is not integer") - } + rorg.Repositories = append(rorg.Repositories, &models.ProjectGithubRepository{ ConnectionStatus: utils.Connected, Enabled: repo.Enabled, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, - RepositoryGithubID: repoGithubID, - ClaGroupID: repo.RepositoryProjectID, - ProjectID: repo.ProjectSFID, - ParentProjectID: repo.RepositorySfdcID, + RepositoryGithubID: repo.RepositoryExternalID, + ClaGroupID: repo.RepositoryClaGroupID, + ProjectID: repo.RepositoryProjectSfid, + ParentProjectID: parentProjectModel.ID, }) + // delete it from connectedRepo array since we have processed it // connectedArray after this loop will contain repo for which github app have permission but // they are enabled in cla @@ -216,10 +220,11 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) Enabled: repo.Enabled, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, - ClaGroupID: repo.RepositoryProjectID, - ProjectID: repo.ProjectSFID, - ParentProjectID: repo.RepositorySfdcID, + ClaGroupID: repo.RepositoryClaGroupID, + ProjectID: repo.RepositoryProjectSfid, + ParentProjectID: parentProjectModel.ID, }) + if rorg.ConnectionStatus == utils.Connected { rorg.ConnectionStatus = utils.PartialConnection } @@ -254,7 +259,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) return out, nil } -func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, input *models.CreateGithubOrganization) (*models.GithubOrganization, error) { +func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, input *models.GithubCreateOrganization) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "v2.github_organizations.service.AddGitHubOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -264,7 +269,7 @@ func (s service) AddGithubOrganization(ctx context.Context, projectSFID string, "organizationName": utils.StringValue(input.OrganizationName), } - var in v1Models.CreateGithubOrganization + var in v1Models.GithubCreateOrganization err := copier.Copy(&in, input) if err != nil { log.WithFields(f).WithError(err).Warn("problem converting the github organization details") @@ -320,7 +325,7 @@ func (s service) DeleteGithubOrganization(ctx context.Context, projectSFID strin } log.WithFields(f).Debug("disabling repositories for github organization...") - err := s.ghRepository.DisableRepositoriesOfGithubOrganization(ctx, projectSFID, githubOrgName) + err := s.gitV1Repository.GitHubDisableRepositoriesOfOrganization(ctx, projectSFID, githubOrgName) if err != nil { log.WithFields(f).WithError(err).Warn("problem disabling repositories for github organization") return err diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 8b2164cb6..2e00e82f2 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -7,12 +7,13 @@ import ( "context" "errors" "fmt" - "github.com/communitybridge/easycla/cla-backend-go/company" - signatures1 "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" "regexp" "strconv" "strings" + "github.com/communitybridge/easycla/cla-backend-go/company" + signatures1 "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" + "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" @@ -21,6 +22,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/signatures" "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + gitV2Repositories "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -35,19 +37,21 @@ type Service interface { type service struct { usersRepository users.UserRepository gitlabRepository gitlab_organizations.RepositoryInterface - githubRepositories repositories.Repository + gitRepository repositories.RepositoryInterface + gitV2Repository gitV2Repositories.RepositoryInterface signaturesRepository signatures.SignatureRepository projectsCLAGroupsRepository projects_cla_groups.Repository companyRepository company.IRepository signatureRepository signatures.SignatureRepository } -func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, githubRepositories repositories.Repository, usersRepository users.UserRepository, signaturesRepository signatures.SignatureRepository, projectsCLAGroupsRepository projects_cla_groups.Repository, +func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRepository repositories.RepositoryInterface, gitV2Repository gitV2Repositories.RepositoryInterface, usersRepository users.UserRepository, signaturesRepository signatures.SignatureRepository, projectsCLAGroupsRepository projects_cla_groups.Repository, companyRepository company.IRepository, signatureRepository signatures.SignatureRepository) Service { return &service{ - usersRepository: usersRepository, gitlabRepository: gitlabRepository, - githubRepositories: githubRepositories, + gitRepository: gitRepository, + gitV2Repository: gitV2Repository, + usersRepository: usersRepository, signaturesRepository: signaturesRepository, projectsCLAGroupsRepository: projectsCLAGroupsRepository, companyRepository: companyRepository, @@ -156,42 +160,32 @@ func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeE repositoryPath := mergeEvent.Project.PathWithNamespace parts := strings.Split(repositoryPath, "/") organizationName := parts[0] + gitlabOrgs, err := s.gitlabRepository.GetGitlabOrganizationByName(ctx, organizationName) - if err != nil || len(gitlabOrgs.List) == 0 { + if err != nil || gitlabOrgs == nil { // try getting it with project name as well gitlabOrgs, err = s.gitlabRepository.GetGitlabOrganizationByName(ctx, mergeEvent.Project.Namespace) - if err != nil { + if err != nil || gitlabOrgs == nil { return nil, fmt.Errorf("gitlab org : %s doesn't exist : %v", organizationName, err) } } - if len(gitlabOrgs.List) == 0 { - return nil, fmt.Errorf("gitlab org : %s doesn't exist", organizationName) - } - - orgID := gitlabOrgs.List[0].OrganizationID - gitlabOrg, err := s.gitlabRepository.GetGitlabOrganization(ctx, orgID) + gitlabOrg, err := s.gitlabRepository.GetGitlabOrganization(ctx, gitlabOrgs.OrganizationID) if err != nil { - return nil, fmt.Errorf("fetching gitlab org : %s failed : %v", orgID, err) + return nil, fmt.Errorf("fetching gitlab org : %s failed : %v", gitlabOrgs.OrganizationID, err) } return gitlabOrg, nil } func (s service) getGitlabRepoByExternalID(ctx context.Context, orgName, gitlabRepoID string) (*models.GithubRepository, error) { - gitlabRepos, err := s.githubRepositories.GetRepositoriesByOrganizationName(ctx, orgName) - if err != nil { - return nil, fmt.Errorf("fetching gitlab repo for external id : %s, orgName : %s, failed : %v", gitlabRepoID, orgName, err) + gitlabRepo, err := s.gitV2Repository.GitLabGetRepositoryByName(ctx, orgName) + if err != nil || gitlabRepo == nil { + return nil, fmt.Errorf("unable to locate GitLab repo for external id : %s, orgName : %s, failed : %v", gitlabRepoID, orgName, err) } - if len(gitlabRepos) == 0 { - return nil, fmt.Errorf("no repositories found for orgName : %s", orgName) - } - - for _, gitlabRepo := range gitlabRepos { - if gitlabRepo.RepositoryExternalID == gitlabRepoID && gitlabRepo.RepositoryType == "gitlab" { - return gitlabRepo, nil - } + if gitlabRepo.RepositoryExternalID == gitlabRepoID && gitlabRepo.RepositoryType == "gitlab" { + return gitlabRepo.ToGitHubModel(), nil } return nil, fmt.Errorf("no repositories found for orgName : %s and gitlab external id : %s", orgName, gitlabRepoID) diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index 74eef308a..fd77b482b 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -4,11 +4,12 @@ package gitlab_activity import ( + "testing" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/xanzy/go-gitlab" - "testing" ) func TestIsUserApprovedForSignature(t *testing.T) { @@ -22,83 +23,82 @@ func TestIsUserApprovedForSignature(t *testing.T) { Username: "one", } - testCases := []struct{ - name string + testCases := []struct { + name string signature *models.Signature - expected bool + expected bool }{ { - name: "nothing matched", - signature : &models.Signature{}, + name: "nothing matched", + signature: &models.Signature{}, }, { name: "email approval list non empty no match", - signature : &models.Signature{ + signature: &models.Signature{ EmailApprovalList: []string{"three@example.com"}, }, }, { name: "email approval list match", - signature : &models.Signature{ + signature: &models.Signature{ EmailApprovalList: []string{"one@example.com"}, }, expected: true, }, { name: "domain approval list match no match", - signature : &models.Signature{ + signature: &models.Signature{ DomainApprovalList: []string{"*.foo.com"}, }, expected: false, }, { name: "domain approval list match domain star", - signature : &models.Signature{ + signature: &models.Signature{ DomainApprovalList: []string{"*.example.com"}, }, expected: true, }, { name: "domain approval list match domain star globbing", - signature : &models.Signature{ + signature: &models.Signature{ DomainApprovalList: []string{"*example.com"}, }, expected: true, }, { name: "domain approval list match domain star dot", - signature : &models.Signature{ + signature: &models.Signature{ DomainApprovalList: []string{".example.com"}, }, expected: true, }, { name: "gitlab username approval list no match", - signature : &models.Signature{ + signature: &models.Signature{ GitlabUsernameApprovalList: []string{"two"}, }, expected: false, }, { name: "gitlab username approval list match", - signature : &models.Signature{ + signature: &models.Signature{ GitlabUsernameApprovalList: []string{"one"}, }, expected: true, }, } - for _, tc := range testCases{ + for _, tc := range testCases { t.Run(tc.name, func(tt *testing.T) { result := IsUserApprovedForSignature(logrus.Fields{}, tc.signature, userModel, gitlabUser) - if tc.expected{ + if tc.expected { assert.True(tt, result) - }else{ + } else { assert.False(tt, result) } }) } - -} \ No newline at end of file +} diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 0c15a6f3e..16f28eed3 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -37,7 +37,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "gitlab_organizations.handlers.GitlabOrganizationsGetProjectGitlabOrganizationsHandler", + "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsGetProjectGitlabOrganizationsHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -77,7 +77,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "Gitlab_organization.handlers.GitlabOrganizationsAddProjectGitlabOrganizationHandler", + "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsAddProjectGitlabOrganizationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -183,7 +183,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "github_organization.handlers.GithubOrganizationsDeleteProjectGithubOrganizationHandler", + "functionName": "v2.gitlab_organizations.handlers.GithubOrganizationsDeleteProjectGithubOrganizationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "orgName": params.OrgName, diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 27e8e032b..83f709404 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -32,10 +32,10 @@ const ( // RepositoryInterface is interface for gitlab org data model type RepositoryInterface interface { - AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.CreateGitlabOrganization) (*models2.GitlabOrganization, error) + AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganizations, error) + GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganization, error) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error @@ -57,13 +57,14 @@ func NewRepository(awsSession *session.Session, stage string) RepositoryInterfac } } -func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.CreateGitlabOrganization) (*models2.GitlabOrganization, error) { +func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) { + gitLabOrganizationName := utils.StringValue(input.OrganizationName) f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.AddGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "projectSFID": projectSFID, - "organizationName": utils.StringValue(input.OrganizationName), + "organizationName": gitLabOrganizationName, "autoEnabled": utils.BoolValue(input.AutoEnabled), "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), } @@ -71,19 +72,26 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS // First, let's check to see if we have an existing gitlab organization with the same name existingRecord, getErr := repo.GetGitlabOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) if getErr != nil { - log.WithFields(f).WithError(getErr).Debug("unable to locate existing github organization by name") - } + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab organization by name %s - ok to create a new record", gitLabOrganizationName) + } + + if existingRecord != nil { + log.WithFields(f).Debugf("An existing GitLab organization with name %s exists in our database", gitLabOrganizationName) + // If everything matches... + if projectSFID == existingRecord.ProjectSFID { + log.WithFields(f).Debug("Existing github organization with same SFID - should be able to update it") + enabledFlag := true + updateErr := repo.UpdateGitlabOrganization(ctx, projectSFID, gitLabOrganizationName, + utils.BoolValue(input.AutoEnabled), input.AutoEnabledClaGroupID, utils.BoolValue(input.BranchProtectionEnabled), &enabledFlag) + if updateErr != nil { + return nil, updateErr + } - if existingRecord != nil && len(existingRecord.List) > 0 { - log.WithFields(f).Debugf("Existing github organization exists in our database, count: %d", len(existingRecord.List)) - if len(existingRecord.List) > 1 { - log.WithFields(f).Warning("more than one github organization with the same name in the database") - } - if parentProjectSFID == existingRecord.List[0].OrganizationSfid { - log.WithFields(f).Debug("Existing github organization with same parent SFID - should be able to update it") - } else { - log.WithFields(f).Debug("Existing github organization with different parent SFID - won't be able to update it - will return conflict") + // Return the updated record + return repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName) } + + log.WithFields(f).Debug("Existing github organization with different project SFID - won't be able to update it - will return conflict") return nil, fmt.Errorf("record already exists") } @@ -146,7 +154,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS return ToModel(gitlabOrg), nil } -// GetGitlabOrganizations get github organizations based on the project SFID +// GetGitlabOrganizations get GitLab organizations based on the project SFID func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.GetGitHubOrganizations", @@ -202,8 +210,8 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s return &models2.GitlabOrganizations{List: gitlabOrgList}, nil } -// GetGitlabOrganizationByName get github organization by name -func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganizations, error) { +// GetGitlabOrganizationByName get GitLab organization by name +func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v1.github_organizations.repository.GetGitHubOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -238,10 +246,9 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOr } if len(results.Items) == 0 { log.WithFields(f).Debug("Unable to find github organization by name - no results") - return &models2.GitlabOrganizations{ - List: []*models2.GitlabOrganization{}, - }, nil + return nil, nil } + var resultOutput []*GitlabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { @@ -249,8 +256,9 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOr return nil, err } - ghOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) - return &models2.GitlabOrganizations{List: ghOrgList}, nil + log.WithFields(f).Debug("building response model...") + gitlabOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) + return gitlabOrgList[0], nil } // GetGitlabOrganization by organization name @@ -357,19 +365,16 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID } _, currentTime := utils.CurrentTime() - gitlabOrgs, lookupErr := repo.GetGitlabOrganizationByName(ctx, organizationName) + gitlabOrg, lookupErr := repo.GetGitlabOrganizationByName(ctx, organizationName) if lookupErr != nil { log.WithFields(f).Warnf("error looking up Gitlab organization by name, error: %+v", lookupErr) return lookupErr } - if gitlabOrgs == nil || len(gitlabOrgs.List) == 0 { - lookupErr := errors.New("unable to lookup Gitlab organization by name") - log.WithFields(f).Warnf("error looking up Gitlab organization, error: %+v", lookupErr) - return lookupErr + if gitlabOrg == nil { + log.WithFields(f).Warn("error looking up Gitlab organization - no results") + return errors.New("unable to lookup Gitlab organization by name") } - gitlabOrg := gitlabOrgs.List[0] - expressionAttributeNames := map[string]*string{ "#A": aws.String("auto_enabled"), "#C": aws.String("auto_enabled_cla_group_id"), diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 0f23e7beb..a12bbe51e 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -13,7 +13,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/config" "github.com/go-openapi/strfmt" - v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + //v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gitlab" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -25,8 +25,8 @@ import ( // Service contains functions of GitlabOrganizations service type Service interface { - GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGitlabOrganizations, error) - AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.CreateGitlabOrganization) (*models.GitlabOrganization, error) + GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) + AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error @@ -39,7 +39,7 @@ type service struct { claGroupRepository projects_cla_groups.Repository } -// NewService creates a new githubOrganizations service +// NewService creates a new gitlab organization service func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups.Repository) Service { return service{ repo: repo, @@ -112,7 +112,7 @@ func (s service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganiz return ToModel(dbModel), nil } -func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.CreateGitlabOrganization) (*models.GitlabOrganization, error) { +func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.AddGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -122,7 +122,6 @@ func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, "organizationName": utils.StringValue(input.OrganizationName), } - log.WithFields(f).Debug("looking up project in project service...") psc := v2ProjectService.GetClient() project, err := psc.GetProject(projectSFID) if err != nil { @@ -140,24 +139,25 @@ func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") - log.WithFields(f).Debug("adding github organization...") + log.WithFields(f).Debug("adding gitlab organization...") resp, err := s.repo.AddGitlabOrganization(ctx, parentProjectSFID, projectSFID, input) if err != nil { - log.WithFields(f).WithError(err).Warn("problem adding github organization for project") + log.WithFields(f).WithError(err).Warn("problem adding gitlab organization for project") return nil, err } + log.WithFields(f).Debugf("created GitLab organization with ID: %s", resp.OrganizationID) - return resp, nil + return s.GetGitlabOrganizations(ctx, projectSFID) } -func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.ProjectGitlabOrganizations, error) { +func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } - // Load the GitHub Organization and Repository details - result will be missing CLA Group info and ProjectSFID details + // Load the GitLab Organization and Repository details - result will be missing CLA Group info and ProjectSFID details log.WithFields(f).Debugf("loading Gitlab organizations for projectSFID: %s", projectSFID) orgs, err := s.repo.GetGitlabOrganizations(ctx, projectSFID) if err != nil { @@ -183,23 +183,23 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) log.WithFields(f).Debug("located parentProjectID...") // Our response model - out := &models.ProjectGitlabOrganizations{ - List: make([]*models.ProjectGitlabOrganization, 0), + out := &models.GitlabProjectOrganizations{ + List: make([]*models.GitlabProjectOrganization, 0), } - // Next, we need to load a bunch of additional data for the response including the github status (if it's still connected/live, not renamed/moved), the CLA Group details, etc. + // Next, we need to load a bunch of additional data for the response including the GitLab status (if it's still connected/live, not renamed/moved), the CLA Group details, etc. - // A temp data model for holding the intermediate results - type gitlabRepoInfo struct { - orgName string - repoInfo *v1Models.GithubRepositoryInfo - } + //// A temp data model for holding the intermediate results + //type gitlabRepoInfo struct { + // orgName string + // repoInfo *v1Models.GitLabRepositoryInfo + //} - orgmap := make(map[string]*models.ProjectGitlabOrganization) + orgmap := make(map[string]*models.GitlabProjectOrganization) for _, org := range orgs.List { autoEnabledCLAGroupName := "" if org.AutoEnabledClaGroupID != "" { - log.WithFields(f).Debugf("Loading CLA Group by ID: %s to obtain the name for GitHub auth enabled CLA Group response", org.AutoEnabledClaGroupID) + log.WithFields(f).Debugf("Loading CLA Group by ID: %s to obtain the name for GitLab auth enabled CLA Group response", org.AutoEnabledClaGroupID) claGroupMode, claGroupLookupErr := s.claGroupRepository.GetCLAGroup(ctx, org.AutoEnabledClaGroupID) if claGroupLookupErr != nil { log.WithFields(f).WithError(claGroupLookupErr).Warnf("Unable to lookup CLA Group by ID: %s", org.AutoEnabledClaGroupID) @@ -216,12 +216,12 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) } installationURL := buildInstallationURL(org.OrganizationID, orgDetailed.AuthState) - rorg := &models.ProjectGitlabOrganization{ + rorg := &models.GitlabProjectOrganization{ AutoEnabled: org.AutoEnabled, AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, AutoEnabledCLAGroupName: autoEnabledCLAGroupName, GitlabOrganizationName: org.OrganizationName, - Repositories: make([]*models.ProjectGithubRepository, 0), + Repositories: make([]*models.GitlabProjectRepository, 0), InstallationURL: installationURL, } @@ -279,7 +279,7 @@ func (s service) DeleteGitlabOrganization(ctx context.Context, projectSFID strin log.WithFields(f).Debugf("retrieved parent of project sfid : %s -> %s", projectSFID, parentProjectSFID) // Todo: Enable this when the repositories are implemented - //err := s.ghRepository.DisableRepositoriesOfGithubOrganization(ctx, parentProjectSFID, gitlabOrgName) + //err := s.ghRepository.GitHubDisableRepositoriesOfOrganization(ctx, parentProjectSFID, gitlabOrgName) //if err != nil { // log.WithFields(f).Warnf("problem disabling repositories for github organizations, error: %+v", projErr) // return err diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go new file mode 100644 index 000000000..7e2445dd5 --- /dev/null +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -0,0 +1,138 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package repositories + +import ( + "context" + "strconv" + + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + repoModels "github.com/communitybridge/easycla/cla-backend-go/repositories" +) + +// GitLabAddRepository service function +func (s *Service) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) { + dbModel, err := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, input) + if err != nil { + return nil, err + } + + return dbModelToGitLabRepository(dbModel) +} + +// GitLabGetRepository service function +func (s *Service) GitLabGetRepository(ctx context.Context, repositoryID string) (*v2Models.GitlabRepository, error) { + dbModel, err := s.gitV2Repository.GitLabGetRepository(ctx, repositoryID) + if err != nil { + return nil, err + } + + return dbModelToGitLabRepository(dbModel) +} + +// GitLabGetRepositoryByName service function +func (s *Service) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*v2Models.GitlabRepository, error) { + dbModel, err := s.gitV2Repository.GitLabGetRepositoryByName(ctx, repositoryName) + if err != nil { + return nil, err + } + + return dbModelToGitLabRepository(dbModel) +} + +// GitLabGetRepositoriesByProjectSFID service function +func (s *Service) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabListRepositories, error) { + dbModel, err := s.gitV2Repository.GitHubGetRepositoriesByProjectSFID(ctx, projectSFID) + if err != nil { + return nil, err + } + + responses, err := dbModelsToGitLabRepositories(dbModel) + if err != nil { + return nil, err + } + + return &v2Models.GitlabListRepositories{ + List: responses, + }, nil +} + +// GitLabGetRepositoriesByCLAGroup service function +func (s *Service) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) { + var dbModels []*repoModels.RepositoryDBModel + var err error + if enabled { + dbModels, err = s.gitV2Repository.GitHubGetRepositoriesByCLAGroupEnabled(ctx, claGroupID) + } else { + dbModels, err = s.gitV2Repository.GitHubGetRepositoriesByCLAGroupDisabled(ctx, claGroupID) + } + if err != nil { + return nil, err + } + + responses, err := dbModelsToGitLabRepositories(dbModels) + if err != nil { + return nil, err + } + + return &v2Models.GitlabListRepositories{ + List: responses, + }, nil +} + +// GitLabEnableRepository service function +func (s *Service) GitLabEnableRepository(ctx context.Context, repositoryID string) error { + return s.gitV2Repository.GitLabEnableRepositoryByID(ctx, repositoryID) +} + +// GitLabDisableRepository service function +func (s *Service) GitLabDisableRepository(ctx context.Context, repositoryID string) error { + return s.gitV2Repository.GitLabDisableRepositoryByID(ctx, repositoryID) +} + +// GitLabDisableCLAGroupRepositories service function +func (s *Service) GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { + return s.gitV2Repository.GitLabDisableCLAGroupRepositories(ctx, claGroupID) +} + +// dbModelToGitLabRepository converts the database model to a v2 response model +func dbModelToGitLabRepository(dbModel *repoModels.RepositoryDBModel) (*v2Models.GitlabRepository, error) { + + gitLabExternalID, err := strconv.ParseInt(dbModel.RepositoryExternalID, 10, 64) + if err != nil { + return nil, err + } + + response := v2Models.GitlabRepository{ + RepositoryID: dbModel.RepositoryID, // Internal database ID for this repository record + RepositoryProjectSfid: dbModel.ProjectSFID, // Project SFID + RepositoryClaGroupID: dbModel.RepositoryCLAGroupID, // CLA Group ID + RepositoryExternalID: gitLabExternalID, // GitLab unique gitV1Repository ID + RepositoryName: dbModel.RepositoryName, // Short repository name + RepositoryOrganizationName: dbModel.RepositoryOrganizationName, // Group/Organization name + RepositoryURL: dbModel.RepositoryURL, // full url + RepositoryType: dbModel.RepositoryType, // gitlab + Enabled: dbModel.Enabled, // Enabled flag + DateCreated: dbModel.DateCreated, // date created + DateModified: dbModel.DateModified, // date updated + Note: dbModel.Note, // Optional note + Version: dbModel.Version, // record version + } + + return &response, nil +} + +// dbModelsToGitLabRepositories converts the slice of database models to a slice of v2 response model +func dbModelsToGitLabRepositories(dbModels []*repoModels.RepositoryDBModel) ([]*v2Models.GitlabRepository, error) { + var responses []*v2Models.GitlabRepository + for _, dbModel := range dbModels { + response, err := dbModelToGitLabRepository(dbModel) + if err != nil { + return nil, err + } + // Add to the list + responses = append(responses, response) + } + return responses, nil +} diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index bf8ba337d..4364ec18c 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -9,6 +9,8 @@ import ( "fmt" "strings" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_repositories" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" "github.com/sirupsen/logrus" @@ -29,14 +31,14 @@ import ( ) // Configure establishes the middleware handlers for the repository service -func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service) { +func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventService events.Service) { // nolint api.GithubRepositoriesGetProjectGithubRepositoriesHandler = github_repositories.GetProjectGithubRepositoriesHandlerFunc( func(params github_repositories.GetProjectGithubRepositoriesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubRepositoriesGetProjectGithubRepositoriesHandler", + "functionName": "v2.repositories.handlers.GitHubRepositoriesGetProjectGithubRepositoriesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -51,7 +53,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.ListProjectRepositories(ctx, params.ProjectSFID) + result, err := service.GitHubListProjectRepositories(ctx, params.ProjectSFID) if err != nil { if strings.ContainsAny(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("repository not found for projectSFID: %s", params.ProjectSFID) @@ -66,7 +68,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - response := &models.ListGithubRepositories{} + response := &models.GithubListRepositories{} err = copier.Copy(response, result) if err != nil { msg := fmt.Sprintf("problem converting response for projectSFID: %s", params.ProjectSFID) @@ -84,7 +86,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubRepositoriesAddProjectGithubRepositoryHandler", + "functionName": "v2.repositories.handlers.GitHubRepositoriesAddProjectGithubRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -114,7 +116,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } log.WithFields(f).Debugf("Adding GitHub repositories for project: %s", params.ProjectSFID) - results, err := service.AddGithubRepositories(ctx, params.ProjectSFID, params.GithubRepositoryInput) + results, err := service.GitHubAddRepositories(ctx, params.ProjectSFID, params.GithubRepositoryInput) if err != nil { if _, ok := err.(*utils.GitHubRepositoryExists); ok { msg := fmt.Sprintf("unable to add repository - repository already exists for projectSFID: %s", params.ProjectSFID) @@ -154,7 +156,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) } - v2Response := &models.ListGithubRepositories{} + v2Response := &models.GithubListRepositories{} v2Response.List = v2ResponseList return github_repositories.NewAddProjectGithubRepositoryOK().WithPayload(v2Response) @@ -166,7 +168,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubRepositoriesDeleteProjectGithubRepositoryHandler", + "functionName": "v2.repositories.handlers.GitHubRepositoriesDeleteProjectGithubRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -182,7 +184,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseForbidden(reqID, msg)) } - ghRepo, err := service.GetRepository(ctx, params.RepositoryID) + ghRepo, err := service.GitHubGetRepository(ctx, params.RepositoryID) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { msg := fmt.Sprintf("repository not found for projectSFID: %s", params.ProjectSFID) @@ -197,7 +199,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - err = service.DisableRepository(ctx, params.RepositoryID) + err = service.GitHubDisableRepository(ctx, params.RepositoryID) if err != nil { msg := fmt.Sprintf("problem disabling repository for projectSFID: %s, error: %+v", params.ProjectSFID, err) log.WithFields(f).WithError(err).Warn(msg) @@ -208,7 +210,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, ProjectSFID: params.ProjectSFID, - ProjectID: ghRepo.RepositoryProjectID, + CLAGroupID: ghRepo.RepositoryClaGroupID, LfUsername: authUser.UserName, EventData: &events.RepositoryDisabledEventData{ RepositoryName: ghRepo.RepositoryName, @@ -224,7 +226,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubRepositoriesGetProjectGithubRepositoryBranchProtectionHandler", + "functionName": "v2.repositories.handlers.GitHubRepositoriesGetProjectGithubRepositoryBranchProtectionHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -247,7 +249,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. branchName = *params.BranchName } - protectedBranch, err := service.GetProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID, branchName) + protectedBranch, err := service.GitHubGetProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID, branchName) if err != nil { if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { msg := fmt.Sprintf("unable to locatate branch protection projectSFID: %s, repository: %s", params.ProjectSFID, params.RepositoryID) @@ -285,7 +287,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "GitHubRepositoriesUpdateProjectGitHubRepositoryBranchProtectionHandler", + "functionName": "v2.repositories.handlers.GitHubRepositoriesUpdateProjectGitHubRepositoryBranchProtectionHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, @@ -301,9 +303,9 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseForbidden(reqID, msg)) } - protectedBranch, err := service.UpdateProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID, params.GithubRepositoryBranchProtectionInput) + protectedBranch, err := service.GitHubUpdateProtectedBranch(ctx, params.ProjectSFID, params.RepositoryID, params.GithubRepositoryBranchProtectionInput) if err != nil { - log.Warnf("update protected branch failed for repo %s : %v", params.RepositoryID, err) + log.Warnf("update protected branch failed for gitV1Repository %s : %v", params.RepositoryID, err) if _, ok := err.(*utils.GitHubRepositoryNotFound); ok { msg := fmt.Sprintf("unable to update branch protection for projectSFID: %s, repository: %s", params.ProjectSFID, params.RepositoryID) log.WithFields(f).WithError(err).Warn(msg) @@ -338,7 +340,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) } - repoModel, repoErr := service.GetRepository(ctx, params.RepositoryID) + repoModel, repoErr := service.GitHubGetRepository(ctx, params.RepositoryID) if repoErr != nil { msg := fmt.Sprintf("problem fetching the repository for projectSFID: %s, with repository: %s, error: %+v", params.ProjectSFID, params.RepositoryID, err) log.WithFields(f).WithError(repoErr).Warning(msg) @@ -360,4 +362,163 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. return github_repositories.NewGetProjectGithubRepositoryBranchProtectionOK().WithPayload(protectedBranch) }) + + api.GitlabRepositoriesGetProjectGitLabRepositoriesHandler = gitlab_repositories.GetProjectGitLabRepositoriesHandlerFunc( + func(params gitlab_repositories.GetProjectGitLabRepositoriesParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "v2.repositories.handlers.GitlabRepositoriesGetProjectGitLabRepositoriesHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to Get GitLab Repositories with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_repositories.NewGetProjectGitLabRepositoriesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + result, err := service.GitLabGetRepositoriesByProjectSFID(ctx, params.ProjectSFID) + if err != nil { + if strings.ContainsAny(err.Error(), "getProjectNotFound") { + msg := fmt.Sprintf("repository not found for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewGetProjectGitLabRepositoriesNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + msg := fmt.Sprintf("problem looking up repositories for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewGetProjectGitLabRepositoriesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + response := &models.GitlabListRepositories{} + err = copier.Copy(response, result) + if err != nil { + msg := fmt.Sprintf("problem converting response for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewGetProjectGitLabRepositoriesInternalServerError().WithXRequestID(reqID).WithPayload(utils.ErrorResponseInternalServerErrorWithError(reqID, msg, err)) + } + + return gitlab_repositories.NewGetProjectGitLabRepositoriesOK().WithPayload(response) + }) + + api.GitlabRepositoriesAddProjectGitLabRepositoryHandler = gitlab_repositories.AddProjectGitLabRepositoryHandlerFunc( + func(params gitlab_repositories.AddProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "v2.repositories.handlers.GitlabRepositoriesAddProjectGitLabRepositoryHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "repositoryGitLabID": utils.Int64Value(params.GitlabAddRepository.RepositoryExternalID), + "repositoryName": utils.StringValue(params.GitlabAddRepository.RepositoryName), + "repositoryURL": utils.StringValue(params.GitlabAddRepository.RepositoryURL), + "repositoryOrganizationName": utils.StringValue(params.GitlabAddRepository.RepositoryOrganizationName), + "repositoryCLAGroupID": utils.StringValue(params.GitlabAddRepository.RepositoryClaGroupID), + "repositoryProjectSFID": utils.StringValue(params.GitlabAddRepository.RepositoryProjectSfid), + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to Add GitLab Repositories with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_repositories.NewAddProjectGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + // If no repository GitLab ID values provided... + // RepositoryGitlabID - provided by the older retool UI which provides only one value + // RepositoryGitlabIds - provided by new PCC which passes multiple values + if params.GitlabAddRepository.RepositoryExternalID == nil { + msg := "missing repository GitLab ID value" + return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + } + + log.WithFields(f).Debugf("Adding GitLab repository for project: %s", params.ProjectSFID) + result, err := service.GitLabAddRepository(ctx, params.ProjectSFID, params.GitlabAddRepository) + if err != nil { + if _, ok := err.(*utils.GitLabRepositoryExists); ok { + msg := fmt.Sprintf("unable to add repository - repository with name: %s already exists for projectSFID: %s", utils.StringValue(params.GitlabAddRepository.RepositoryName), params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewAddProjectGitLabRepositoryConflict().WithXRequestID(reqID).WithPayload(utils.ErrorResponseConflictWithError(reqID, msg, err)) + } + msg := fmt.Sprintf("problem adding GitLab repositories for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + // Log the event + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryAdded, + ProjectSFID: params.ProjectSFID, + CLAGroupID: utils.StringValue(params.GitlabAddRepository.RepositoryClaGroupID), + LfUsername: authUser.UserName, + EventData: &events.RepositoryAddedEventData{ + RepositoryName: utils.StringValue(params.GitlabAddRepository.RepositoryName), + }, + }) + + return gitlab_repositories.NewAddProjectGitLabRepositoryOK().WithPayload(result) + }) + + api.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler = gitlab_repositories.DeleteProjectGitLabRepositoryHandlerFunc( + func(params gitlab_repositories.DeleteProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) + ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + f := logrus.Fields{ + "functionName": "v2.repositories.handlers.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "repositoryID": params.RepositoryID, + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to Delete Gitlab Repositories with Project scope of %s", + authUser.UserName, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_repositories.NewDeleteProjectGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + } + + ghRepo, err := service.GitLabGetRepository(ctx, params.RepositoryID) + if err != nil { + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + msg := fmt.Sprintf("repository not found for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewDeleteProjectGitLabRepositoryNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + msg := fmt.Sprintf("problem looking up repository for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewDeleteProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + err = service.GitLabDisableRepository(ctx, params.RepositoryID) + if err != nil { + msg := fmt.Sprintf("problem disabling repository for projectSFID: %s, error: %+v", params.ProjectSFID, err) + log.WithFields(f).WithError(err).Warn(msg) + return gitlab_repositories.NewDeleteProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryDisabled, + ProjectSFID: params.ProjectSFID, + CLAGroupID: ghRepo.RepositoryClaGroupID, + LfUsername: authUser.UserName, + EventData: &events.RepositoryDisabledEventData{ + RepositoryName: ghRepo.RepositoryName, + }, + }) + + return gitlab_repositories.NewDeleteProjectGitLabRepositoryNoContent().WithXRequestID(reqID) + }) } diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go new file mode 100644 index 000000000..df24ca1b6 --- /dev/null +++ b/cla-backend-go/v2/repositories/repository.go @@ -0,0 +1,465 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package repositories + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go/service/dynamodb/expression" + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + repoModels "github.com/communitybridge/easycla/cla-backend-go/repositories" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/gofrs/uuid" + "github.com/sirupsen/logrus" +) + +// RepositoryInterface interface defines the functions for the GitLab repository data model +type RepositoryInterface interface { + GitLabGetRepository(ctx context.Context, repositoryID string) (*repoModels.RepositoryDBModel, error) + GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*repoModels.RepositoryDBModel, error) + GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) + GitHubGetRepositoriesByCLAGroupEnabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) + GitHubGetRepositoriesByCLAGroupDisabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) + GitHubGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) ([]*repoModels.RepositoryDBModel, error) + GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*repoModels.RepositoryDBModel, error) + GitLabEnableRepositoryByID(ctx context.Context, repositoryID string) error + GitLabDisableRepositoryByID(ctx context.Context, repositoryID string) error + GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error +} + +// Repository object/struct +type Repository struct { + stage string + dynamoDBClient *dynamodb.DynamoDB + repositoryTableName string + gitLabOrgTableName string +} + +// NewRepository creates a new instance of the GitLab repository service +func NewRepository(awsSession *session.Session, stage string) *Repository { + return &Repository{ + stage: stage, + dynamoDBClient: dynamodb.New(awsSession), + repositoryTableName: fmt.Sprintf("cla-%s-repositories", stage), + gitLabOrgTableName: fmt.Sprintf("cla-%s-gitlab-orgs", stage), + } +} + +// GitLabGetRepository returns the database model for the internal repository ID +func (r *Repository) GitLabGetRepository(ctx context.Context, repositoryID string) (*repoModels.RepositoryDBModel, error) { + f := logrus.Fields{ + "functionName": "v2.repositories.repositories.GitLabGetRepository", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "repositoryID": repositoryID, + } + + result, err := r.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ + TableName: aws.String(r.repositoryTableName), + Key: map[string]*dynamodb.AttributeValue{ + "repository_id": { + S: aws.String(repositoryID), + }, + }, + }) + + if err != nil { + log.WithFields(f).WithError(err).Warn("problem querying using repository ID") + return nil, err + } + if len(result.Item) == 0 { + msg := fmt.Sprintf("repository with ID: %s does not exist", repositoryID) + log.WithFields(f).Warn(msg) + return nil, &utils.GitHubRepositoryNotFound{ + Message: msg, + } + } + + // Decode the results into a model + var out repoModels.RepositoryDBModel + err = dynamodbattribute.UnmarshalMap(result.Item, &out) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem unmarshalling database repository response") + return nil, err + } + + return &out, nil +} + +// GitLabGetRepositoryByName returns the database model for the specified repository +func (r *Repository) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryNameColumn).Equal(expression.Value(repositoryName)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) + record, err := r.getRepositoryWithConditionFilter(ctx, condition, filter, repoModels.RepositoryNameIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + RepositoryName: repositoryName, + } + } + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabDuplicateRepositoriesFound); ok { + return nil, &utils.GitLabDuplicateRepositoriesFound{ + RepositoryName: repositoryName, + } + } + // Some other error + return nil, err + } + + return record, nil +} + +// GitHubGetRepositoriesByCLAGroup returns the database models for the specified CLA Group ID +func (r *Repository) GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryCLAGroupIDColumn).Equal(expression.Value(claGroupID)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryProjectIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + CLAGroupID: claGroupID, + } + } + + // Some other error + return nil, err + } + + return records, nil +} + +// GitHubGetRepositoriesByCLAGroupEnabled returns the database models for the specified CLA Group ID that are enabled +func (r *Repository) GitHubGetRepositoriesByCLAGroupEnabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryCLAGroupIDColumn).Equal(expression.Value(claGroupID)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)). + And(expression.Name(repoModels.RepositoryEnabledColumn).Equal(expression.Value(true))) + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryProjectIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + CLAGroupID: claGroupID, + } + } + + // Some other error + return nil, err + } + + return records, nil +} + +// GitHubGetRepositoriesByCLAGroupDisabled returns the database models for the specified CLA Group ID that are disabled +func (r *Repository) GitHubGetRepositoriesByCLAGroupDisabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryCLAGroupIDColumn).Equal(expression.Value(claGroupID)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)). + And(expression.Name(repoModels.RepositoryEnabledColumn).Equal(expression.Value(false))) + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryProjectIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + CLAGroupID: claGroupID, + } + } + + // Some other error + return nil, err + } + + return records, nil +} + +// GitHubGetRepositoriesByProjectSFID returns a list of repositories associated with the specified project +func (r *Repository) GitHubGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryProjectIDColumn).Equal(expression.Value(projectSFID)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) + + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryProjectSFIDIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + ProjectSFID: projectSFID, + } + } + + // Some other error + return nil, err + } + + return records, nil +} + +// GitLabAddRepository creates a new entry in the repositories table using the specified input parameters +func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*repoModels.RepositoryDBModel, error) { + f := logrus.Fields{ + "functionName": "v2.repositories.repositories.GitHubAddRepositories", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "repositoryExternalID": utils.Int64Value(input.RepositoryExternalID), + "repositoryURL": utils.StringValue(input.RepositoryURL), + "repositoryName": utils.StringValue(input.RepositoryName), + "repositoryType": utils.GitLabLower, + "repositoryCLAGroupID": utils.StringValue(input.RepositoryClaGroupID), + "repositoryProjectSFID": utils.StringValue(input.RepositoryProjectSfid), + "repositoryOrganizationName": utils.StringValue(input.RepositoryOrganizationName), + } + + // Check first to see if the repository already exists + _, err := r.GitLabGetRepositoryByName(ctx, utils.StringValue(input.RepositoryName)) + if err != nil { + // Expecting Not found - no issue if not found - all other error we throw + if _, ok := err.(*utils.GitLabRepositoryNotFound); !ok { + return nil, err + } + } else { + return nil, &utils.GitLabRepositoryExists{ + Message: fmt.Sprintf("GitLab repository with name: %s has alerady been registered", utils.StringValue(input.RepositoryName)), + RepositoryName: "", + Err: nil, + } + } + + _, currentTime := utils.CurrentTime() + repoID, err := uuid.NewV4() + if err != nil { + return nil, err + } + + repository := &repoModels.RepositoryDBModel{ + RepositoryID: repoID.String(), // internal ID that we assign + RepositorySfdcID: projectSFID, + ProjectSFID: projectSFID, + DateCreated: currentTime, + DateModified: currentTime, + RepositoryExternalID: strconv.FormatInt(utils.Int64Value(input.RepositoryExternalID), 10), + RepositoryName: utils.StringValue(input.RepositoryName), + RepositoryURL: utils.StringValue(input.RepositoryURL), + RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), // gitlab group/organization + RepositoryCLAGroupID: utils.StringValue(input.RepositoryClaGroupID), + RepositoryType: utils.GitLabLower, // should always be gitlab + Enabled: true, // default is enabled + Note: fmt.Sprintf("created on %s", currentTime), + Version: "v1", + } + av, err := dynamodbattribute.MarshalMap(repository) + if err != nil { + log.WithFields(f).Warnf("problem marshalling the input, error: %+v", err) + return nil, err + } + + _, err = r.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ + Item: av, + TableName: aws.String(r.repositoryTableName), + }) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to add github repository") + return nil, err + } + + return repository, nil +} + +// GitLabEnableRepositoryByID enables the specified repository +func (r *Repository) GitLabEnableRepositoryByID(ctx context.Context, repositoryID string) error { + return r.setRepositoryEnabledValue(ctx, repositoryID, true) +} + +// GitLabDisableRepositoryByID disables the specified repository +func (r *Repository) GitLabDisableRepositoryByID(ctx context.Context, repositoryID string) error { + return r.setRepositoryEnabledValue(ctx, repositoryID, false) +} + +// GitLabEnableCLAGroupRepositories enables the specified CLA Group repositories +func (r *Repository) GitLabEnableCLAGroupRepositories(ctx context.Context, claGroupID string) error { + repositories, err := r.GitHubGetRepositoriesByCLAGroup(ctx, claGroupID) + if err != nil { + return err + } + + for _, repo := range repositories { + enableErr := r.GitLabEnableRepositoryByID(ctx, repo.RepositoryID) + if enableErr != nil { + return enableErr + } + } + + return nil +} + +// GitLabDisableCLAGroupRepositories disables the GitLab repositories by the specified CLA Group +func (r *Repository) GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { + repositories, err := r.GitHubGetRepositoriesByCLAGroup(ctx, claGroupID) + if err != nil { + return err + } + + for _, repo := range repositories { + enableErr := r.GitLabDisableRepositoryByID(ctx, repo.RepositoryID) + if enableErr != nil { + return enableErr + } + } + + return nil +} + +// getRepositoryWithConditionFilter fetches the repository entry based on the specified condition and filter criteria using the provided index +func (r *Repository) getRepositoryWithConditionFilter(ctx context.Context, condition expression.KeyConditionBuilder, filter expression.ConditionBuilder, indexName string) (*repoModels.RepositoryDBModel, error) { + f := logrus.Fields{ + "functionName": "v2.repositories.repository.getRepositoryWithConditionFilter", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "indexName": indexName, + } + + expr, err := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter).Build() + if err != nil { + log.WithFields(f).WithError(err).Warn("problem creating builder") + return nil, err + } + + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(r.repositoryTableName), + IndexName: aws.String(indexName), + } + + results, err := r.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to get repositories using query: %+v", queryInput) + return nil, err + } + + if len(results.Items) == 0 { + log.WithFields(f).Warnf("no repositories found matching filter critera: %+v", queryInput) + // Generic - no details as we don't know what filter content was provided + return nil, &utils.GitLabRepositoryNotFound{} + } + + var repositories []*repoModels.RepositoryDBModel + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &repositories) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem unmarshalling response") + return nil, err + } + + if len(repositories) > 1 { + log.WithFields(f).Warn("multiple repositories records with the same repository name and type found") + // Generic - no details as we don't know what filter content was provided + return nil, &utils.GitLabDuplicateRepositoriesFound{} + } + + return repositories[0], nil +} + +// getRepositoriesWithConditionFilter fetches the repository entry based on the specified condition and filter criteria +// using the provided index +func (r *Repository) getRepositoriesWithConditionFilter(ctx context.Context, condition expression.KeyConditionBuilder, filter expression.ConditionBuilder, indexName string) ([]*repoModels.RepositoryDBModel, error) { + f := logrus.Fields{ + "functionName": "v2.repositories.repository.getRepositoriesWithConditionFilter", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "indexName": indexName, + } + + expr, err := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter).Build() + if err != nil { + log.WithFields(f).WithError(err).Warn("problem creating builder") + return nil, err + } + + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(r.repositoryTableName), + IndexName: aws.String(indexName), + } + + results, err := r.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("unable to get repositories using query: %+v", queryInput) + return nil, err + } + + if len(results.Items) == 0 { + log.WithFields(f).Warnf("no repositories found matching filter critera: %+v", queryInput) + // Generic - no details as we don't know what filter content was provided + return nil, &utils.GitLabRepositoryNotFound{} + } + + var repositories []*repoModels.RepositoryDBModel + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &repositories) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem unmarshalling response") + return nil, err + } + + return repositories, nil +} + +// setRepositoryEnabledValue sets the specified repository to the specified enabled value +func (r *Repository) setRepositoryEnabledValue(ctx context.Context, repositoryID string, enabledValue bool) error { + // Load the existing model - need to fetch the old values, if available + existingModel, getErr := r.GitLabGetRepository(ctx, repositoryID) + if getErr != nil { + return getErr + } + if existingModel == nil { + return fmt.Errorf("unable to locate existing repository entry by ID: %s", repositoryID) + } + + // If we have an old note - grab it/save it + var existingNote = "" + if existingModel.Note != "" { + if !strings.HasSuffix(strings.TrimSpace(existingModel.Note), ".") { + existingNote = strings.TrimSpace(existingModel.Note) + ". " + } else { + existingNote = strings.TrimSpace(existingModel.Note) + " " + } + } + + _, now := utils.CurrentTime() + _, err := r.dynamoDBClient.UpdateItem(&dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + "repository_id": {S: aws.String(repositoryID)}, + }, + ExpressionAttributeNames: map[string]*string{ + "#enabled": aws.String(repoModels.RepositoryEnabledColumn), + "#note": aws.String(repoModels.RepositoryNoteColumn), + "#dateModified": aws.String(repoModels.RepositoryDateModifiedColumn), + }, + ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":enabledValue": { + BOOL: aws.Bool(enabledValue), + }, + ":noteValue": { + S: aws.String(fmt.Sprintf("%s Updated enabled flag to %t on %s.", existingNote, enabledValue, now)), + }, + ":dateModifiedValue": { + S: aws.String(now), + }, + }, + UpdateExpression: aws.String("SET #enabled = :enabledValue, #note = :noteValue, #dateModified = :dateModifiedValue"), + TableName: aws.String(r.repositoryTableName), + }) + + return err +} diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index c83126b5a..3b80b9d1f 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -29,28 +29,44 @@ import ( v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" ) -// Service contains functions of GitHub V3Repositories service -type Service interface { - AddGithubRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) - EnableRepository(ctx context.Context, repositoryID string) error - DisableRepository(ctx context.Context, repositoryID string) error - ListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.ListGithubRepositories, error) - GetRepository(ctx context.Context, repositoryID string) (*v1Models.GithubRepository, error) - GetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) - DisableCLAGroupRepositories(ctx context.Context, claGroupID string) error - GetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) - UpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) +// ServiceInterface contains functions of the repositories service +type ServiceInterface interface { + // GitHub + + GitHubAddRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) + GitHubEnableRepository(ctx context.Context, repositoryID string) error + GitHubDisableRepository(ctx context.Context, repositoryID string) error + GitHubListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.GithubListRepositories, error) + GitHubGetRepository(ctx context.Context, repositoryID string) (*v1Models.GithubRepository, error) + GitHubGetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) + GitHubDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error + GitHubGetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) + GitHubUpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) + + // GitLab + + GitLabGetRepository(ctx context.Context, repositoryID string) (*v2Models.GitlabRepository, error) + GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*v2Models.GitlabRepository, error) + //GitLabGetRepositoriesByGitLabID(ctx context.Context, gitLabID int64) (*v2Models.GitlabRepository, error) + GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabListRepositories, error) + GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) + GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) + GitLabEnableRepository(ctx context.Context, repositoryID string) error + GitLabDisableRepository(ctx context.Context, repositoryID string) error + GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error } -// GithubOrgRepo provide method to get github organization by name +// GithubOrgRepo provide method to get GitHub organization by name type GithubOrgRepo interface { GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganizations, error) GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganization, error) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*v1Models.GithubOrganizations, error) } -type service struct { - repo v1Repositories.Repository +// Service is the service model/structure +type Service struct { + gitV1Repository v1Repositories.RepositoryInterface + gitV2Repository RepositoryInterface projectsClaGroupsRepo projects_cla_groups.Repository ghOrgRepo GithubOrgRepo } @@ -62,17 +78,19 @@ var ( ) // NewService creates a new githubOrganizations service -func NewService(repo v1Repositories.Repository, pcgRepo projects_cla_groups.Repository, ghOrgRepo GithubOrgRepo) Service { - return &service{ - repo: repo, +func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository RepositoryInterface, pcgRepo projects_cla_groups.Repository, ghOrgRepo GithubOrgRepo) ServiceInterface { + return &Service{ + gitV1Repository: gitV1Repository, + gitV2Repository: gitV2Repository, projectsClaGroupsRepo: pcgRepo, ghOrgRepo: ghOrgRepo, } } -func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) { +// GitHubAddRepositories adds the specified GitHub repository to the specified project +func (s *Service) GitHubAddRepositories(ctx context.Context, projectSFID string, input *models.GithubRepositoryInput) ([]*v1Models.GithubRepository, error) { f := logrus.Fields{ - "functionName": "v2.repositories.service.AddGithubRepositories", + "functionName": "v2.repositories.service.GitHubAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "claGroupID": utils.StringValue(input.ClaGroupID), @@ -155,7 +173,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, // Check if this repository exists in our database log.WithFields(f).Debugf("checking if GitHub repository by name: %s exists...", utils.StringValue(ghRepo.FullName)) - existingRepositoryModel, lookupErr := s.GetRepositoryByName(ctx, utils.StringValue(ghRepo.FullName)) + existingRepositoryModel, lookupErr := s.GitHubGetRepositoryByName(ctx, utils.StringValue(ghRepo.FullName)) if lookupErr != nil { // If we have the repository not found error - this is ok - we are expecting this if notFoundErr, ok := lookupErr.(*utils.GitHubRepositoryNotFound); ok { @@ -186,7 +204,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, } // Update Repo details in case of any changes - updatedRepository, updateErr := s.repo.UpdateGithubRepository(ctx, existingRepositoryModel.RepositoryID, v1Input) + updatedRepository, updateErr := s.gitV1Repository.GitHubUpdateRepository(ctx, existingRepositoryModel.RepositoryID, v1Input) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warnf("unable to update GitHub repository with name: %s, id: %s, using input: %+v", utils.StringValue(ghRepo.FullName), existingRepositoryModel.RepositoryID, v1Input) return nil, updateErr @@ -210,7 +228,7 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, RepositoryURL: ghRepo.HTMLURL, } - addedModel, addErr := s.repo.AddGithubRepository(ctx, parentProjectSFID, projectSFID, in) + addedModel, addErr := s.gitV1Repository.GitHubAddRepository(ctx, parentProjectSFID, projectSFID, in) if addErr != nil { log.WithFields(f).WithError(addErr).Warnf("unable to add github repository: %s for project: %s", *ghRepo.FullName, projectSFID) return nil, addErr @@ -224,17 +242,20 @@ func (s *service) AddGithubRepositories(ctx context.Context, projectSFID string, return response, nil } -func (s *service) EnableRepository(ctx context.Context, repositoryID string) error { - return s.repo.EnableRepository(ctx, repositoryID) +// GitHubEnableRepository service function +func (s *Service) GitHubEnableRepository(ctx context.Context, repositoryID string) error { + return s.gitV1Repository.GitHubEnableRepository(ctx, repositoryID) } -func (s *service) DisableRepository(ctx context.Context, repositoryID string) error { - return s.repo.DisableRepository(ctx, repositoryID) +// GitHubDisableRepository service function +func (s *Service) GitHubDisableRepository(ctx context.Context, repositoryID string) error { + return s.gitV1Repository.GitHubDisableRepository(ctx, repositoryID) } -func (s *service) ListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.ListGithubRepositories, error) { +// GitHubListProjectRepositories service function +func (s *Service) GitHubListProjectRepositories(ctx context.Context, projectSFID string) (*v1Models.GithubListRepositories, error) { f := logrus.Fields{ - "functionName": "v2.repositories.service.ListProjectRepositories", + "functionName": "v2.repositories.service.GitHubListProjectRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -256,7 +277,7 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin } log.WithFields(f).Debug("loaded project from the project service") enabled := true - return s.repo.ListProjectRepositories(ctx, projectSFID, &enabled) + return s.gitV1Repository.GitHubListProjectRepositories(ctx, projectSFID, &enabled) //// Lookup orgs via projectSFID //log.WithFields(f).Debug("querying EasyCLA for organizations by project id...") @@ -276,7 +297,7 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin //} // //// Our response - empty to start with - //response := &v1Models.ListGithubRepositories{ + //response := &v1Models.GithubListRepositories{ // List: []*v1Models.GithubRepository{}, //} // @@ -313,7 +334,7 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin //} // //// Now, query our DB.... - //listOurGitHubRepos, err := s.repo.ListProjectRepositories(ctx, "", projectSFID, true) + //listOurGitHubRepos, err := s.gitV1Repository.GitHubListProjectRepositories(ctx, "", projectSFID, true) //if err != nil { // log.WithFields(f).WithError(err).Warn("unable to lookup repository records by id in our repositories table ") // return response, err @@ -323,7 +344,7 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin // return response, err //} // - //// For each repo that we have... + //// For each gitV1Repository that we have... //for _, ourGitHubRepo := range listOurGitHubRepos.List { // // Inefficient, but ok if the number of repos is relatively small // for _, r := range response.List { @@ -343,17 +364,20 @@ func (s *service) ListProjectRepositories(ctx context.Context, projectSFID strin //return response, nil } -func (s *service) GetRepository(ctx context.Context, repositoryID string) (*v1Models.GithubRepository, error) { - return s.repo.GetRepository(ctx, repositoryID) +// GitHubGetRepository service function +func (s *Service) GitHubGetRepository(ctx context.Context, repositoryID string) (*v1Models.GithubRepository, error) { + return s.gitV1Repository.GitHubGetRepository(ctx, repositoryID) } -func (s *service) GetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) { - return s.repo.GetRepositoryByName(ctx, repositoryName) +// GitHubGetRepositoryByName service function +func (s *Service) GitHubGetRepositoryByName(ctx context.Context, repositoryName string) (*v1Models.GithubRepository, error) { + return s.gitV1Repository.GitHubGetRepositoryByName(ctx, repositoryName) } -func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) { +// GitHubGetProtectedBranch service function +func (s *Service) GitHubGetProtectedBranch(ctx context.Context, projectSFID, repositoryID, branchName string) (*v2Models.GithubRepositoryBranchProtection, error) { f := logrus.Fields{ - "functionName": "v2.repositories.service.GetProtectedBranch", + "functionName": "v2.repositories.service.GitHubGetProtectedBranch", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "repositoryID": repositoryID, @@ -389,7 +413,7 @@ func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositor if errors.Is(err, branch_protection.ErrBranchNotProtected) { return result, nil } - log.WithFields(f).WithError(err).Warnf("getting the github protected branch for owner : %s, repo : %s and branch : %s failed : %v", owner, githubRepoName, branchName, err) + log.WithFields(f).WithError(err).Warnf("getting the github protected branch for owner : %s, gitV1Repository : %s and branch : %s failed : %v", owner, githubRepoName, branchName, err) return nil, err } @@ -405,9 +429,10 @@ func (s *service) GetProtectedBranch(ctx context.Context, projectSFID, repositor return result, nil } -func (s *service) UpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) { +// GitHubUpdateProtectedBranch service function +func (s *Service) GitHubUpdateProtectedBranch(ctx context.Context, projectSFID, repositoryID string, input *v2Models.GithubRepositoryBranchProtectionInput) (*v2Models.GithubRepositoryBranchProtection, error) { f := logrus.Fields{ - "functionName": "v2.repositories.service.UpdateProtectedBranch", + "functionName": "v2.repositories.service.GitHubUpdateProtectedBranch", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "repositoryID": repositoryID, @@ -477,10 +502,11 @@ func (s *service) UpdateProtectedBranch(ctx context.Context, projectSFID, reposi return nil, err } - return s.GetProtectedBranch(ctx, projectSFID, repositoryID, branchName) + return s.GitHubGetProtectedBranch(ctx, projectSFID, repositoryID, branchName) } -func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID string) (*v1Models.GithubRepository, error) { +// getGithubRepo service function +func (s *Service) getGithubRepo(ctx context.Context, projectSFID, repositoryID string) (*v1Models.GithubRepository, error) { f := logrus.Fields{ "functionName": "v2.repositories.service.getGitHubRepo", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -493,14 +519,14 @@ func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID s if err != nil { return nil, err } - githubRepository, err := s.GetRepository(ctx, repositoryID) + githubRepository, err := s.GitHubGetRepository(ctx, repositoryID) if err != nil { log.WithFields(f).Warnf("fetching repository failed : %s : %v", repositoryID, err) return nil, err } - // check if project and repo are actually associated - if githubRepository.ProjectSFID != projectSFID { + // check if project and gitV1Repository are actually associated + if githubRepository.RepositoryProjectSfid != projectSFID { msg := fmt.Sprintf("github repository %s doesn't belong to project : %s", repositoryID, projectSFID) log.WithFields(f).Warn(msg) return nil, errors.New(msg) @@ -509,7 +535,8 @@ func (s *service) getGithubRepo(ctx context.Context, projectSFID, repositoryID s return githubRepository, nil } -func (s *service) getBranchProtectionRepositoryForOrgName(ctx context.Context, githubOrgName string) (*branch_protection.BranchProtectionRepository, error) { +// getBranchProtectionRepositoryForOrgName service function +func (s *Service) getBranchProtectionRepositoryForOrgName(ctx context.Context, githubOrgName string) (*branch_protection.BranchProtectionRepository, error) { f := logrus.Fields{ "functionName": "v2.repositories.service.getGitHubClientForOrgName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -529,22 +556,23 @@ func (s *service) getBranchProtectionRepositoryForOrgName(ctx context.Context, g return branchProtectionRepo, nil } -func (s *service) getGithubOwner(ctx context.Context, branchProtectionRepository *branch_protection.BranchProtectionRepository, githubOrgName, githubRepoName string) (string, error) { +// getGithubOwner service function +func (s *Service) getGithubOwner(ctx context.Context, branchProtectionRepository *branch_protection.BranchProtectionRepository, githubOrgName, githubRepoName string) (string, error) { owner, err := branchProtectionRepository.GetOwnerName(ctx, githubOrgName, githubRepoName) if err != nil { - log.Warnf("getting the owner name for org : %s and repo : %s failed : %v", githubOrgName, githubRepoName, err) + log.Warnf("getting the owner name for org : %s and gitV1Repository : %s failed : %v", githubOrgName, githubRepoName, err) return "", err } if owner == "" { - log.Warnf("GitHub returned empty owner name for org : %s and repo : %s", githubOrgName, githubRepoName) + log.Warnf("GitHub returned empty owner name for org : %s and gitV1Repository : %s", githubOrgName, githubRepoName) return "", fmt.Errorf("empty owner name") } return owner, nil } -// getRequiredProtectedBranchCheckStatus -func (s *service) getRequiredProtectedBranchCheckStatus(branchProtectionRule *branch_protection.BranchProtectionRule, requiredChecks []string) []*v2Models.GithubRepositoryBranchProtectionStatusChecks { +// getRequiredProtectedBranchCheckStatus service function to get the required protected branch check status +func (s *Service) getRequiredProtectedBranchCheckStatus(branchProtectionRule *branch_protection.BranchProtectionRule, requiredChecks []string) []*v2Models.GithubRepositoryBranchProtectionStatusChecks { f := logrus.Fields{ "functionName": "v2.repositories.service.getRequiredProtectedBranchCheckStatus", } @@ -578,15 +606,16 @@ func (s *service) getRequiredProtectedBranchCheckStatus(branchProtectionRule *br return result } -func (s *service) DisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { +// GitHubDisableCLAGroupRepositories service function to disable CLA group repositories +func (s *Service) GitHubDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { f := logrus.Fields{ - "functionName": "v2.repositories.service.DisableCLAGroupRepositories", + "functionName": "v2.repositories.service.GitHubDisableCLAGroupRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, } var deleteErr error - ghOrgs, err := s.repo.GetCLAGroupRepositoriesGroupByOrgs(ctx, claGroupID, true) + ghOrgs, err := s.gitV1Repository.GitHubGetCLAGroupRepositoriesGroupByOrgs(ctx, claGroupID, true) if err != nil { return err } @@ -594,7 +623,7 @@ func (s *service) DisableCLAGroupRepositories(ctx context.Context, claGroupID st log.WithFields(f).Debugf("Deleting repositories for cla-group :%s", claGroupID) for _, ghOrg := range ghOrgs { for _, item := range ghOrg.List { - deleteErr = s.repo.DisableRepository(ctx, item.RepositoryID) + deleteErr = s.gitV1Repository.GitHubDisableRepository(ctx, item.RepositoryID) if deleteErr != nil { log.WithFields(f).Warnf("Unable to remove repository: %s for project :%s error :%v", item.RepositoryID, claGroupID, deleteErr) } From a6090a3335b547a327f4d385dbb1d1c228b24b73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:05:54 -0700 Subject: [PATCH 0398/1276] Bump path-parse from 1.0.6 to 1.0.7 in /cla-frontend-corporate-console/src (#3130) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-corporate-console/src/yarn.lock b/cla-frontend-corporate-console/src/yarn.lock index 111cea165..c4d00d2d8 100644 --- a/cla-frontend-corporate-console/src/yarn.lock +++ b/cla-frontend-corporate-console/src/yarn.lock @@ -2906,8 +2906,9 @@ path-key@^2.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" From 3010897be7b3f9ca4f97701080a42c58825b4e70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:06:06 -0700 Subject: [PATCH 0399/1276] Bump path-parse from 1.0.6 to 1.0.7 in /cla-frontend-project-console/src (#3131) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 6fe68d0dd..191d14eee 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -3027,8 +3027,9 @@ path-key@^2.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" From 2772d37b07bf3af4a4cc926c3941c456122d5577 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:06:18 -0700 Subject: [PATCH 0400/1276] Bump jszip from 3.5.0 to 3.7.1 in /cla-frontend-corporate-console (#3134) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 4094dd7cd..fe72ed2b5 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -3748,9 +3748,9 @@ jsprim@^1.2.2: verror "1.10.0" jszip@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" pako "~1.0.2" From ed99606c6d21506c86316b284c42facd281276ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:06:29 -0700 Subject: [PATCH 0401/1276] Bump path-parse from 1.0.6 to 1.0.7 in /cla-frontend-contributor-console/src (#3132) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index e1fc3cdc2..f93af2470 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -2903,8 +2903,9 @@ path-key@^2.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" From 077f327caf63fa0e08affb5d5ee6d7699bcc17b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:06:43 -0700 Subject: [PATCH 0402/1276] Bump jszip from 3.5.0 to 3.7.1 in /cla-backend-go (#3139) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index 55e94ac56..f44d4608b 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -3021,9 +3021,9 @@ jsprim@^1.2.2: verror "1.10.0" jszip@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" pako "~1.0.2" From 46c7b28667e7bfdfaf775cd288f2992c9c25c647 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:06:55 -0700 Subject: [PATCH 0403/1276] Bump jszip from 3.5.0 to 3.7.1 in /cla-backend (#3137) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 2112e51ab..ea5cc2a06 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -3709,9 +3709,9 @@ jsprim@^1.2.2: verror "1.10.0" jszip@^3.1.0, jszip@^3.2.2, jszip@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" pako "~1.0.2" From ad91337760b754e933b8998956314d5f68ff86b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:07:10 -0700 Subject: [PATCH 0404/1276] Bump path-parse from 1.0.6 to 1.0.7 (#3135) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ecc42d4b0..9801b4dcf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3591,9 +3591,9 @@ path-loader@^1.0.10: superagent "^3.8.3" path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" From aa24a02cf3abe043647d8ced037086b20b93c23e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:07:24 -0700 Subject: [PATCH 0405/1276] Bump jszip from 3.5.0 to 3.7.1 in /cla-frontend-contributor-console (#3138) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index d5cee53c5..3a75259b0 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -3761,9 +3761,9 @@ jsprim@^1.2.2: verror "1.10.0" jszip@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" pako "~1.0.2" From 186ab82b25aa9a47795a918138c8f50ccf8c63e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:07:36 -0700 Subject: [PATCH 0406/1276] Bump jszip from 3.5.0 to 3.7.1 (#3136) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9801b4dcf..1e449494c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2894,9 +2894,9 @@ jsprim@^1.2.2: verror "1.10.0" jszip@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" pako "~1.0.2" From 41c3e6c4605704fe20855f0e9fa44432db592bc1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 11 Aug 2021 17:22:59 -0700 Subject: [PATCH 0407/1276] Added GitLab Org Permissions Feedback (#3141) --- cla-backend-go/swagger/cla.v2.yaml | 4 ++ .../v2/gitlab_organizations/handlers.go | 39 +++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 93dd7f870..638828d42 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1643,6 +1643,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' '500': @@ -1794,6 +1796,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' '500': diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 16f28eed3..e19172201 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "strings" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" @@ -44,9 +45,17 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "projectSFID": params.ProjectSFID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_organizations.NewGetProjectGitlabOrganizationsNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Get Project GitHub Organizations with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Get Project GitHub Organizations for Project '%s' with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_organizations.NewGetProjectGitlabOrganizationsForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -84,9 +93,17 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "projectSFID": params.ProjectSFID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Add Project Gitlab Organizations with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Add Project GitHub Organizations for Project '%s' with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -191,14 +208,22 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "authEmail": authUser.Email, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_organizations.NewDeleteProjectGitlabOrganizationNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("authUser %s does not have access to Delete Project GitHub Organizations with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Delete Project GitHub Organizations for Project '%s' with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_organizations.NewDeleteProjectGitlabOrganizationForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - err := service.DeleteGitlabOrganization(ctx, params.ProjectSFID, params.OrgName) + err = service.DeleteGitlabOrganization(ctx, params.ProjectSFID, params.OrgName) if err != nil { if strings.Contains(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("project not found with given SFID: %s", params.ProjectSFID) From cd8d4943dbf88fd7920e155f970086c4df16aefb Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 11 Aug 2021 17:35:32 -0700 Subject: [PATCH 0408/1276] Added GitLab Org Permissions Feedback (#3142) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 2 ++ cla-backend-go/v2/gitlab_organizations/service.go | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index e19172201..d93aebc0a 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -10,6 +10,8 @@ import ( project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "strings" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" "github.com/communitybridge/easycla/cla-backend-go/gitlab" "github.com/gofrs/uuid" diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index a12bbe51e..30d89d83f 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -299,9 +299,8 @@ func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI //params.Add("redirect_uri", "http://localhost:8080/v4/gitlab/oauth/callback") params.Add("response_type", "code") params.Add("state", state) - params.Add("scope", "read_user read_api read_repository write_repository api") + params.Add("scope", "api read_user read_api read_repository write_repository email") installationURL := strfmt.URI(base + "?" + params.Encode()) return &installationURL - } From 75143529ab6bc3886d0b6e26ab73a450d95fef73 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 11 Aug 2021 23:34:34 -0700 Subject: [PATCH 0409/1276] Updated Debug for GitLab (#3143) --- cla-backend-go/gitlab/client.go | 12 ++++++------ cla-backend-go/gitlab/client_test.go | 17 ++++++++++------- cla-backend-go/gitlab/init.go | 14 ++++++++++---- .../v2/gitlab_organizations/handlers.go | 1 - 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/cla-backend-go/gitlab/client.go b/cla-backend-go/gitlab/client.go index d04757245..757267078 100644 --- a/cla-backend-go/gitlab/client.go +++ b/cla-backend-go/gitlab/client.go @@ -40,15 +40,15 @@ func NewGitlabOauthClient(authInfo string) (*gitlab.Client, error) { // EncryptAuthInfo encrypts the oauth response into a string func EncryptAuthInfo(oauthResp *OauthSuccessResponse) (string, error) { - key := getGitlabAppPrivateKey() + key := getGitLabAppPrivateKey() keyDecoded, err := base64.StdEncoding.DecodeString(key) if err != nil { - return "", fmt.Errorf("decode key : %v", err) + return "", fmt.Errorf("problem decoding GitLab private glClientKey, error: %v", err) } b, err := json.Marshal(oauthResp) if err != nil { - return "", fmt.Errorf("oauth resp json marshall : %v", err) + return "", fmt.Errorf("problem marshalling oauth resp json, error: %v", err) } authInfo := string(b) //log.Infof("auth info before encrypting : %s", authInfo) @@ -61,7 +61,7 @@ func EncryptAuthInfo(oauthResp *OauthSuccessResponse) (string, error) { return hex.EncodeToString(encrypted), nil } -// DecryptAuthInfo decrytps the authinfo into OauthSuccessResponse data structure +// DecryptAuthInfo decrytps the auth info into OauthSuccessResponse data structure func DecryptAuthInfo(authInfoEncoded string) (*OauthSuccessResponse, error) { ciphertext, err := hex.DecodeString(authInfoEncoded) if err != nil { @@ -70,10 +70,10 @@ func DecryptAuthInfo(authInfoEncoded string) (*OauthSuccessResponse, error) { //log.Infof("auth info decoded : %s", ciphertext) - key := getGitlabAppPrivateKey() + key := getGitLabAppPrivateKey() keyDecoded, err := base64.StdEncoding.DecodeString(key) if err != nil { - return nil, fmt.Errorf("decode key : %v", err) + return nil, fmt.Errorf("decode glClientKey : %v", err) } //log.Debugf("before decrypt : keyDecoded : %s, cipherText : %s", keyDecoded, ciphertext) diff --git a/cla-backend-go/gitlab/client_test.go b/cla-backend-go/gitlab/client_test.go index 46c994c2c..30855ba71 100644 --- a/cla-backend-go/gitlab/client_test.go +++ b/cla-backend-go/gitlab/client_test.go @@ -10,16 +10,18 @@ import ( "github.com/stretchr/testify/assert" ) -var key = "0WqnDWHnZKo2cmQ8m93EtY9ZBpfzQW4UnnEuRmgtJKM=" +var glClientID = "124453345" +var glClientKey = "0WqnDWHnZKo2cmQ8m93EtY9ZBpfzQW4UnnEuRmgtJKM=" var oauthRespStr = `{"access_token":"a30671b8749ba5d48925712344377f11a5aba43ec630f099e464b9843796e6a6","token_type":"Bearer","expires_in":0,"refresh_token":"0838a31d0d796973eacefdf513523e6e47aa06fac9d26622964da1e473509458","created_at":1626435922}` func TestNewGitlabOauthClient(t *testing.T) { - Init("124453345", key) + Init(glClientID, glClientKey) t.Cleanup(func() { - gitlabAppPrivateKey = "" + gitLabAppPrivateKey = "" }) - t.Logf("app private key is : %s", getGitlabAppPrivateKey()) + t.Logf("app private ID is : %s", getGitLabAppID()) + t.Logf("app private key is : %s", getGitLabAppPrivateKey()) var oauthResp OauthSuccessResponse err := json.Unmarshal([]byte(oauthRespStr), &oauthResp) @@ -34,12 +36,13 @@ func TestNewGitlabOauthClient(t *testing.T) { } func TestEncryptDecryptAuthInfo(t *testing.T) { - Init("124453345", key) + Init(glClientID, glClientKey) t.Cleanup(func() { - gitlabAppPrivateKey = "" + gitLabAppPrivateKey = "" }) - t.Logf("app private key is : %s", getGitlabAppPrivateKey()) + t.Logf("app private ID is : %s", getGitLabAppID()) + t.Logf("app private key is : %s", getGitLabAppPrivateKey()) var oauthResp OauthSuccessResponse err := json.Unmarshal([]byte(oauthRespStr), &oauthResp) diff --git a/cla-backend-go/gitlab/init.go b/cla-backend-go/gitlab/init.go index b390e6124..c15db8ce9 100644 --- a/cla-backend-go/gitlab/init.go +++ b/cla-backend-go/gitlab/init.go @@ -3,13 +3,19 @@ package gitlab -var gitlabAppPrivateKey string +var gitLabAppPrivateKey string +var gitLabAppID string // Init initializes the required gitlab variables func Init(glAppID string, glAppPrivateKey string) { - gitlabAppPrivateKey = glAppPrivateKey + gitLabAppID = glAppID + gitLabAppPrivateKey = glAppPrivateKey } -func getGitlabAppPrivateKey() string { - return gitlabAppPrivateKey +func getGitLabAppID() string { + return gitLabAppID +} + +func getGitLabAppPrivateKey() string { + return gitLabAppPrivateKey } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index d93aebc0a..a3bf4268f 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "strings" project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" From 80c51ee82544d05d2e13171d3b99e6aaf5ea0210 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 10:35:01 -0700 Subject: [PATCH 0410/1276] Updated Debug for GitLab (#3144) --- cla-backend-go/gitlab/auth.go | 22 +++++++++- .../v2/gitlab_organizations/handlers.go | 12 ++--- .../v2/gitlab_organizations/repository.go | 44 +++++++++---------- .../v2/gitlab_organizations/service.go | 2 +- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/cla-backend-go/gitlab/auth.go b/cla-backend-go/gitlab/auth.go index d9da3a218..b49ff162d 100644 --- a/cla-backend-go/gitlab/auth.go +++ b/cla-backend-go/gitlab/auth.go @@ -4,12 +4,23 @@ package gitlab import ( + "errors" + "fmt" + "github.com/communitybridge/easycla/cla-backend-go/config" + log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/go-resty/resty/v2" + "github.com/sirupsen/logrus" ) // FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { + f := logrus.Fields{ + "functionName": "gitlab.auth.FetchOauthCredentials", + "code": code, + "redirectURI": config.GetConfig().Gitlab.RedirectURI, + } + client := resty.New() params := map[string]string{ "client_id": config.GetConfig().Gitlab.AppID, @@ -20,14 +31,21 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { //"redirect_uri": "http://localhost:8080/v4/gitlab/oauth/callback", } + url := "https://gitlab.com/oauth/token" resp, err := client.R(). SetQueryParams(params). SetResult(&OauthSuccessResponse{}). - Post("https://gitlab.com/oauth/token") - + Post(url) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", url) return nil, err } + if resp.StatusCode() < 200 || resp.StatusCode() > 299 { + msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d", url, resp.StatusCode()) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + return resp.Result().(*OauthSuccessResponse), nil } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index a3bf4268f..d6100aadc 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -55,7 +55,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Get Project GitHub Organizations for Project '%s' with scope of %s", + msg := fmt.Sprintf("user %s does not have access to Get Project GitLab Organizations for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_organizations.NewGetProjectGitlabOrganizationsForbidden().WithPayload( @@ -103,7 +103,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Add Project GitHub Organizations for Project '%s' with scope of %s", + msg := fmt.Sprintf("user %s does not have access to Add Project GitLab Organizations for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( @@ -138,7 +138,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. result, err := service.AddGitlabOrganization(ctx, params.ProjectSFID, params.Body) if err != nil { - msg := fmt.Sprintf("unable to add github organization, error: %+v", err) + msg := fmt.Sprintf("unable to add GitLab organization, error: %+v", err) log.WithFields(f).WithError(err).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( utils.ErrorResponseBadRequestWithError(reqID, msg, err)) @@ -201,7 +201,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.handlers.GithubOrganizationsDeleteProjectGithubOrganizationHandler", + "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsDeleteProjectGitlabOrganizationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": params.ProjectSFID, "orgName": params.OrgName, @@ -218,7 +218,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Delete Project GitHub Organizations for Project '%s' with scope of %s", + msg := fmt.Sprintf("user %s does not have access to Delete Project GitLab Organizations for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_organizations.NewDeleteProjectGitlabOrganizationForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) @@ -238,7 +238,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. eventService.LogEventWithContext(ctx, &events.LogEventArgs{ LfUsername: authUser.UserName, - EventType: events.GitHubOrganizationDeleted, + EventType: events.GitlabOrganizationDeleted, ProjectSFID: params.ProjectSFID, EventData: &events.GitlabOrganizationDeletedEventData{ GitlabOrganizationName: params.OrgName, diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 83f709404..2a6bd05ca 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -35,7 +35,7 @@ type RepositoryInterface interface { AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganization, error) + GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models2.GitlabOrganization, error) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error @@ -79,7 +79,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS log.WithFields(f).Debugf("An existing GitLab organization with name %s exists in our database", gitLabOrganizationName) // If everything matches... if projectSFID == existingRecord.ProjectSFID { - log.WithFields(f).Debug("Existing github organization with same SFID - should be able to update it") + log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") enabledFlag := true updateErr := repo.UpdateGitlabOrganization(ctx, projectSFID, gitLabOrganizationName, utils.BoolValue(input.AutoEnabled), input.AutoEnabledClaGroupID, utils.BoolValue(input.BranchProtectionEnabled), &enabledFlag) @@ -91,7 +91,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS return repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName) } - log.WithFields(f).Debug("Existing github organization with different project SFID - won't be able to update it - will return conflict") + log.WithFields(f).Debug("Existing GitLab organization with different project SFID - won't be able to update it - will return conflict") return nil, fmt.Errorf("record already exists") } @@ -126,7 +126,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS Version: "v1", } - log.WithFields(f).Debug("Encoding github organization record for adding to the database...") + log.WithFields(f).Debug("Encoding GitLab organization record for adding to the database...") av, err := dynamodbattribute.MarshalMap(gitlabOrg) if err != nil { log.WithFields(f).WithError(err).Warn("unable to marshall request for query") @@ -157,7 +157,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS // GetGitlabOrganizations get GitLab organizations based on the project SFID func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.repository.GetGitHubOrganizations", + "functionName": "v2.gitlab_organizations.repository.GetGitlabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -188,7 +188,7 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s results, err := repo.dynamoDBClient.Query(queryInput) if err != nil { - log.WithFields(f).Warnf("error retrieving github_organizations using project_sfid = %s. error = %s", projectSFID, err.Error()) + log.WithFields(f).Warnf("error retrieving gitlab_organizations using project_sfid = %s. error = %s", projectSFID, err.Error()) return nil, err } @@ -211,16 +211,16 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s } // GetGitlabOrganizationByName get GitLab organization by name -func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOrganizationName string) (*models2.GitlabOrganization, error) { +func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models2.GitlabOrganization, error) { f := logrus.Fields{ - "functionName": "v1.github_organizations.repository.GetGitHubOrganizationByName", + "functionName": "v1.gitlab_organizations.repository.GetGitlabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "githubOrganizationName": githubOrganizationName, + "gitLabOrganizationName": gitLabOrganizationName, } - githubOrganizationName = strings.ToLower(githubOrganizationName) + gitLabOrganizationName = strings.ToLower(gitLabOrganizationName) - condition := expression.Key("organization_name_lower").Equal(expression.Value(strings.ToLower(githubOrganizationName))) + condition := expression.Key("organization_name_lower").Equal(expression.Value(strings.ToLower(gitLabOrganizationName))) builder := expression.NewBuilder().WithKeyCondition(condition) // Use the nice builder to create the expression expr, err := builder.Build() @@ -238,14 +238,14 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, githubOr IndexName: aws.String(GitlabOrgLowerNameIndex), } - log.WithFields(f).Debugf("querying for github organization by name using organization_name_lower=%s...", strings.ToLower(githubOrganizationName)) + log.WithFields(f).Debugf("querying for GitLab organization by name using organization_name_lower=%s...", strings.ToLower(gitLabOrganizationName)) results, err := repo.dynamoDBClient.Query(queryInput) if err != nil { - log.WithFields(f).WithError(err).Warnf("error retrieving github_organizations using githubOrganizationName = %s", githubOrganizationName) + log.WithFields(f).WithError(err).Warnf("error retrieving gitlab_organizations using gitLabOrganizationName = %s", gitLabOrganizationName) return nil, err } if len(results.Items) == 0 { - log.WithFields(f).Debug("Unable to find github organization by name - no results") + log.WithFields(f).Debug("Unable to find GitLab organization by name - no results") return nil, nil } @@ -269,7 +269,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza "gitlabOrganizationID": gitlabOrganizationID, } - log.WithFields(f).Debug("Querying for github organization by name...") + log.WithFields(f).Debug("Querying for GitLab organization by name...") result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ Key: map[string]*dynamodb.AttributeValue{ "organization_id": { @@ -282,7 +282,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza return nil, err } if len(result.Item) == 0 { - log.WithFields(f).Debug("Unable to find github organization by name - no results") + log.WithFields(f).Debug("Unable to find GitLab organization by name - no results") return nil, nil } @@ -429,10 +429,10 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { f := logrus.Fields{ - "functionName": "v1.github_organizations.repository.DeleteGitHubOrganization", + "functionName": "v1.gitlab_organizations.repository.DeleteGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "githubOrgName": gitlabOrgName, + "gitlabOrgName": gitlabOrgName, } var gitlabOrganizationID string @@ -443,14 +443,14 @@ func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID return errors.New(errMsg) } - for _, githubOrg := range orgs.List { - if strings.EqualFold(githubOrg.OrganizationName, gitlabOrgName) { - gitlabOrganizationID = githubOrg.OrganizationID + for _, gitLabOrg := range orgs.List { + if strings.EqualFold(gitLabOrg.OrganizationName, gitlabOrgName) { + gitlabOrganizationID = gitLabOrg.OrganizationID break } } - log.WithFields(f).Debug("Deleting GitHub organization...") + log.WithFields(f).Debug("Deleting GitLab organization...") // Update enabled flag as false _, currentTime := utils.CurrentTime() note := fmt.Sprintf("Enabled set to false due to org deletion at %s ", currentTime) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 30d89d83f..ef0d8b714 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -263,7 +263,7 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) func (s service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error { f := logrus.Fields{ - "functionName": "DeleteGitHubOrganization", + "functionName": "DeleteGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "gitlabOrgName": gitlabOrgName, From 368bb503e0809979dd7c8c02720cf3df40a45635 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 12:46:13 -0700 Subject: [PATCH 0411/1276] Added GitLab Auth Debug (#3146) --- cla-backend-go/gitlab/auth.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/gitlab/auth.go b/cla-backend-go/gitlab/auth.go index b49ff162d..db1c97090 100644 --- a/cla-backend-go/gitlab/auth.go +++ b/cla-backend-go/gitlab/auth.go @@ -16,11 +16,14 @@ import ( // FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { f := logrus.Fields{ - "functionName": "gitlab.auth.FetchOauthCredentials", - "code": code, - "redirectURI": config.GetConfig().Gitlab.RedirectURI, + "functionName": "gitlab.auth.FetchOauthCredentials", + "code": code, + "redirectURI": config.GetConfig().Gitlab.RedirectURI, + "gitLabClientID": fmt.Sprintf("%s...", config.GetConfig().Gitlab.AppID[0:6]), + "gitLabClientSecret": fmt.Sprintf("%s...", config.GetConfig().Gitlab.ClientSecret[0:6]), } + // For info on this authorization flow, see: https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow client := resty.New() params := map[string]string{ "client_id": config.GetConfig().Gitlab.AppID, @@ -42,7 +45,7 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { } if resp.StatusCode() < 200 || resp.StatusCode() > 299 { - msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d", url, resp.StatusCode()) + msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", url, resp.StatusCode(), string(resp.Body())) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } From 7eced9a4f480ddee1c04f84a1a2a62b006a8028a Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 13:02:29 -0700 Subject: [PATCH 0412/1276] Resolved GitHub Org Parent Project Issues (#3147) --- cla-backend-go/v2/github_organizations/service.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index f95f88046..c4f75b635 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -191,10 +191,10 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) } key := fmt.Sprintf("%s#%v", repo.RepositoryOrganizationName, repo.RepositoryExternalID) + parentProjectSFID = repo.RepositoryProjectSfid parentProjectModel, projectModelErr := v2ProjectService.GetClient().GetParentProjectModel(repo.RepositoryProjectSfid) - if projectModelErr != nil || parentProjectModel == nil { - log.WithFields(f).Warnf("unable to load parent for project: %s", repo.RepositoryProjectSfid) - return nil, projectModelErr + if projectModelErr == nil && parentProjectModel != nil { + parentProjectSFID = parentProjectModel.ID } if _, ok := connectedRepo[key]; ok { @@ -207,7 +207,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) RepositoryGithubID: repo.RepositoryExternalID, ClaGroupID: repo.RepositoryClaGroupID, ProjectID: repo.RepositoryProjectSfid, - ParentProjectID: parentProjectModel.ID, + ParentProjectID: parentProjectSFID, }) // delete it from connectedRepo array since we have processed it From 9fad7a4d0f8c25b5e0547da3d63d4d3dd265c036 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 14:17:59 -0700 Subject: [PATCH 0413/1276] Resolved Additional Parent Project ID Issue (#3148) --- cla-backend-go/v2/github_organizations/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index c4f75b635..9c57eaaed 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -222,7 +222,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) RepositoryName: repo.RepositoryName, ClaGroupID: repo.RepositoryClaGroupID, ProjectID: repo.RepositoryProjectSfid, - ParentProjectID: parentProjectModel.ID, + ParentProjectID: parentProjectSFID, }) if rorg.ConnectionStatus == utils.Connected { From 71f794acc0eba73fabaa4a0f0cc5fbb8a70cadab Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 14:42:38 -0700 Subject: [PATCH 0414/1276] Added More GitLab Auth Debug (#3149) --- cla-backend-go/gitlab/auth.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/gitlab/auth.go b/cla-backend-go/gitlab/auth.go index db1c97090..6f374437c 100644 --- a/cla-backend-go/gitlab/auth.go +++ b/cla-backend-go/gitlab/auth.go @@ -15,22 +15,32 @@ import ( // FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { + gitLabConfig := config.GetConfig().Gitlab f := logrus.Fields{ - "functionName": "gitlab.auth.FetchOauthCredentials", - "code": code, - "redirectURI": config.GetConfig().Gitlab.RedirectURI, - "gitLabClientID": fmt.Sprintf("%s...", config.GetConfig().Gitlab.AppID[0:6]), - "gitLabClientSecret": fmt.Sprintf("%s...", config.GetConfig().Gitlab.ClientSecret[0:6]), + "functionName": "gitlab.auth.FetchOauthCredentials", + "code": code, + "redirectURI": config.GetConfig().Gitlab.RedirectURI, + } + + if len(gitLabConfig.AppID) > 4 { + f["gitLabClientID"] = fmt.Sprintf("%s...%s", gitLabConfig.AppID[0:4], gitLabConfig.AppID[len(gitLabConfig.AppID)-4:]) + } else { + return nil, errors.New("gitlab application client ID value is not set - value is empty or malformed") + } + if len(gitLabConfig.ClientSecret) > 4 { + f["gitLabClientSecret"] = fmt.Sprintf("%s...%s", gitLabConfig.ClientSecret[0:4], gitLabConfig.ClientSecret[len(gitLabConfig.ClientSecret)-4:]) + } else { + return nil, errors.New("gitlab application client secret value is not set - value is empty or malformed") } // For info on this authorization flow, see: https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow client := resty.New() params := map[string]string{ - "client_id": config.GetConfig().Gitlab.AppID, - "client_secret": config.GetConfig().Gitlab.ClientSecret, + "client_id": gitLabConfig.AppID, + "client_secret": gitLabConfig.ClientSecret, "code": code, "grant_type": "authorization_code", - "redirect_uri": config.GetConfig().Gitlab.RedirectURI, + "redirect_uri": gitLabConfig.RedirectURI, //"redirect_uri": "http://localhost:8080/v4/gitlab/oauth/callback", } From a6b720f6b5ae932ac7ab2cd405be649f1dfd1d18 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 17:02:24 -0700 Subject: [PATCH 0415/1276] Testing GitLab Auth (#3150) --- .../cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/gitlab/client.go | 16 +++---- cla-backend-go/gitlab/client_test.go | 26 +++++------ cla-backend-go/gitlab/init.go | 44 +++++++++++++++---- cla-backend-go/v2/gitlab-activity/service.go | 6 ++- .../v2/gitlab_organizations/service.go | 6 ++- 7 files changed, 63 insertions(+), 39 deletions(-) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 72edafe5f..f396fd985 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -94,7 +94,7 @@ func init() { token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // initialize gitlab - gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) + _ = gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) user_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) project_service.InitClient(configFile.APIGatewayURL) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 35e59ba7a..3f1370192 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -237,7 +237,7 @@ func server(localMode bool) http.Handler { // initialize github github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // initialize gitlab - gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) + _ = gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) diff --git a/cla-backend-go/gitlab/client.go b/cla-backend-go/gitlab/client.go index 757267078..06a0e7e5f 100644 --- a/cla-backend-go/gitlab/client.go +++ b/cla-backend-go/gitlab/client.go @@ -28,8 +28,8 @@ type OauthSuccessResponse struct { } // NewGitlabOauthClient creates a new gitlab client from the given oauth info, authInfo is encrypted -func NewGitlabOauthClient(authInfo string) (*gitlab.Client, error) { - oauthResp, err := DecryptAuthInfo(authInfo) +func NewGitlabOauthClient(authInfo string, gitLabApp *App) (*gitlab.Client, error) { + oauthResp, err := DecryptAuthInfo(authInfo, gitLabApp) if err != nil { return nil, err } @@ -39,9 +39,8 @@ func NewGitlabOauthClient(authInfo string) (*gitlab.Client, error) { } // EncryptAuthInfo encrypts the oauth response into a string -func EncryptAuthInfo(oauthResp *OauthSuccessResponse) (string, error) { - key := getGitLabAppPrivateKey() - keyDecoded, err := base64.StdEncoding.DecodeString(key) +func EncryptAuthInfo(oauthResp *OauthSuccessResponse, gitLabApp *App) (string, error) { + keyDecoded, err := base64.StdEncoding.DecodeString(gitLabApp.GetAppPrivateKey()) if err != nil { return "", fmt.Errorf("problem decoding GitLab private glClientKey, error: %v", err) } @@ -61,8 +60,8 @@ func EncryptAuthInfo(oauthResp *OauthSuccessResponse) (string, error) { return hex.EncodeToString(encrypted), nil } -// DecryptAuthInfo decrytps the auth info into OauthSuccessResponse data structure -func DecryptAuthInfo(authInfoEncoded string) (*OauthSuccessResponse, error) { +// DecryptAuthInfo decrypts the auth info into OauthSuccessResponse data structure +func DecryptAuthInfo(authInfoEncoded string, gitLabApp *App) (*OauthSuccessResponse, error) { ciphertext, err := hex.DecodeString(authInfoEncoded) if err != nil { return nil, fmt.Errorf("decode auth info %s : %v", authInfoEncoded, err) @@ -70,8 +69,7 @@ func DecryptAuthInfo(authInfoEncoded string) (*OauthSuccessResponse, error) { //log.Infof("auth info decoded : %s", ciphertext) - key := getGitLabAppPrivateKey() - keyDecoded, err := base64.StdEncoding.DecodeString(key) + keyDecoded, err := base64.StdEncoding.DecodeString(gitLabApp.GetAppPrivateKey()) if err != nil { return nil, fmt.Errorf("decode glClientKey : %v", err) } diff --git a/cla-backend-go/gitlab/client_test.go b/cla-backend-go/gitlab/client_test.go index 30855ba71..45149a423 100644 --- a/cla-backend-go/gitlab/client_test.go +++ b/cla-backend-go/gitlab/client_test.go @@ -15,45 +15,39 @@ var glClientKey = "0WqnDWHnZKo2cmQ8m93EtY9ZBpfzQW4UnnEuRmgtJKM=" var oauthRespStr = `{"access_token":"a30671b8749ba5d48925712344377f11a5aba43ec630f099e464b9843796e6a6","token_type":"Bearer","expires_in":0,"refresh_token":"0838a31d0d796973eacefdf513523e6e47aa06fac9d26622964da1e473509458","created_at":1626435922}` func TestNewGitlabOauthClient(t *testing.T) { - Init(glClientID, glClientKey) - t.Cleanup(func() { - gitLabAppPrivateKey = "" - }) + gitLabApp := Init(glClientID, glClientKey) - t.Logf("app private ID is : %s", getGitLabAppID()) - t.Logf("app private key is : %s", getGitLabAppPrivateKey()) + t.Logf("app private ID is : %s", gitLabApp.GetAppID()) + t.Logf("app private key is : %s", gitLabApp.GetAppPrivateKey()) var oauthResp OauthSuccessResponse err := json.Unmarshal([]byte(oauthRespStr), &oauthResp) assert.NoError(t, err) - encrypted, err := EncryptAuthInfo(&oauthResp) + encrypted, err := EncryptAuthInfo(&oauthResp, gitLabApp) assert.NoError(t, err) - client, err := NewGitlabOauthClient(encrypted) + client, err := NewGitlabOauthClient(encrypted, gitLabApp) assert.NoError(t, err) assert.NotNil(t, client) } func TestEncryptDecryptAuthInfo(t *testing.T) { - Init(glClientID, glClientKey) - t.Cleanup(func() { - gitLabAppPrivateKey = "" - }) + gitLabApp := Init(glClientID, glClientKey) - t.Logf("app private ID is : %s", getGitLabAppID()) - t.Logf("app private key is : %s", getGitLabAppPrivateKey()) + t.Logf("app private ID is : %s", gitLabApp.GetAppID()) + t.Logf("app private key is : %s", gitLabApp.GetAppPrivateKey()) var oauthResp OauthSuccessResponse err := json.Unmarshal([]byte(oauthRespStr), &oauthResp) assert.NoError(t, err) t.Logf("unmarshall ok : %+v", oauthResp) - encrypted, err := EncryptAuthInfo(&oauthResp) + encrypted, err := EncryptAuthInfo(&oauthResp, gitLabApp) assert.NoError(t, err) t.Logf("encrypted auth info : %s", encrypted) - oauthRespDecrypted, err := DecryptAuthInfo(encrypted) + oauthRespDecrypted, err := DecryptAuthInfo(encrypted, gitLabApp) assert.NoError(t, err) assert.Equal(t, &oauthResp, oauthRespDecrypted) diff --git a/cla-backend-go/gitlab/init.go b/cla-backend-go/gitlab/init.go index c15db8ce9..8ed849ce3 100644 --- a/cla-backend-go/gitlab/init.go +++ b/cla-backend-go/gitlab/init.go @@ -3,19 +3,45 @@ package gitlab -var gitLabAppPrivateKey string -var gitLabAppID string +import ( + "sync" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) + +// App is a wrapper for the GitLab configuration items +type App struct { + gitLabAppPrivateKey string + gitLabAppID string +} + +var gitLabAppSingleton *App + +var once sync.Once // Init initializes the required gitlab variables -func Init(glAppID string, glAppPrivateKey string) { - gitLabAppID = glAppID - gitLabAppPrivateKey = glAppPrivateKey +func Init(glAppID string, glAppPrivateKey string) *App { + if gitLabAppSingleton == nil { + once.Do( + func() { + log.Debug("Creating object single instance...") + gitLabAppSingleton = &App{ + gitLabAppID: glAppID, + gitLabAppPrivateKey: glAppPrivateKey, + } + }) + } else { + log.Debug("GitLabApp object single instance already - returning singleton instance") + } + return gitLabAppSingleton } -func getGitLabAppID() string { - return gitLabAppID +// GetAppID returns the GitLab application ID +func (app *App) GetAppID() string { + return app.gitLabAppID } -func getGitLabAppPrivateKey() string { - return gitLabAppPrivateKey +// GetAppPrivateKey returns the GitLab application private key +func (app *App) GetAppPrivateKey() string { + return app.gitLabAppPrivateKey } diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 2e00e82f2..30dce2993 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/communitybridge/easycla/cla-backend-go/company" signatures1 "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/signatures" @@ -43,6 +45,7 @@ type service struct { projectsCLAGroupsRepository projects_cla_groups.Repository companyRepository company.IRepository signatureRepository signatures.SignatureRepository + gitLabApp *gitlab2.App } func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRepository repositories.RepositoryInterface, gitV2Repository gitV2Repositories.RepositoryInterface, usersRepository users.UserRepository, signaturesRepository signatures.SignatureRepository, projectsCLAGroupsRepository projects_cla_groups.Repository, @@ -56,6 +59,7 @@ func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRe projectsCLAGroupsRepository: projectsCLAGroupsRepository, companyRepository: companyRepository, signatureRepository: signatureRepository, + gitLabApp: gitlab2.Init(config.GetConfig().Gitlab.AppID, config.GetConfig().Gitlab.AppPrivateKey), } } @@ -85,7 +89,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git log.WithFields(f).Debugf("internal gitlab org : %s:%s is associated with external path : %s", gitlabOrg.OrganizationID, gitlabOrg.OrganizationName, repositoryPath) - gitlabClient, err := gitlab2.NewGitlabOauthClient(gitlabOrg.AuthInfo) + gitlabClient, err := gitlab2.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) if err != nil { return fmt.Errorf("initializing gitlab client : %v", err) } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index ef0d8b714..f813f9bd9 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -37,6 +37,7 @@ type Service interface { type service struct { repo RepositoryInterface claGroupRepository projects_cla_groups.Repository + gitLabApp *gitlab.App } // NewService creates a new gitlab organization service @@ -44,6 +45,7 @@ func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups return service{ repo: repo, claGroupRepository: claGroupRepository, + gitLabApp: gitlab.Init(config.GetConfig().Gitlab.AppID, config.GetConfig().Gitlab.AppPrivateKey), } } @@ -71,7 +73,7 @@ func (s service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganiz } log.WithFields(f).Debugf("updating gitlab org auth") - authInfoEncrypted, err := gitlab.EncryptAuthInfo(oauthResp) + authInfoEncrypted, err := gitlab.EncryptAuthInfo(oauthResp, s.gitLabApp) if err != nil { return fmt.Errorf("encrypt failed : %v", err) } @@ -228,7 +230,7 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) if orgDetailed.AuthInfo == "" { rorg.ConnectionStatus = utils.NoConnection } else { - glClient, err := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo) + glClient, err := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) if err != nil { log.WithFields(f).Errorf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) rorg.ConnectionStatus = utils.ConnectionFailure From 8798a46983db63a5ee8b5838f6545b65e163cda3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 12 Aug 2021 23:28:55 -0700 Subject: [PATCH 0416/1276] Added More Debug for Callback (#3151) Signed-off-by: David Deal --- cla-backend-go/cmd/gitlab/auth/main.go | 12 ++++++------ cla-backend-go/config/ssm.go | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/cmd/gitlab/auth/main.go b/cla-backend-go/cmd/gitlab/auth/main.go index 798a07ef6..33c72e56d 100644 --- a/cla-backend-go/cmd/gitlab/auth/main.go +++ b/cla-backend-go/cmd/gitlab/auth/main.go @@ -18,9 +18,9 @@ import ( ) const ( - REDIRECT_URI = "http://localhost:8080/gitlab/oauth/callback" - APPLICATION_ID = "18718b478096e6a257eda51414d0d446ad28866c15187aa765f602fe906d0b17" - APPLICATION_SECRET = "8dd14ace0eb0e4674b849b6fed4ce51bbcc456fc62d9149aff15353c1dda6327" + clientRedirectURI = "http://localhost:8080/gitlab/oauth/callback" + clientAppID = "18718b478096e6a257eda51414d0d446ad28866c15187aa765f602fe906d0b17" + clientAppSecret = "8dd14ace0eb0e4674b849b6fed4ce51bbcc456fc62d9149aff15353c1dda6327" ) const ( @@ -168,11 +168,11 @@ func main() { client := resty.New() params := map[string]string{ - "client_id": APPLICATION_ID, - "client_secret": APPLICATION_SECRET, + "client_id": clientAppID, + "client_secret": clientAppSecret, "code": code, "grant_type": "authorization_code", - "redirect_uri": REDIRECT_URI, + "redirect_uri": clientRedirectURI, } resp, err := client.R(). diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index d9c4ffeac..643216973 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -159,8 +159,10 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.Gitlab.ClientSecret = resp.value case fmt.Sprintf("cla-gitlab-app-id-%s", stage): config.Gitlab.AppID = resp.value + log.WithFields(f).Debugf("CLA GitLab App ID: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) case fmt.Sprintf("cla-gitlab-app-private-key-%s", stage): config.Gitlab.AppPrivateKey = resp.value + log.WithFields(f).Debugf("CLA GitLab App Private Key: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) case fmt.Sprintf("cla-gitlab-app-redirect-uri-%s", stage): config.Gitlab.RedirectURI = resp.value case fmt.Sprintf("cla-gitlab-app-web-hook-uri-%s", stage): From 8c15b0e54f01367517e06df6eb8a1c20e3153b99 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 13 Aug 2021 08:48:08 -0700 Subject: [PATCH 0417/1276] Testing GitLab Secret Fix (#3152) --- cla-backend-go/config/ssm.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 643216973..1cf481f26 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -155,13 +155,14 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.GitHub.TestRepositoryID = resp.value // gitlab ssm - case fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage): - config.Gitlab.ClientSecret = resp.value + //case fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage): + // config.Gitlab.ClientSecret = resp.value case fmt.Sprintf("cla-gitlab-app-id-%s", stage): config.Gitlab.AppID = resp.value log.WithFields(f).Debugf("CLA GitLab App ID: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) case fmt.Sprintf("cla-gitlab-app-private-key-%s", stage): config.Gitlab.AppPrivateKey = resp.value + config.Gitlab.ClientSecret = resp.value log.WithFields(f).Debugf("CLA GitLab App Private Key: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) case fmt.Sprintf("cla-gitlab-app-redirect-uri-%s", stage): config.Gitlab.RedirectURI = resp.value From b5344bfcbf9d23c06ade7527aebd9585b08668e2 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 13 Aug 2021 11:02:51 -0700 Subject: [PATCH 0418/1276] Added GitLab App Client Secret (#3153) --- cla-backend-go/cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/config/config.go | 12 ++++++------ cla-backend-go/config/ssm.go | 14 +++++++++----- cla-backend-go/gitlab/auth.go | 12 ++++++------ cla-backend-go/gitlab/client_test.go | 5 +++-- cla-backend-go/gitlab/init.go | 11 +++++++++-- cla-backend-go/v2/gitlab-activity/service.go | 2 +- cla-backend-go/v2/gitlab_organizations/service.go | 5 ++--- 9 files changed, 38 insertions(+), 27 deletions(-) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index f396fd985..ceb81722d 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -94,7 +94,7 @@ func init() { token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // initialize gitlab - _ = gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) + _ = gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) user_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) project_service.InitClient(configFile.APIGatewayURL) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 3f1370192..67fdf51ae 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -237,7 +237,7 @@ func server(localMode bool) http.Handler { // initialize github github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // initialize gitlab - _ = gitlab.Init(configFile.Gitlab.AppID, configFile.Gitlab.AppPrivateKey) + _ = gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 07c6d58dd..47407cf23 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -137,13 +137,13 @@ type GitHub struct { TestRepositoryID string `json:"test_repository_id"` } -// Gitlab model +// Gitlab config data model type Gitlab struct { - ClientSecret string `json:"clientSecret"` - AppID string `json:"app_id"` - AppPrivateKey string `json:"app_private_key"` - RedirectURI string `json:"redirect_uri"` - WebHookURI string `json:"web_hook_uri"` + AppClientID string `json:"app_client_id"` + AppClientSecret string `json:"app_client_secret"` + AppPrivateKey string `json:"app_client_private_key"` + RedirectURI string `json:"app_redirect_uri"` + WebHookURI string `json:"app_web_hook_uri"` } // MetricsReport keeps the config needed to send the metrics data report diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 1cf481f26..b0f80e695 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -69,8 +69,9 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-gh-test-organization-installation-id-%s", stage), fmt.Sprintf("cla-gh-test-repository-%s", stage), fmt.Sprintf("cla-gh-test-repository-id-%s", stage), - fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage), + //fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage), fmt.Sprintf("cla-gitlab-app-id-%s", stage), + fmt.Sprintf("cla-gitlab-app-secret-%s", stage), fmt.Sprintf("cla-gitlab-app-private-key-%s", stage), fmt.Sprintf("cla-gitlab-app-redirect-uri-%s", stage), fmt.Sprintf("cla-gitlab-app-web-hook-uri-%s", stage), @@ -155,14 +156,17 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.GitHub.TestRepositoryID = resp.value // gitlab ssm - //case fmt.Sprintf("cla-gitlab-oauth-secret-go-backend-%s", stage): - // config.Gitlab.ClientSecret = resp.value case fmt.Sprintf("cla-gitlab-app-id-%s", stage): - config.Gitlab.AppID = resp.value + config.Gitlab.AppClientID = resp.value + // DEBUG log.WithFields(f).Debugf("CLA GitLab App ID: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) + case fmt.Sprintf("cla-gitlab-app-secret-%s", stage): + config.Gitlab.AppClientSecret = resp.value + // DEBUG + log.WithFields(f).Debugf("CLA GitLab App Secret: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) case fmt.Sprintf("cla-gitlab-app-private-key-%s", stage): config.Gitlab.AppPrivateKey = resp.value - config.Gitlab.ClientSecret = resp.value + // DEBUG log.WithFields(f).Debugf("CLA GitLab App Private Key: %s...%s", resp.value[0:4], resp.value[len(resp.value)-4:]) case fmt.Sprintf("cla-gitlab-app-redirect-uri-%s", stage): config.Gitlab.RedirectURI = resp.value diff --git a/cla-backend-go/gitlab/auth.go b/cla-backend-go/gitlab/auth.go index 6f374437c..d18fce41e 100644 --- a/cla-backend-go/gitlab/auth.go +++ b/cla-backend-go/gitlab/auth.go @@ -22,13 +22,13 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { "redirectURI": config.GetConfig().Gitlab.RedirectURI, } - if len(gitLabConfig.AppID) > 4 { - f["gitLabClientID"] = fmt.Sprintf("%s...%s", gitLabConfig.AppID[0:4], gitLabConfig.AppID[len(gitLabConfig.AppID)-4:]) + if len(gitLabConfig.AppClientID) > 4 { + f["gitLabClientID"] = fmt.Sprintf("%s...%s", gitLabConfig.AppClientID[0:4], gitLabConfig.AppClientID[len(gitLabConfig.AppClientID)-4:]) } else { return nil, errors.New("gitlab application client ID value is not set - value is empty or malformed") } - if len(gitLabConfig.ClientSecret) > 4 { - f["gitLabClientSecret"] = fmt.Sprintf("%s...%s", gitLabConfig.ClientSecret[0:4], gitLabConfig.ClientSecret[len(gitLabConfig.ClientSecret)-4:]) + if len(gitLabConfig.AppClientSecret) > 4 { + f["gitLabClientSecret"] = fmt.Sprintf("%s...%s", gitLabConfig.AppClientSecret[0:4], gitLabConfig.AppClientSecret[len(gitLabConfig.AppClientSecret)-4:]) } else { return nil, errors.New("gitlab application client secret value is not set - value is empty or malformed") } @@ -36,8 +36,8 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { // For info on this authorization flow, see: https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow client := resty.New() params := map[string]string{ - "client_id": gitLabConfig.AppID, - "client_secret": gitLabConfig.ClientSecret, + "client_id": gitLabConfig.AppClientID, + "client_secret": gitLabConfig.AppClientSecret, "code": code, "grant_type": "authorization_code", "redirect_uri": gitLabConfig.RedirectURI, diff --git a/cla-backend-go/gitlab/client_test.go b/cla-backend-go/gitlab/client_test.go index 45149a423..7c6d0967c 100644 --- a/cla-backend-go/gitlab/client_test.go +++ b/cla-backend-go/gitlab/client_test.go @@ -11,11 +11,12 @@ import ( ) var glClientID = "124453345" +var glClientSecret = "124453345" var glClientKey = "0WqnDWHnZKo2cmQ8m93EtY9ZBpfzQW4UnnEuRmgtJKM=" var oauthRespStr = `{"access_token":"a30671b8749ba5d48925712344377f11a5aba43ec630f099e464b9843796e6a6","token_type":"Bearer","expires_in":0,"refresh_token":"0838a31d0d796973eacefdf513523e6e47aa06fac9d26622964da1e473509458","created_at":1626435922}` func TestNewGitlabOauthClient(t *testing.T) { - gitLabApp := Init(glClientID, glClientKey) + gitLabApp := Init(glClientID, glClientSecret, glClientKey) t.Logf("app private ID is : %s", gitLabApp.GetAppID()) t.Logf("app private key is : %s", gitLabApp.GetAppPrivateKey()) @@ -33,7 +34,7 @@ func TestNewGitlabOauthClient(t *testing.T) { } func TestEncryptDecryptAuthInfo(t *testing.T) { - gitLabApp := Init(glClientID, glClientKey) + gitLabApp := Init(glClientID, glClientSecret, glClientKey) t.Logf("app private ID is : %s", gitLabApp.GetAppID()) t.Logf("app private key is : %s", gitLabApp.GetAppPrivateKey()) diff --git a/cla-backend-go/gitlab/init.go b/cla-backend-go/gitlab/init.go index 8ed849ce3..e3beb6127 100644 --- a/cla-backend-go/gitlab/init.go +++ b/cla-backend-go/gitlab/init.go @@ -11,8 +11,9 @@ import ( // App is a wrapper for the GitLab configuration items type App struct { - gitLabAppPrivateKey string gitLabAppID string + gitLabAppSecret string + gitLabAppPrivateKey string } var gitLabAppSingleton *App @@ -20,13 +21,14 @@ var gitLabAppSingleton *App var once sync.Once // Init initializes the required gitlab variables -func Init(glAppID string, glAppPrivateKey string) *App { +func Init(glAppID, glAppSecret, glAppPrivateKey string) *App { if gitLabAppSingleton == nil { once.Do( func() { log.Debug("Creating object single instance...") gitLabAppSingleton = &App{ gitLabAppID: glAppID, + gitLabAppSecret: glAppSecret, gitLabAppPrivateKey: glAppPrivateKey, } }) @@ -41,6 +43,11 @@ func (app *App) GetAppID() string { return app.gitLabAppID } +// GetAppSecret returns the GitLab application secret +func (app *App) GetAppSecret() string { + return app.gitLabAppSecret +} + // GetAppPrivateKey returns the GitLab application private key func (app *App) GetAppPrivateKey() string { return app.gitLabAppPrivateKey diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 30dce2993..f2a416338 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -59,7 +59,7 @@ func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRe projectsCLAGroupsRepository: projectsCLAGroupsRepository, companyRepository: companyRepository, signatureRepository: signatureRepository, - gitLabApp: gitlab2.Init(config.GetConfig().Gitlab.AppID, config.GetConfig().Gitlab.AppPrivateKey), + gitLabApp: gitlab2.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index f813f9bd9..fb8808d0c 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -45,7 +45,7 @@ func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups return service{ repo: repo, claGroupRepository: claGroupRepository, - gitLabApp: gitlab.Init(config.GetConfig().Gitlab.AppID, config.GetConfig().Gitlab.AppPrivateKey), + gitLabApp: gitlab.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } @@ -79,7 +79,6 @@ func (s service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganiz } return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, authInfoEncrypted) - } func (s service) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { @@ -296,7 +295,7 @@ func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI state := fmt.Sprintf("%s:%s", gitlabOrgID, authStateNonce) params := url.Values{} - params.Add("client_id", c.Gitlab.AppID) + params.Add("client_id", c.Gitlab.AppClientID) params.Add("redirect_uri", c.Gitlab.RedirectURI) //params.Add("redirect_uri", "http://localhost:8080/v4/gitlab/oauth/callback") params.Add("response_type", "code") From 3877167a346d1ea0bd2adc46bb238d5b5f5079d9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 13 Aug 2021 16:35:11 -0700 Subject: [PATCH 0419/1276] Adding GitLab Install Success Page (#3154) --- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/gitlab/client.go | 5 + .../models.go | 7 +- cla-backend-go/v2/gitlab-activity/service.go | 4 +- .../v2/gitlab_organizations/handlers.go | 135 ++++++++++-- .../v2/gitlab_organizations/repository.go | 32 +-- .../v2/gitlab_organizations/service.go | 196 +++++++++++------- .../v2/repositories/gitlab_services.go | 74 +++++++ cla-backend-go/v2/repositories/service.go | 9 +- 9 files changed, 357 insertions(+), 109 deletions(-) rename cla-backend-go/v2/{gitlab_organizations => common}/models.go (86%) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 67fdf51ae..a3a34bef8 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -345,10 +345,10 @@ func server(localMode bool) http.Handler { v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) - gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) - gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) + gitlab_organizations.Configure(v2API, gitlabOrganizationsService, v2RepositoriesService, eventsService) + gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, v1ProjectClaGroupRepo) v2Company.Configure(v2API, v2CompanyService, v1ProjectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleV1URL) diff --git a/cla-backend-go/gitlab/client.go b/cla-backend-go/gitlab/client.go index 06a0e7e5f..145a9e73c 100644 --- a/cla-backend-go/gitlab/client.go +++ b/cla-backend-go/gitlab/client.go @@ -38,6 +38,11 @@ func NewGitlabOauthClient(authInfo string, gitLabApp *App) (*gitlab.Client, erro return gitlab.NewOAuthClient(oauthResp.AccessToken) } +// NewGitlabOauthClientFromAccessToken creates a new gitlab client from the given access token +func NewGitlabOauthClientFromAccessToken(accessToken string) (*gitlab.Client, error) { + return gitlab.NewOAuthClient(accessToken) +} + // EncryptAuthInfo encrypts the oauth response into a string func EncryptAuthInfo(oauthResp *OauthSuccessResponse, gitLabApp *App) (string, error) { keyDecoded, err := base64.StdEncoding.DecodeString(gitLabApp.GetAppPrivateKey()) diff --git a/cla-backend-go/v2/gitlab_organizations/models.go b/cla-backend-go/v2/common/models.go similarity index 86% rename from cla-backend-go/v2/gitlab_organizations/models.go rename to cla-backend-go/v2/common/models.go index 2094a002b..d3fc221bd 100644 --- a/cla-backend-go/v2/gitlab_organizations/models.go +++ b/cla-backend-go/v2/common/models.go @@ -1,4 +1,4 @@ -package gitlab_organizations +package common // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT @@ -10,6 +10,7 @@ import ( // GitlabOrganization is data model for gitlab organizations type GitlabOrganization struct { OrganizationID string `json:"organization_id"` + ExternalGroupID int `json:"external_gitlab_group_id"` DateCreated string `json:"date_created,omitempty"` DateModified string `json:"date_modified,omitempty"` OrganizationName string `json:"organization_name,omitempty"` @@ -38,10 +39,12 @@ func ToModel(in *GitlabOrganization) *models2.GitlabOrganization { AutoEnabled: in.AutoEnabled, AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, ProjectSFID: in.ProjectSFID, + // Not exposing ExternalGroupID } } -func toModels(input []*GitlabOrganization) []*models2.GitlabOrganization { +// ToModels converts a list of GitLab organizations to a list of external GitLab organization response models +func ToModels(input []*GitlabOrganization) []*models2.GitlabOrganization { out := make([]*models2.GitlabOrganization, 0) for _, in := range input { out = append(out, ToModel(in)) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index f2a416338..625b6d82c 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + "github.com/communitybridge/easycla/cla-backend-go/config" "github.com/communitybridge/easycla/cla-backend-go/company" @@ -160,7 +162,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git return err } -func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*gitlab_organizations.GitlabOrganization, error) { +func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*common.GitlabOrganization, error) { repositoryPath := mergeEvent.Project.PathWithNamespace parts := strings.Split(repositoryPath, "/") organizationName := parts[0] diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index d6100aadc..3a375d69a 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -7,8 +7,11 @@ import ( "context" "errors" "fmt" + "net/http" "strings" + "github.com/go-openapi/runtime" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" @@ -26,11 +29,12 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/utils" + v2GitRepo "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" "github.com/go-openapi/runtime/middleware" ) // Configure setups handlers on api with service -func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service) { +func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Service v2GitRepo.ServiceInterface, eventService events.Service) { api.GitlabOrganizationsGetProjectGitlabOrganizationsHandler = gitlab_organizations.GetProjectGitlabOrganizationsHandlerFunc( func(params gitlab_organizations.GetProjectGitlabOrganizationsParams, authUser *auth.User) middleware.Responder { @@ -283,37 +287,134 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. stateVar := codeParts[1] ctx := context.Background() - _, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) + gitLabOrg, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) if err != nil { - msg := fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err) - log.WithFields(f).Errorf(msg) return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( - utils.ErrorResponseBadRequest(reqID, msg)) + utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err))) } // now fetch the oauth credentials and store to db oauthResp, err := gitlab.FetchOauthCredentials(params.Code) if err != nil { - msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) - log.WithFields(f).Errorf(msg) return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( - utils.ErrorResponseBadRequest(reqID, msg)) + utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err))) } - log.Infof("oauth resp is like : %+v", oauthResp) + log.WithFields(f).Debugf("oauth resp is like : %+v", oauthResp) err = service.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) if err != nil { - msg := fmt.Sprintf("updating gitlab credentials failed : %s : %v", gitlabOrganizationID, err) - log.WithFields(f).Errorf(msg) return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( - utils.ErrorResponseBadRequest(reqID, msg)) + utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("updating gitlab credentials failed : %s : %v", gitlabOrganizationID, err))) } - return gitlab_activity.NewGitlabOauthCallbackOK().WithPayload(&models.SuccessResponse{ - Code: "200", - Message: "oauth credentials stored successfully", - XRequestID: reqID, - }) + // Reload the GitLab organization - will have additional details now... + updatedGitLabOrgDBModel, err := service.GetGitlabOrganizationByID(ctx, gitLabOrg.OrganizationID) + if err != nil { + return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( + utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("problem loading updated gitlab organization by ID: %s : %v", gitlabOrganizationID, err))) + } + + _, err = gitV2Service.GitLabAddRepositoriesByApp(ctx, updatedGitLabOrgDBModel) + if err != nil { + return NewRedirectServerError(reqID, updatedGitLabOrgDBModel.OrganizationName, err) + } + + return NewRedirectOK(reqID, updatedGitLabOrgDBModel.ProjectSFID, updatedGitLabOrgDBModel.OrganizationName) }) } + +// GetRedirectOK Success +type GetRedirectOK struct { + ReqID string + ProjectSFID string + GitLabGroupName string +} + +// NewRedirectOK creates a new redirect handler +func NewRedirectOK(reqID, projectSFID, gitLabGroupName string) *GetRedirectOK { + return &GetRedirectOK{reqID, projectSFID, gitLabGroupName} +} + +// WriteResponse to the client +func (o *GetRedirectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + configPage := "https://gitlab.com/-/profile/applications" + + html := fmt.Sprintf(` + + + LFX EasyCLA Service GitLab App Installation Status + + + + + + + + + +
    + lf logo +
    +

    LFx EasyCLA Service GitLab App - Installation Successful

    +

    Thank you for installing the LFX EasyCLA GitLab Application/Bot. Your GitLab Group and repositories are now onboarded.

    +

    To review the configuration or revoke the application, navigate to the GitLab Applications under your User Settings.

    +

    You may now close this window and return to the LFX Project Control Center and select the repositories for EasyCLA.

    + + `, configPage) + + rw.Header().Set("Content-Type", "text/html") + rw.Header().Set(utils.XREQUESTID, o.ReqID) + rw.WriteHeader(http.StatusOK) + _, err := rw.Write([]byte(html)) + if err != nil { + panic(err) + } +} + +// GetRedirectServerError Success +type GetRedirectServerError struct { + ReqID string + GitLabGroupName string + Error error +} + +// NewRedirectServerError creates a new redirect handler +func NewRedirectServerError(reqID string, gitLabGroupName string, theError error) *GetRedirectServerError { + return &GetRedirectServerError{ + ReqID: reqID, + GitLabGroupName: gitLabGroupName, + Error: theError, + } +} + +// WriteResponse to the client +func (o *GetRedirectServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + html := fmt.Sprintf(` + + + LFX EasyCLA Service GitLab App Installation Status + + + + + + + + + +
    + lf logo +
    +

    LFx EasyCLA Service GitLab App - Installation Issue

    +

    Unable to install the GitLab Group %s due to the following error: %s.

    + + `, o.GitLabGroupName, o.Error.Error()) + + rw.Header().Set("Content-Type", "text/html") + rw.Header().Set(utils.XREQUESTID, o.ReqID) + _, err := rw.Write([]byte(html)) + if err != nil { + panic(err) + } +} diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 2a6bd05ca..db9097b97 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -7,8 +7,11 @@ import ( "context" "errors" "fmt" + "strconv" "strings" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + "github.com/gofrs/uuid" "github.com/aws/aws-sdk-go/aws" @@ -34,9 +37,9 @@ const ( type RepositoryInterface interface { AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) - GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) + GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models2.GitlabOrganization, error) - UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error + UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, gitLabGroupID int, authInfo string) error UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } @@ -110,7 +113,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS } enabled := true - gitlabOrg := &GitlabOrganization{ + gitlabOrg := &common.GitlabOrganization{ OrganizationID: organizationID.String(), DateCreated: currentTime, DateModified: currentTime, @@ -151,7 +154,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS return nil, err } - return ToModel(gitlabOrg), nil + return common.ToModel(gitlabOrg), nil } // GetGitlabOrganizations get GitLab organizations based on the project SFID @@ -199,7 +202,7 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s }, nil } - var resultOutput []*GitlabOrganization + var resultOutput []*common.GitlabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { return nil, err @@ -249,7 +252,7 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr return nil, nil } - var resultOutput []*GitlabOrganization + var resultOutput []*common.GitlabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) @@ -262,7 +265,7 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr } // GetGitlabOrganization by organization name -func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*GitlabOrganization, error) { +func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.GetGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -286,7 +289,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza return nil, nil } - var org GitlabOrganization + var org common.GitlabOrganization err = dynamodbattribute.UnmarshalMap(result.Item, &org) if err != nil { log.WithFields(f).Warnf("error unmarshalling organization table data, error: %v", err) @@ -296,7 +299,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza } // UpdateGitlabOrganizationAuth updates the specified Gitlab organization oauth info -func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID, authInfo string) error { +func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, gitLabGroupID int, authInfo string) error { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -319,6 +322,7 @@ func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabO expressionAttributeNames := map[string]*string{ "#A": aws.String("auth_info"), "#M": aws.String("date_modified"), + "#P": aws.String("external_gitlab_group_id"), } expressionAttributeValues := map[string]*dynamodb.AttributeValue{ ":a": { @@ -327,8 +331,12 @@ func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabO ":m": { S: aws.String(currentTime), }, + ":p": { + N: aws.String(strconv.Itoa(gitLabGroupID)), + }, } - updateExpression := "SET #A = :a, #M = :m" + + updateExpression := "SET #A = :a, #M = :m, #P = :p" input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ @@ -490,7 +498,7 @@ func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID return nil } -func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*GitlabOrganization) []*models2.GitlabOrganization { +func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*common.GitlabOrganization) []*models2.GitlabOrganization { f := logrus.Fields{ "functionName": "buildGitlabOrganizationListModels", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -498,7 +506,7 @@ func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations log.WithFields(f).Debugf("fetching gitlab info for the list") // Convert the database model to a response model - return toModels(gitlabOrganizations) + return common.ToModels(gitlabOrganizations) // TODO: Fetch the gitlab information } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index fb8808d0c..d40f7269e 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -10,10 +10,12 @@ import ( "sort" "strings" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + "github.com/communitybridge/easycla/cla-backend-go/config" + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" "github.com/go-openapi/strfmt" - //v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gitlab" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -23,97 +25,36 @@ import ( "github.com/sirupsen/logrus" ) -// Service contains functions of GitlabOrganizations service -type Service interface { - GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) +// ServiceInterface contains functions of GitlabOrganizations service +type ServiceInterface interface { AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) + GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) + GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error } -type service struct { +// Service data model +type Service struct { repo RepositoryInterface claGroupRepository projects_cla_groups.Repository gitLabApp *gitlab.App } // NewService creates a new gitlab organization service -func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups.Repository) Service { - return service{ +func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups.Repository) *Service { + return &Service{ repo: repo, claGroupRepository: claGroupRepository, gitLabApp: gitlab.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } -func (s service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { - f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, - } - - log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) - dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) - if err != nil { - return nil, err - } - - return ToModel(dbModel), nil -} - -func (s service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error { - f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.UpdateGitlabOrganizationAuth", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, - } - - log.WithFields(f).Debugf("updating gitlab org auth") - authInfoEncrypted, err := gitlab.EncryptAuthInfo(oauthResp, s.gitLabApp) - if err != nil { - return fmt.Errorf("encrypt failed : %v", err) - } - - return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, authInfoEncrypted) -} - -func (s service) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { - // check if valid cla group id is passed - if autoEnabledClaGroupID != "" { - if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { - return err - } - } - - return s.repo.UpdateGitlabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) -} - -func (s service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) { - f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, - "authState": authState, - } - - log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) - dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) - if err != nil { - return nil, err - } - - if dbModel.AuthState != authState { - return nil, fmt.Errorf("auth state doesn't match") - } - - return ToModel(dbModel), nil -} - -func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { +// AddGitlabOrganization adds the specified GitLab organization +func (s *Service) AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.AddGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -151,7 +92,39 @@ func (s service) AddGitlabOrganization(ctx context.Context, projectSFID string, return s.GetGitlabOrganizations(ctx, projectSFID) } -func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { +// GetGitlabOrganization returns the GitLab organization based on the specified GitLab Organization ID +func (s *Service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { + dbModel, err := s.GetGitlabOrganizationByID(ctx, gitlabOrganizationID) + if err != nil { + return nil, err + } + + if dbModel == nil { + return nil, nil + } + + return common.ToModel(dbModel), err +} + +// GetGitlabOrganizationByID returns the record associated with the GitLab Organization ID +func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + } + + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) + dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + if err != nil { + return nil, err + } + + return dbModel, nil +} + +// GetGitlabOrganizations returns a collection of GitLab organizations based on the specified project SFID value +func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -262,7 +235,82 @@ func (s service) GetGitlabOrganizations(ctx context.Context, projectSFID string) return out, nil } -func (s service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error { +// GetGitlabOrganizationByState returns the GitLab organization by the auth state +func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + "authState": authState, + } + + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) + dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + if err != nil { + return nil, err + } + + if dbModel.AuthState != authState { + return nil, fmt.Errorf("auth state doesn't match") + } + + return common.ToModel(dbModel), nil +} + +// UpdateGitlabOrganizationAuth updates the GitLab organization authentication information +func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.UpdateGitlabOrganizationAuth", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationID, + } + + log.WithFields(f).Debugf("updating gitlab org auth") + authInfoEncrypted, err := gitlab.EncryptAuthInfo(oauthResp, s.gitLabApp) + if err != nil { + return fmt.Errorf("encrypt failed : %v", err) + } + + gitLabOrgModel, err := s.GetGitlabOrganizationByID(ctx, gitlabOrganizationID) + if err != nil { + return fmt.Errorf("gitlab organization lookup error: %+v", err) + } + + // Get the client + gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(oauthResp.AccessToken) + if err != nil { + return fmt.Errorf("initializing gitlab client : %v", err) + } + + // Need to lookup the GitLab Group/Organization to obtain the ID + groups, resp, searchErr := gitLabClient.Groups.SearchGroup(gitLabOrgModel.OrganizationName) + if searchErr != nil { + return fmt.Errorf("GitLab search error while locating Group by name: %s, error: %v", gitLabOrgModel.OrganizationName, searchErr) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return fmt.Errorf("unable to locate GitLab group by name: %s, status code: %d", gitLabOrgModel.OrganizationName, resp.StatusCode) + } + if len(groups) != 1 { + return fmt.Errorf("expecting 1 result for GitLab group name '%s' search, found: %d", gitLabOrgModel.OrganizationName, len(groups)) + } + + return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, groups[0].ID, authInfoEncrypted) +} + +// UpdateGitlabOrganization updates the GitLab organization +func (s *Service) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { + // check if valid cla group id is passed + if autoEnabledClaGroupID != "" { + if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { + return err + } + } + + return s.repo.UpdateGitlabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) +} + +// DeleteGitlabOrganization deletes the specified GitLab organization +func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error { f := logrus.Fields{ "functionName": "DeleteGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 7e2445dd5..c077eec40 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -5,8 +5,18 @@ package repositories import ( "context" + "fmt" "strconv" + v2GitLabOrg "github.com/communitybridge/easycla/cla-backend-go/v2/common" + + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/xanzy/go-gitlab" + + "github.com/sirupsen/logrus" + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" repoModels "github.com/communitybridge/easycla/cla-backend-go/repositories" ) @@ -21,6 +31,70 @@ func (s *Service) GitLabAddRepository(ctx context.Context, projectSFID string, i return dbModelToGitLabRepository(dbModel) } +// GitLabAddRepositoriesByApp adds the GitLab repositories based on the application credentials +func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *v2GitLabOrg.GitlabOrganization) ([]*v2Models.GitlabRepository, error) { + f := logrus.Fields{ + "functionName": "v2.repositories.gitlab_services.GitLabAddRepositoriesByApp", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": gitLabOrgModel.ProjectSFID, + "organizationName": gitLabOrgModel.OrganizationName, + } + // Get the client + gitlabClient, err := gitlab2.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) + if err != nil { + return nil, fmt.Errorf("initializing gitlab client : %v", err) + } + + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + opt := &gitlab.ListTreeOptions{ + ListOptions: gitlab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, + }, + //Path *string `url:"path,omitempty" json:"path,omitempty"` + //Ref *string `url:"ref,omitempty" json:"ref,omitempty"` + Recursive: utils.Bool(true), + } + + tree, resp, listTreeErr := gitlabClient.Repositories.ListTree(gitLabOrgModel.ExternalGroupID, opt) + if listTreeErr != nil { + return nil, fmt.Errorf("unable to locate GitLab repositories group ID: %d, error: %+v", gitLabOrgModel.ExternalGroupID, listTreeErr) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf("unable to locate GitLab repositories group ID: %d, status code: %d", gitLabOrgModel.ExternalGroupID, resp.StatusCode) + } + + // Add repos to table + for _, tr := range tree { + log.WithFields(f).Debugf("Repository: %s, path: %s, id: %s", tr.Name, tr.Path, tr.ID) + externalID, convertErr := strconv.ParseInt(tr.ID, 10, 64) + if convertErr != nil { + return nil, convertErr + } + + _, addRepoErr := s.GitLabAddRepository(ctx, gitLabOrgModel.ProjectSFID, &v2Models.GitlabAddRepository{ + Enabled: true, + Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), + RepositoryExternalID: utils.Int64(externalID), + RepositoryName: utils.StringRef(tr.Name), + RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), + RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), + RepositoryURL: utils.StringRef(tr.Path), + //RepositoryClaGroupID: utils.StringRef(gitLabOrgModel.), + }) + if addRepoErr != nil { + return nil, addRepoErr + } + } + + // Return the list of repos to caller + dbModels, getRepoErr := s.gitV2Repository.GitHubGetRepositoriesByProjectSFID(ctx, gitLabOrgModel.ProjectSFID) + if getRepoErr != nil { + return nil, getRepoErr + } + return dbModelsToGitLabRepositories(dbModels) +} + // GitLabGetRepository service function func (s *Service) GitLabGetRepository(ctx context.Context, repositoryID string) (*v2Models.GitlabRepository, error) { dbModel, err := s.gitV2Repository.GitLabGetRepository(ctx, repositoryID) diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 3b80b9d1f..b59c5bab4 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -9,6 +9,11 @@ import ( "fmt" "strconv" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + + "github.com/communitybridge/easycla/cla-backend-go/config" + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" "github.com/sirupsen/logrus" @@ -47,10 +52,10 @@ type ServiceInterface interface { GitLabGetRepository(ctx context.Context, repositoryID string) (*v2Models.GitlabRepository, error) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*v2Models.GitlabRepository, error) - //GitLabGetRepositoriesByGitLabID(ctx context.Context, gitLabID int64) (*v2Models.GitlabRepository, error) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabListRepositories, error) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) + GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitlabOrganization) ([]*v2Models.GitlabRepository, error) GitLabEnableRepository(ctx context.Context, repositoryID string) error GitLabDisableRepository(ctx context.Context, repositoryID string) error GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error @@ -69,6 +74,7 @@ type Service struct { gitV2Repository RepositoryInterface projectsClaGroupsRepo projects_cla_groups.Repository ghOrgRepo GithubOrgRepo + gitLabApp *gitlab2.App } var ( @@ -84,6 +90,7 @@ func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository Repo gitV2Repository: gitV2Repository, projectsClaGroupsRepo: pcgRepo, ghOrgRepo: ghOrgRepo, + gitLabApp: gitlab2.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } From bbb9cfd88411eab2a3c883e150ef7de6564f8556 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 13 Aug 2021 17:20:27 -0700 Subject: [PATCH 0420/1276] Updated GitLab Group Search (#3155) --- cla-backend-go/tests/gitlab_client_test.go | 43 ++++++++++++++ .../v2/gitlab_organizations/handlers.go | 58 +++++++++---------- .../v2/gitlab_organizations/service.go | 21 +++++-- 3 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 cla-backend-go/tests/gitlab_client_test.go diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go new file mode 100644 index 000000000..e07ae9fd8 --- /dev/null +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -0,0 +1,43 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "fmt" + "testing" + + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/stretchr/testify/assert" + "github.com/xanzy/go-gitlab" +) + +const enabled = false // nolint + +func TestGitLabSearchGroup(t *testing.T) { // no lint + + if enabled { // nolint + // Get the client + accessToken := "" //update to run this test + group := "The Linux Foundation/product/EasyCLA" + gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) + assert.Nil(t, err, "GitLab OAuth Client") + + // Need to look up the GitLab Group/Organization to obtain the ID + opts := &gitlab.ListGroupsOptions{ + ListOptions: gitlab.ListOptions{}, + } + groups, resp, searchErr := gitLabClient.Groups.ListGroups(opts) + assert.Nil(t, searchErr, "GitLab OAuth Client") + if resp.StatusCode < 200 || resp.StatusCode > 299 { + assert.Fail(t, "unable to locate GitLab group by name: %s, status code: %d", group, resp.StatusCode) + } + for _, g := range groups { + t.Logf("group name: %s, ID: %d, path: %s", g.Name, g.ID, g.Path) + } + if len(groups) != 1 { + + assert.Fail(t, fmt.Sprintf("expecting 1 result for GitLab group name '%s' search, found: %d - %+v", group, len(groups), groups)) + } + } +} diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 3a375d69a..bf17c2847 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -264,23 +264,20 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Servic if params.Code == "" { msg := "missing code parameter" log.WithFields(f).Errorf(msg) - return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( - utils.ErrorResponseBadRequest(reqID, msg)) + return NewServerError(reqID, "", errors.New(msg)) } if params.State == "" { msg := "missing state parameter" log.WithFields(f).Errorf(msg) - return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( - utils.ErrorResponseBadRequest(reqID, msg)) + return NewServerError(reqID, "", errors.New(msg)) } codeParts := strings.Split(params.State, ":") if len(codeParts) != 2 { msg := fmt.Sprintf("invalid state variable passed : %s", params.State) log.WithFields(f).Errorf(msg) - return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( - utils.ErrorResponseBadRequest(reqID, msg)) + return NewServerError(reqID, "", errors.New(msg)) } gitlabOrganizationID := codeParts[0] @@ -289,55 +286,58 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Servic ctx := context.Background() gitLabOrg, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) if err != nil { - return gitlab_activity.NewGitlabOauthCallbackBadRequest().WithPayload( - utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err))) + msg := fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return NewServerError(reqID, "", errors.New(msg)) } // now fetch the oauth credentials and store to db oauthResp, err := gitlab.FetchOauthCredentials(params.Code) if err != nil { - return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( - utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err))) + msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return NewServerError(reqID, "", errors.New(msg)) } log.WithFields(f).Debugf("oauth resp is like : %+v", oauthResp) err = service.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) if err != nil { - return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( - utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("updating gitlab credentials failed : %s : %v", gitlabOrganizationID, err))) + msg := fmt.Sprintf("updating gitlab credentials failed : %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return NewServerError(reqID, "", errors.New(msg)) } // Reload the GitLab organization - will have additional details now... updatedGitLabOrgDBModel, err := service.GetGitlabOrganizationByID(ctx, gitLabOrg.OrganizationID) if err != nil { - return gitlab_activity.NewGitlabOauthCallbackInternalServerError().WithPayload( - utils.ErrorResponseBadRequest(reqID, fmt.Sprintf("problem loading updated gitlab organization by ID: %s : %v", gitlabOrganizationID, err))) + msg := fmt.Sprintf("problem loading updated gitlab organization by ID: %s : %v", gitlabOrganizationID, err) + log.WithFields(f).Errorf(msg) + return NewServerError(reqID, "", errors.New(msg)) } _, err = gitV2Service.GitLabAddRepositoriesByApp(ctx, updatedGitLabOrgDBModel) if err != nil { - return NewRedirectServerError(reqID, updatedGitLabOrgDBModel.OrganizationName, err) + return NewServerError(reqID, updatedGitLabOrgDBModel.OrganizationName, err) } - return NewRedirectOK(reqID, updatedGitLabOrgDBModel.ProjectSFID, updatedGitLabOrgDBModel.OrganizationName) + return NewSuccessResponse(reqID, updatedGitLabOrgDBModel.ProjectSFID, updatedGitLabOrgDBModel.OrganizationName) }) - } -// GetRedirectOK Success -type GetRedirectOK struct { +// SuccessResponse Success +type SuccessResponse struct { ReqID string ProjectSFID string GitLabGroupName string } -// NewRedirectOK creates a new redirect handler -func NewRedirectOK(reqID, projectSFID, gitLabGroupName string) *GetRedirectOK { - return &GetRedirectOK{reqID, projectSFID, gitLabGroupName} +// NewSuccessResponse creates a new redirect handler +func NewSuccessResponse(reqID, projectSFID, gitLabGroupName string) *SuccessResponse { + return &SuccessResponse{reqID, projectSFID, gitLabGroupName} } // WriteResponse to the client -func (o *GetRedirectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { +func (o *SuccessResponse) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { configPage := "https://gitlab.com/-/profile/applications" html := fmt.Sprintf(` @@ -372,16 +372,16 @@ func (o *GetRedirectOK) WriteResponse(rw http.ResponseWriter, producer runtime.P } } -// GetRedirectServerError Success -type GetRedirectServerError struct { +// ServerError Success +type ServerError struct { ReqID string GitLabGroupName string Error error } -// NewRedirectServerError creates a new redirect handler -func NewRedirectServerError(reqID string, gitLabGroupName string, theError error) *GetRedirectServerError { - return &GetRedirectServerError{ +// NewServerError creates a new redirect handler +func NewServerError(reqID string, gitLabGroupName string, theError error) *ServerError { + return &ServerError{ ReqID: reqID, GitLabGroupName: gitLabGroupName, Error: theError, @@ -389,7 +389,7 @@ func NewRedirectServerError(reqID string, gitLabGroupName string, theError error } // WriteResponse to the client -func (o *GetRedirectServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { +func (o *ServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { html := fmt.Sprintf(` diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index d40f7269e..29f4e24cb 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -23,6 +23,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/sirupsen/logrus" + goGitLab "github.com/xanzy/go-gitlab" ) // ServiceInterface contains functions of GitlabOrganizations service @@ -282,19 +283,29 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani return fmt.Errorf("initializing gitlab client : %v", err) } - // Need to lookup the GitLab Group/Organization to obtain the ID - groups, resp, searchErr := gitLabClient.Groups.SearchGroup(gitLabOrgModel.OrganizationName) + // Need to look up the GitLab Group/Organization to obtain the ID + //groups, resp, searchErr := gitLabClient.Groups.SearchGroup(gitLabOrgModel.OrganizationName) + // Need to look up the GitLab Group/Organization to obtain the ID + opts := &goGitLab.ListGroupsOptions{ + ListOptions: goGitLab.ListOptions{ + Page: 1, + PerPage: 100, + }, + } + groups, resp, searchErr := gitLabClient.Groups.ListGroups(opts) if searchErr != nil { return fmt.Errorf("GitLab search error while locating Group by name: %s, error: %v", gitLabOrgModel.OrganizationName, searchErr) } if resp.StatusCode < 200 || resp.StatusCode > 299 { return fmt.Errorf("unable to locate GitLab group by name: %s, status code: %d", gitLabOrgModel.OrganizationName, resp.StatusCode) } - if len(groups) != 1 { - return fmt.Errorf("expecting 1 result for GitLab group name '%s' search, found: %d", gitLabOrgModel.OrganizationName, len(groups)) + for _, g := range groups { + if g.Name == gitLabOrgModel.OrganizationName { + return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted) + } } - return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, groups[0].ID, authInfoEncrypted) + return fmt.Errorf("unable to locate GitLab group name '%s' using search, found: %d", gitLabOrgModel.OrganizationName, len(groups)) } // UpdateGitlabOrganization updates the GitLab organization From 4425616280368e57ab5a8071576f365bd26421b0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Sat, 14 Aug 2021 13:40:27 -0700 Subject: [PATCH 0421/1276] Changed GitLab Repository ID from Int to String (#3156) --- cla-backend-go/repositories/models.go | 3 ++- .../swagger/common/gitlab-add-repository.yaml | 10 ++++------ cla-backend-go/swagger/common/gitlab-repository.yaml | 9 ++++----- cla-backend-go/v2/gitlab_organizations/handlers.go | 9 +++++---- cla-backend-go/v2/repositories/gitlab_services.go | 10 +++++----- cla-backend-go/v2/repositories/handlers.go | 4 ++-- cla-backend-go/v2/repositories/repository.go | 5 ++--- 7 files changed, 24 insertions(+), 26 deletions(-) diff --git a/cla-backend-go/repositories/models.go b/cla-backend-go/repositories/models.go index ddde2b9e7..16befd8a9 100644 --- a/cla-backend-go/repositories/models.go +++ b/cla-backend-go/repositories/models.go @@ -14,7 +14,8 @@ import ( type RepositoryDBModel struct { DateCreated string `dynamodbav:"date_created" json:"date_created,omitempty"` DateModified string `dynamodbav:"date_modified" json:"date_modified,omitempty"` - RepositoryExternalID string `dynamodbav:"repository_external_id" json:"repository_external_id,omitempty"` + RepositoryExternalID string `dynamodbav:"repository_external_id" json:"repository_external_id,omitempty"` // Integer value from GitHub + RepositoryGitLabExternalID string `dynamodbav:"repository_gitlab_external_id" json:"repository_gitlab_external_id,omitempty"` // String value from GitLab RepositoryID string `dynamodbav:"repository_id" json:"repository_id,omitempty"` RepositoryName string `dynamodbav:"repository_name" json:"repository_name,omitempty"` RepositoryOrganizationName string `dynamodbav:"repository_organization_name" json:"repository_organization_name,omitempty"` diff --git a/cla-backend-go/swagger/common/gitlab-add-repository.yaml b/cla-backend-go/swagger/common/gitlab-add-repository.yaml index 7e908ead9..56fade13b 100644 --- a/cla-backend-go/swagger/common/gitlab-add-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-add-repository.yaml @@ -3,18 +3,16 @@ type: object required: - - repository_external_id + - repository_gitlab_external_id - repository_project_sfid - repository_cla_group_id - repository_name - repository_organization_name - repository_url properties: - repository_external_id: - type: integer - description: The repository ID from the external service, such as GitHub or GitLab - minimum: 1 - example: 7 + repository_gitlab_external_id: + type: string + description: The GitLab repository ID from the external service repository_project_sfid: description: Project SFID $ref: './common/properties/external-id.yaml' diff --git a/cla-backend-go/swagger/common/gitlab-repository.yaml b/cla-backend-go/swagger/common/gitlab-repository.yaml index 6af477008..6ca0410df 100644 --- a/cla-backend-go/swagger/common/gitlab-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-repository.yaml @@ -6,11 +6,10 @@ properties: repositoryID: description: The internal repository ID $ref: './common/properties/internal-id.yaml' - repository_external_id: - type: integer - description: The repository ID from the external service, such as GitHub or GitLab - minimum: 1 - example: 7 + repository_gitlab_external_id: + type: string + description: The GitLab repository ID from the external service + exampe: '0ab0e8f6cc4f8fff75cc1586f98a7d2362709ee0' repository_cla_group_id: type: string description: The CLA Group ID associated with this repository diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index bf17c2847..e46897e1b 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -253,10 +253,12 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Servic }) api.GitlabActivityGitlabOauthCallbackHandler = gitlab_activity.GitlabOauthCallbackHandlerFunc(func(params gitlab_activity.GitlabOauthCallbackParams) middleware.Responder { + ctx := utils.NewContext() f := logrus.Fields{ - "functionName": "gitlab_organization.handlers.GitlabActivityGitlabOauthCallbackHandler", - "code": params.Code, - "state": params.State, + "functionName": "gitlab_organization.handlers.GitlabActivityGitlabOauthCallbackHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "code": params.Code, + "state": params.State, } requestID, _ := uuid.NewV4() @@ -283,7 +285,6 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Servic gitlabOrganizationID := codeParts[0] stateVar := codeParts[1] - ctx := context.Background() gitLabOrg, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) if err != nil { msg := fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err) diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index c077eec40..04da44340 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -67,15 +67,15 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel // Add repos to table for _, tr := range tree { log.WithFields(f).Debugf("Repository: %s, path: %s, id: %s", tr.Name, tr.Path, tr.ID) - externalID, convertErr := strconv.ParseInt(tr.ID, 10, 64) - if convertErr != nil { - return nil, convertErr - } + //externalID, convertErr := strconv.ParseInt(tr.ID, 10, 64) + //if convertErr != nil { + // return nil, convertErr + //} _, addRepoErr := s.GitLabAddRepository(ctx, gitLabOrgModel.ProjectSFID, &v2Models.GitlabAddRepository{ Enabled: true, Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), - RepositoryExternalID: utils.Int64(externalID), + RepositoryGitlabExternalID: utils.StringRef(tr.ID), // utils.Int64(externalID), RepositoryName: utils.StringRef(tr.Name), RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 4364ec18c..5d443ae2d 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -418,7 +418,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "authUser": authUser.UserName, "authEmail": authUser.Email, "projectSFID": params.ProjectSFID, - "repositoryGitLabID": utils.Int64Value(params.GitlabAddRepository.RepositoryExternalID), + "repositoryGitLabID": utils.StringValue(params.GitlabAddRepository.RepositoryGitlabExternalID), "repositoryName": utils.StringValue(params.GitlabAddRepository.RepositoryName), "repositoryURL": utils.StringValue(params.GitlabAddRepository.RepositoryURL), "repositoryOrganizationName": utils.StringValue(params.GitlabAddRepository.RepositoryOrganizationName), @@ -436,7 +436,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic // If no repository GitLab ID values provided... // RepositoryGitlabID - provided by the older retool UI which provides only one value // RepositoryGitlabIds - provided by new PCC which passes multiple values - if params.GitlabAddRepository.RepositoryExternalID == nil { + if params.GitlabAddRepository.RepositoryGitlabExternalID == nil { msg := "missing repository GitLab ID value" return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index df24ca1b6..7c79f37e0 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -6,7 +6,6 @@ package repositories import ( "context" "fmt" - "strconv" "strings" "github.com/aws/aws-sdk-go/aws" @@ -208,7 +207,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string "functionName": "v2.repositories.repositories.GitHubAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "repositoryExternalID": utils.Int64Value(input.RepositoryExternalID), + "repositoryGitLabExternalID": utils.StringValue(input.RepositoryGitlabExternalID), "repositoryURL": utils.StringValue(input.RepositoryURL), "repositoryName": utils.StringValue(input.RepositoryName), "repositoryType": utils.GitLabLower, @@ -244,7 +243,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string ProjectSFID: projectSFID, DateCreated: currentTime, DateModified: currentTime, - RepositoryExternalID: strconv.FormatInt(utils.Int64Value(input.RepositoryExternalID), 10), + RepositoryExternalID: utils.StringValue(input.RepositoryGitlabExternalID), RepositoryName: utils.StringValue(input.RepositoryName), RepositoryURL: utils.StringValue(input.RepositoryURL), RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), // gitlab group/organization From 4c16663b55b42a05d29940bc4b4544636ab2eb2a Mon Sep 17 00:00:00 2001 From: David Deal Date: Sat, 14 Aug 2021 15:32:37 -0700 Subject: [PATCH 0422/1276] Added Missing CLA Group to GitLab Repo Insert (#3157) * Added Missing CLA Group to GitLab Repo Insert Signed-off-by: David Deal * Updated CI/CD circleci/python docker image Signed-off-by: David Deal --- .circleci/config.yml | 2 +- cla-backend-go/v2/repositories/gitlab_services.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b325f41a9..ad3195388 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -131,7 +131,7 @@ jobs: # Builds buildBackend: &buildBackendAnchor docker: - - image: circleci/python:3.7.9-node + - image: circleci/python:3.7.11-node steps: - checkout - add_ssh_keys: diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 04da44340..52dde1b1b 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -64,6 +64,12 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel return nil, fmt.Errorf("unable to locate GitLab repositories group ID: %d, status code: %d", gitLabOrgModel.ExternalGroupID, resp.StatusCode) } + // lookup CLA Group for this project SFID + projectCLAGroupModel, projectCLAGroupLookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, gitLabOrgModel.ProjectSFID) + if projectCLAGroupLookupErr != nil || projectCLAGroupModel == nil { + return nil, fmt.Errorf("unable to locate Project CLAGroup using projectSFID: %s for GitLab repositories group ID: %d, error: %+v", gitLabOrgModel.ProjectSFID, gitLabOrgModel.ExternalGroupID, listTreeErr) + } + // Add repos to table for _, tr := range tree { log.WithFields(f).Debugf("Repository: %s, path: %s, id: %s", tr.Name, tr.Path, tr.ID) @@ -80,7 +86,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), RepositoryURL: utils.StringRef(tr.Path), - //RepositoryClaGroupID: utils.StringRef(gitLabOrgModel.), + RepositoryClaGroupID: utils.StringRef(projectCLAGroupModel.ClaGroupID), }) if addRepoErr != nil { return nil, addRepoErr From 9d28dcd9c7fa9de12e8c0b0f1e756cc333606885 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 16 Aug 2021 10:09:08 -0700 Subject: [PATCH 0423/1276] Reviewed and Updated GitLab Project Query (#3158) --- cla-backend-go/repositories/models.go | 3 +- .../swagger/common/gitlab-add-repository.yaml | 10 +-- .../swagger/common/gitlab-repository.yaml | 9 +-- cla-backend-go/tests/gitlab_client_test.go | 60 ++++++++++++++++- .../v2/repositories/gitlab_services.go | 66 +++++++++++-------- cla-backend-go/v2/repositories/handlers.go | 4 +- cla-backend-go/v2/repositories/repository.go | 8 ++- 7 files changed, 119 insertions(+), 41 deletions(-) diff --git a/cla-backend-go/repositories/models.go b/cla-backend-go/repositories/models.go index 16befd8a9..2390e7ce4 100644 --- a/cla-backend-go/repositories/models.go +++ b/cla-backend-go/repositories/models.go @@ -14,8 +14,7 @@ import ( type RepositoryDBModel struct { DateCreated string `dynamodbav:"date_created" json:"date_created,omitempty"` DateModified string `dynamodbav:"date_modified" json:"date_modified,omitempty"` - RepositoryExternalID string `dynamodbav:"repository_external_id" json:"repository_external_id,omitempty"` // Integer value from GitHub - RepositoryGitLabExternalID string `dynamodbav:"repository_gitlab_external_id" json:"repository_gitlab_external_id,omitempty"` // String value from GitLab + RepositoryExternalID string `dynamodbav:"repository_external_id" json:"repository_external_id,omitempty"` // Integer value from GitHub RepositoryID string `dynamodbav:"repository_id" json:"repository_id,omitempty"` RepositoryName string `dynamodbav:"repository_name" json:"repository_name,omitempty"` RepositoryOrganizationName string `dynamodbav:"repository_organization_name" json:"repository_organization_name,omitempty"` diff --git a/cla-backend-go/swagger/common/gitlab-add-repository.yaml b/cla-backend-go/swagger/common/gitlab-add-repository.yaml index 56fade13b..7e908ead9 100644 --- a/cla-backend-go/swagger/common/gitlab-add-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-add-repository.yaml @@ -3,16 +3,18 @@ type: object required: - - repository_gitlab_external_id + - repository_external_id - repository_project_sfid - repository_cla_group_id - repository_name - repository_organization_name - repository_url properties: - repository_gitlab_external_id: - type: string - description: The GitLab repository ID from the external service + repository_external_id: + type: integer + description: The repository ID from the external service, such as GitHub or GitLab + minimum: 1 + example: 7 repository_project_sfid: description: Project SFID $ref: './common/properties/external-id.yaml' diff --git a/cla-backend-go/swagger/common/gitlab-repository.yaml b/cla-backend-go/swagger/common/gitlab-repository.yaml index 6ca0410df..6af477008 100644 --- a/cla-backend-go/swagger/common/gitlab-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-repository.yaml @@ -6,10 +6,11 @@ properties: repositoryID: description: The internal repository ID $ref: './common/properties/internal-id.yaml' - repository_gitlab_external_id: - type: string - description: The GitLab repository ID from the external service - exampe: '0ab0e8f6cc4f8fff75cc1586f98a7d2362709ee0' + repository_external_id: + type: integer + description: The repository ID from the external service, such as GitHub or GitLab + minimum: 1 + example: 7 repository_cla_group_id: type: string description: The CLA Group ID associated with this repository diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index e07ae9fd8..f15d3e59b 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -7,19 +7,21 @@ import ( "fmt" "testing" + "github.com/communitybridge/easycla/cla-backend-go/utils" + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" "github.com/stretchr/testify/assert" "github.com/xanzy/go-gitlab" ) const enabled = false // nolint +const group = "The Linux Foundation/product/EasyCLA" func TestGitLabSearchGroup(t *testing.T) { // no lint if enabled { // nolint // Get the client accessToken := "" //update to run this test - group := "The Linux Foundation/product/EasyCLA" gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) assert.Nil(t, err, "GitLab OAuth Client") @@ -41,3 +43,59 @@ func TestGitLabSearchGroup(t *testing.T) { // no lint } } } + +func TestGitLabListProjects(t *testing.T) { // no lint + + if enabled { // nolint + // Get the client + accessToken := "" //update to run this test + gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) + assert.Nil(t, err, "GitLab OAuth Client") + + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + listProjectsOpts := &gitlab.ListProjectsOptions{ + ListOptions: gitlab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, + }, + Archived: nil, + Visibility: nil, + OrderBy: nil, + Sort: nil, + Search: utils.StringRef("linuxfoundation"), + SearchNamespaces: utils.Bool(true), + Simple: nil, + Owned: nil, + Membership: utils.Bool(true), + Starred: nil, + Statistics: nil, + Topic: nil, + WithCustomAttributes: nil, + WithIssuesEnabled: nil, + WithMergeRequestsEnabled: nil, + WithProgrammingLanguage: nil, + WikiChecksumFailed: nil, + RepositoryChecksumFailed: nil, + MinAccessLevel: gitlab.AccessLevel(gitlab.MaintainerPermissions), + IDAfter: nil, + IDBefore: nil, + LastActivityAfter: nil, + LastActivityBefore: nil, + } + + // Need to use this func to get the list of projects the user has access to, see: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63811 + projects, resp, listProjectsErr := gitLabClient.Projects.ListProjects(listProjectsOpts) + assert.Nil(t, listProjectsErr, "GitLab OAuth Client") + if resp.StatusCode < 200 || resp.StatusCode > 299 { + assert.Fail(t, "unable to locate GitLab group by name: %s, status code: %d", group, resp.StatusCode) + } + + t.Logf("Recevied %d projects", len(projects)) + for _, p := range projects { + t.Logf("project name: %s, ID: %d, path: %s", p.Name, p.ID, p.PathWithNamespace) + } + if len(projects) > 1 { + assert.Fail(t, fmt.Sprintf("expecting > 1 result for GitLab list projects, found: %d - %+v", len(projects), projects)) + } + } +} diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 52dde1b1b..c8b0cc748 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -45,47 +45,61 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel return nil, fmt.Errorf("initializing gitlab client : %v", err) } - // Query GitLab for repos - fetch the list of repositories available to the GitLab App - opt := &gitlab.ListTreeOptions{ - ListOptions: gitlab.ListOptions{ - Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination - PerPage: 100, - }, - //Path *string `url:"path,omitempty" json:"path,omitempty"` - //Ref *string `url:"ref,omitempty" json:"ref,omitempty"` - Recursive: utils.Bool(true), + // lookup CLA Group for this project SFID + projectCLAGroupModel, projectCLAGroupLookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, gitLabOrgModel.ProjectSFID) + if projectCLAGroupLookupErr != nil || projectCLAGroupModel == nil { + return nil, fmt.Errorf("unable to locate Project CLAGroup using projectSFID: %s for GitLab repositories group ID: %d, error: %+v", gitLabOrgModel.ProjectSFID, gitLabOrgModel.ExternalGroupID, projectCLAGroupLookupErr) } - tree, resp, listTreeErr := gitlabClient.Repositories.ListTree(gitLabOrgModel.ExternalGroupID, opt) - if listTreeErr != nil { - return nil, fmt.Errorf("unable to locate GitLab repositories group ID: %d, error: %+v", gitLabOrgModel.ExternalGroupID, listTreeErr) + user, resp, userErr := gitlabClient.Users.CurrentUser() + if userErr != nil { + return nil, fmt.Errorf("unable to locate current user, error: %+v", userErr) } if resp.StatusCode < 200 || resp.StatusCode > 299 { - return nil, fmt.Errorf("unable to locate GitLab repositories group ID: %d, status code: %d", gitLabOrgModel.ExternalGroupID, resp.StatusCode) + return nil, fmt.Errorf("unable to locate current user, status code: %d", resp.StatusCode) } - // lookup CLA Group for this project SFID - projectCLAGroupModel, projectCLAGroupLookupErr := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, gitLabOrgModel.ProjectSFID) - if projectCLAGroupLookupErr != nil || projectCLAGroupModel == nil { - return nil, fmt.Errorf("unable to locate Project CLAGroup using projectSFID: %s for GitLab repositories group ID: %d, error: %+v", gitLabOrgModel.ProjectSFID, gitLabOrgModel.ExternalGroupID, listTreeErr) + log.WithFields(f).Debugf("fetched current username: %s with name: %s with email: %s", user.Username, user.Name, user.PublicEmail) + + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + listProjectsOpts := &gitlab.ListProjectsOptions{ + ListOptions: gitlab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, // max is 100 + }, + Search: utils.StringRef(gitLabOrgModel.OrganizationName), // filter by our organization + SearchNamespaces: utils.Bool(true), + Simple: nil, + Owned: nil, + Membership: utils.Bool(true), + MinAccessLevel: gitlab.AccessLevel(gitlab.MaintainerPermissions), + } + + // TODO - DAD - loop until no more repos, could be more than 100 + log.WithFields(f).Debugf("searching for GitLab projects based on the search critera: %s", gitLabOrgModel.OrganizationName) + // Need to use this func to get the list of projects the user has access to, see: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63811 + projects, resp, listProjectsErr := gitlabClient.Projects.ListProjects(listProjectsOpts) + //projects, resp, listProjectsErr := gitlabClient.Projects.ListUserProjects(user.ID, listProjectsOpts) + if listProjectsErr != nil { + return nil, fmt.Errorf("unable to list projects for current user, error: %+v", listProjectsErr) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf("unable to list projects for current user, status code: %d", resp.StatusCode) } // Add repos to table - for _, tr := range tree { - log.WithFields(f).Debugf("Repository: %s, path: %s, id: %s", tr.Name, tr.Path, tr.ID) - //externalID, convertErr := strconv.ParseInt(tr.ID, 10, 64) - //if convertErr != nil { - // return nil, convertErr - //} + for _, proj := range projects { + log.WithFields(f).Debugf("Repository: %s, path: %s, id: %d", proj.Name, proj.Path, proj.ID) + // TODO - make sure we don't have duplicates? _, addRepoErr := s.GitLabAddRepository(ctx, gitLabOrgModel.ProjectSFID, &v2Models.GitlabAddRepository{ Enabled: true, Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), - RepositoryGitlabExternalID: utils.StringRef(tr.ID), // utils.Int64(externalID), - RepositoryName: utils.StringRef(tr.Name), + RepositoryExternalID: utils.Int64(int64(proj.ID)), + RepositoryName: utils.StringRef(proj.Name), RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), - RepositoryURL: utils.StringRef(tr.Path), + RepositoryURL: utils.StringRef(proj.Path), RepositoryClaGroupID: utils.StringRef(projectCLAGroupModel.ClaGroupID), }) if addRepoErr != nil { diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 5d443ae2d..fc1824aae 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -418,7 +418,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "authUser": authUser.UserName, "authEmail": authUser.Email, "projectSFID": params.ProjectSFID, - "repositoryGitLabID": utils.StringValue(params.GitlabAddRepository.RepositoryGitlabExternalID), + "repositoryExternalID": utils.Int64Value(params.GitlabAddRepository.RepositoryExternalID), "repositoryName": utils.StringValue(params.GitlabAddRepository.RepositoryName), "repositoryURL": utils.StringValue(params.GitlabAddRepository.RepositoryURL), "repositoryOrganizationName": utils.StringValue(params.GitlabAddRepository.RepositoryOrganizationName), @@ -436,7 +436,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic // If no repository GitLab ID values provided... // RepositoryGitlabID - provided by the older retool UI which provides only one value // RepositoryGitlabIds - provided by new PCC which passes multiple values - if params.GitlabAddRepository.RepositoryGitlabExternalID == nil { + if params.GitlabAddRepository.RepositoryExternalID == nil { msg := "missing repository GitLab ID value" return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index 7c79f37e0..707eec4a7 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -6,6 +6,7 @@ package repositories import ( "context" "fmt" + "strconv" "strings" "github.com/aws/aws-sdk-go/aws" @@ -207,7 +208,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string "functionName": "v2.repositories.repositories.GitHubAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "repositoryGitLabExternalID": utils.StringValue(input.RepositoryGitlabExternalID), + "repositoryExternalID": utils.Int64Value(input.RepositoryExternalID), "repositoryURL": utils.StringValue(input.RepositoryURL), "repositoryName": utils.StringValue(input.RepositoryName), "repositoryType": utils.GitLabLower, @@ -237,13 +238,16 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string return nil, err } + // Convert int64* to string + repositoryExternalIDString := strconv.FormatInt(utils.Int64Value(input.RepositoryExternalID), 10) + repository := &repoModels.RepositoryDBModel{ RepositoryID: repoID.String(), // internal ID that we assign RepositorySfdcID: projectSFID, ProjectSFID: projectSFID, DateCreated: currentTime, DateModified: currentTime, - RepositoryExternalID: utils.StringValue(input.RepositoryGitlabExternalID), + RepositoryExternalID: repositoryExternalIDString, RepositoryName: utils.StringValue(input.RepositoryName), RepositoryURL: utils.StringValue(input.RepositoryURL), RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), // gitlab group/organization From 52755894b84fdb2729ce7ca6c8efeafb70baafe4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 16 Aug 2021 11:29:53 -0700 Subject: [PATCH 0424/1276] Updated GitLab WebURL and Path (#3159) --- cla-backend-go/tests/gitlab_client_test.go | 14 ++++++++++++-- cla-backend-go/v2/repositories/gitlab_services.go | 10 ++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index f15d3e59b..f313e52fc 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -4,6 +4,7 @@ package tests import ( + "encoding/json" "fmt" "testing" @@ -16,12 +17,12 @@ import ( const enabled = false // nolint const group = "The Linux Foundation/product/EasyCLA" +const accessToken = "" func TestGitLabSearchGroup(t *testing.T) { // no lint if enabled { // nolint // Get the client - accessToken := "" //update to run this test gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) assert.Nil(t, err, "GitLab OAuth Client") @@ -48,7 +49,6 @@ func TestGitLabListProjects(t *testing.T) { // no lint if enabled { // nolint // Get the client - accessToken := "" //update to run this test gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) assert.Nil(t, err, "GitLab OAuth Client") @@ -90,10 +90,20 @@ func TestGitLabListProjects(t *testing.T) { // no lint assert.Fail(t, "unable to locate GitLab group by name: %s, status code: %d", group, resp.StatusCode) } + // DEBUG t.Logf("Recevied %d projects", len(projects)) for _, p := range projects { t.Logf("project name: %s, ID: %d, path: %s", p.Name, p.ID, p.PathWithNamespace) } + + // DEBUG + t.Log("projects:") + for _, p := range projects { + byteArr, err := json.Marshal(p) + assert.Nil(t, err) + t.Logf("project: %s", byteArr) + } + if len(projects) > 1 { assert.Fail(t, fmt.Sprintf("expecting > 1 result for GitLab list projects, found: %d - %+v", len(projects), projects)) } diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index c8b0cc748..731fef78e 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -92,14 +92,20 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel log.WithFields(f).Debugf("Repository: %s, path: %s, id: %d", proj.Name, proj.Path, proj.ID) // TODO - make sure we don't have duplicates? + /* + Name: "easycla-test-repo-demo-1", + NameWithNamespace: "The Linux Foundation / product / EasyCLA / Demo / easycla-test-repo-demo-1", + Path: "easycla-test-repo-demo-1", + PathWithNamespace: "linuxfoundation/product/easycla/demo/easycla-test-repo-demo-1", + */ _, addRepoErr := s.GitLabAddRepository(ctx, gitLabOrgModel.ProjectSFID, &v2Models.GitlabAddRepository{ Enabled: true, Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), RepositoryExternalID: utils.Int64(int64(proj.ID)), - RepositoryName: utils.StringRef(proj.Name), + RepositoryName: utils.StringRef(proj.PathWithNamespace), RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), - RepositoryURL: utils.StringRef(proj.Path), + RepositoryURL: utils.StringRef(proj.WebURL), RepositoryClaGroupID: utils.StringRef(projectCLAGroupModel.ClaGroupID), }) if addRepoErr != nil { From 1e682039eaf9335f24f480f4d66ee36ed5c01d26 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 16 Aug 2021 14:48:27 -0700 Subject: [PATCH 0425/1276] Added GitLab Repositories to Response (#3160) --- cla-backend-go/cmd/server.go | 2 +- .../github_organizations/service.go | 6 +- cla-backend-go/repositories/constants.go | 3 + .../common/gitlab-project-repository.yaml | 4 + cla-backend-go/utils/errors.go | 14 +- .../v2/gitlab_organizations/service.go | 144 ++++++++++++++++-- .../v2/repositories/gitlab_services.go | 21 ++- cla-backend-go/v2/repositories/repository.go | 24 ++- cla-backend-go/v2/repositories/service.go | 1 + 9 files changed, 197 insertions(+), 22 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index a3a34bef8..47a3fb09c 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -304,7 +304,7 @@ func server(localMode bool) http.Handler { authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo) - gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v1ProjectClaGroupRepo) + gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo) gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, gitV1Repository, gitV2Repository, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 630e5751a..4e0f50eea 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -73,7 +73,7 @@ func (s Service) AddGitHubOrganization(ctx context.Context, projectSFID string, return s.repo.AddGitHubOrganization(ctx, parentProjectSFID, projectSFID, input) } -// GetGitHubOrganizations returns the github organization for the specified project +// GetGitHubOrganizations returns the GitHub organization for the specified project func (s Service) GetGitHubOrganizations(ctx context.Context, projectSFID string) (*models.GithubOrganizations, error) { f := logrus.Fields{ "functionName": "GetGitHubOrganizations", @@ -133,12 +133,12 @@ func (s Service) GetGitHubOrganizations(ctx context.Context, projectSFID string) return &gitHubOrgModels, err } -// GetGitHubOrganizationsByParent returns the github organizations for the specified parent project SFID +// GetGitHubOrganizationsByParent returns the GitHub organizations for the specified parent project SFID func (s Service) GetGitHubOrganizationsByParent(ctx context.Context, parentProjectSFID string) (*models.GithubOrganizations, error) { return s.repo.GetGitHubOrganizationsByParent(ctx, parentProjectSFID) } -// GetGitHubOrganizationByName returns the github organizations for the specified github organization name +// GetGitHubOrganizationByName returns the GitHub organizations for the specified GitHub organization name func (s Service) GetGitHubOrganizationByName(ctx context.Context, githubOrgName string) (*models.GithubOrganization, error) { f := logrus.Fields{ "functionName": "GetGitHubOrganizationByName", diff --git a/cla-backend-go/repositories/constants.go b/cla-backend-go/repositories/constants.go index a525fd0ad..40ab63317 100644 --- a/cla-backend-go/repositories/constants.go +++ b/cla-backend-go/repositories/constants.go @@ -15,6 +15,9 @@ const RepositoryProjectIDColumn = "project_sfid" // RepositoryCLAGroupIDColumn constant const RepositoryCLAGroupIDColumn = "repository_project_id" +// RepositoryOrganizationNameColumn constant +const RepositoryOrganizationNameColumn = "repository_organization_name" + // RepositoryEnabledColumn constant const RepositoryEnabledColumn = "enabled" diff --git a/cla-backend-go/swagger/common/gitlab-project-repository.yaml b/cla-backend-go/swagger/common/gitlab-project-repository.yaml index 7441f2eb2..ac3ebdee1 100644 --- a/cla-backend-go/swagger/common/gitlab-project-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-project-repository.yaml @@ -17,6 +17,10 @@ properties: type: string description: 'GitLab Repository/Project name' x-omitempty: false + repository_url: + type: string + description: 'GitLab Repository/Project URL' + x-omitempty: false cla_group_id: description: CLA Group ID $ref: './common/properties/internal-id.yaml' diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index ea5f35a30..3c80e7418 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -354,11 +354,12 @@ func (e *GitHubRepositoryExists) Unwrap() error { // GitLabRepositoryNotFound is an error model for a GitLab repository not found type GitLabRepositoryNotFound struct { - Message string - RepositoryName string - ProjectSFID string - CLAGroupID string - Err error + Message string + OrganizationName string + RepositoryName string + ProjectSFID string + CLAGroupID string + Err error } // Error is an error string function for the GitHubRepositoryNotFound model @@ -367,6 +368,9 @@ func (e *GitLabRepositoryNotFound) Error() string { if e.Message != "" { msg = e.Message } + if e.OrganizationName != "" { + msg = fmt.Sprintf("%s - organization: %s ", msg, e.OrganizationName) + } if e.RepositoryName != "" { msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 29f4e24cb..2a6bad4d5 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -5,17 +5,23 @@ package gitlab_organizations import ( "context" + "errors" "fmt" + "io" "net/url" "sort" "strings" + "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" "github.com/communitybridge/easycla/cla-backend-go/config" gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" "github.com/go-openapi/strfmt" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" "github.com/communitybridge/easycla/cla-backend-go/gitlab" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -41,14 +47,16 @@ type ServiceInterface interface { // Service data model type Service struct { repo RepositoryInterface + v2GitRepoService repositories.ServiceInterface claGroupRepository projects_cla_groups.Repository gitLabApp *gitlab.App } // NewService creates a new gitlab organization service -func NewService(repo RepositoryInterface, claGroupRepository projects_cla_groups.Repository) *Service { +func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceInterface, claGroupRepository projects_cla_groups.Repository) *Service { return &Service{ repo: repo, + v2GitRepoService: v2GitRepoService, claGroupRepository: claGroupRepository, gitLabApp: gitlab.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } @@ -184,26 +192,33 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string } } - orgDetailed, err := s.repo.GetGitlabOrganization(ctx, org.OrganizationID) - if err != nil { - log.WithFields(f).Errorf("fetching gitlab org failed : %s : %v", org.OrganizationID, err) + orgDetailed, orgErr := s.repo.GetGitlabOrganization(ctx, org.OrganizationID) + if orgErr != nil { + log.WithFields(f).Errorf("fetching gitlab org failed : %s : %v", org.OrganizationID, orgErr) + continue + } + + reposFromOrg, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByOrganizationName(ctx, org.OrganizationName) + if repoErr != nil { + log.WithFields(f).Errorf("fetching gitlab org repositories for GitLab Org: %s : %v", org.OrganizationName, repoErr) continue } - installationURL := buildInstallationURL(org.OrganizationID, orgDetailed.AuthState) + glClient, err := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) + rorg := &models.GitlabProjectOrganization{ AutoEnabled: org.AutoEnabled, AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, AutoEnabledCLAGroupName: autoEnabledCLAGroupName, GitlabOrganizationName: org.OrganizationName, - Repositories: make([]*models.GitlabProjectRepository, 0), - InstallationURL: installationURL, + Repositories: s.updateRepositoryStatus(glClient, toGitLabProjectResponse(reposFromOrg)), + InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), + ConnectionStatus: "", // updated below } if orgDetailed.AuthInfo == "" { rorg.ConnectionStatus = utils.NoConnection } else { - glClient, err := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) if err != nil { log.WithFields(f).Errorf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) rorg.ConnectionStatus = utils.ConnectionFailure @@ -323,7 +338,7 @@ func (s *Service) UpdateGitlabOrganization(ctx context.Context, projectSFID stri // DeleteGitlabOrganization deletes the specified GitLab organization func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error { f := logrus.Fields{ - "functionName": "DeleteGitlabOrganization", + "functionName": "v2.gitlab_organizations.service.DeleteGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "gitlabOrgName": gitlabOrgName, @@ -364,3 +379,114 @@ func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI installationURL := strfmt.URI(base + "?" + params.Encode()) return &installationURL } + +func toGitLabProjectResponse(gitLabListRepos *models.GitlabListRepositories) []*models.GitlabProjectRepository { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.toGitLabProjectResponse", + } + + if gitLabListRepos == nil { + return []*models.GitlabProjectRepository{} + } + + var repoList []*models.GitlabProjectRepository + for _, repo := range gitLabListRepos.List { + parentProjectSFID, err := project_service.GetClient().GetParentProject(repo.RepositoryProjectSfid) + if err != nil { + log.WithFields(f).Warnf("unable to lookup project parent SFID using SFID: %s", repo.RepositoryProjectSfid) + } + + repoList = append(repoList, &models.GitlabProjectRepository{ + ClaGroupID: repo.RepositoryClaGroupID, + //ConnectionStatus: "", // set via another function + Enabled: repo.Enabled, + ParentProjectID: parentProjectSFID, + ProjectID: repo.RepositoryProjectSfid, + RepositoryGitlabID: repo.RepositoryExternalID, + RepositoryID: repo.RepositoryID, + RepositoryName: repo.RepositoryName, + RepositoryURL: repo.RepositoryURL, + }) + } + + return repoList +} + +// updateRepositoryStatus is a helper function to set/add the repository connection status +func (s *Service) updateRepositoryStatus(glClient *goGitLab.Client, gitLabProjectRepos []*models.GitlabProjectRepository) []*models.GitlabProjectRepository { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.updateRepositoryStatus", + } + + if gitLabProjectRepos == nil { + return []*models.GitlabProjectRepository{} + } + + type responseChannelModel struct { + RepositoryExternalID int64 + ConnectionStatus string + Error error + } + // A channel for the responses from the go routines + responseChannel := make(chan responseChannelModel) + + opts := &goGitLab.GetProjectOptions{} + for _, repo := range gitLabProjectRepos { + // Create a go routine to this concurrently + go func(glClient *goGitLab.Client, repo *models.GitlabProjectRepository) { + projectModel, resp, lookupErr := glClient.Projects.GetProject(int(repo.RepositoryGitlabID), opts) // OK to convert int64 to int as it is the ID and probably should be typed as a int anyway + if lookupErr != nil { + log.WithFields(f).WithError(lookupErr).Warnf("problem loading GitLab project by external ID: %d, error: %v", repo.RepositoryGitlabID, lookupErr) + repo.ConnectionStatus = utils.ConnectionFailure + responseChannel <- responseChannelModel{ + RepositoryExternalID: repo.RepositoryGitlabID, + ConnectionStatus: utils.ConnectionFailure, + Error: lookupErr, + } + return + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + responseBody, readErr := io.ReadAll(resp.Body) + if readErr != nil { + log.WithFields(f).WithError(lookupErr).Warnf("problem loading GitLab project by external ID: %d, error: %v", repo.RepositoryGitlabID, readErr) + responseChannel <- responseChannelModel{ + RepositoryExternalID: repo.RepositoryGitlabID, + ConnectionStatus: utils.ConnectionFailure, + Error: readErr, + } + return + } + msg := fmt.Sprintf("problem loading GitLab project by external ID: %d, response code: %d, body: %s", repo.RepositoryGitlabID, resp.StatusCode, responseBody) + log.WithFields(f).Warnf(msg) + responseChannel <- responseChannelModel{ + RepositoryExternalID: repo.RepositoryGitlabID, + ConnectionStatus: utils.ConnectionFailure, + Error: errors.New(msg), + } + return + } + + log.WithFields(f).Debugf("connected to project/repo: %s", projectModel.PathWithNamespace) + responseChannel <- responseChannelModel{ + RepositoryExternalID: repo.RepositoryGitlabID, + ConnectionStatus: utils.Connected, + Error: nil, + } + }(glClient, repo) + } + + for i := 0; i < len(gitLabProjectRepos); i++ { + resp := <-responseChannel + // Find the matching repo for this response + for _, repo := range gitLabProjectRepos { + if repo.RepositoryGitlabID == resp.RepositoryExternalID { + repo.ConnectionStatus = resp.ConnectionStatus + } + } + if resp.Error != nil { + log.WithFields(f).Warnf("problem connecting to GitLab repoistory, error: %+v", resp.Error) + } + } + + return gitLabProjectRepos +} diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 731fef78e..ed6d4d5b5 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -69,8 +69,6 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel }, Search: utils.StringRef(gitLabOrgModel.OrganizationName), // filter by our organization SearchNamespaces: utils.Bool(true), - Simple: nil, - Owned: nil, Membership: utils.Bool(true), MinAccessLevel: gitlab.AccessLevel(gitlab.MaintainerPermissions), } @@ -99,7 +97,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel PathWithNamespace: "linuxfoundation/product/easycla/demo/easycla-test-repo-demo-1", */ _, addRepoErr := s.GitLabAddRepository(ctx, gitLabOrgModel.ProjectSFID, &v2Models.GitlabAddRepository{ - Enabled: true, + Enabled: false, // default is false Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), RepositoryExternalID: utils.Int64(int64(proj.ID)), RepositoryName: utils.StringRef(proj.PathWithNamespace), @@ -181,6 +179,23 @@ func (s *Service) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupI }, nil } +// GitLabGetRepositoriesByOrganizationName returns the list of repositories associated with the Organization/Group name +func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabListRepositories, error) { + dbModels, err := s.gitV2Repository.GitHubGetRepositoriesByOrganizationName(ctx, orgName) + if err != nil { + return nil, err + } + + responses, err := dbModelsToGitLabRepositories(dbModels) + if err != nil { + return nil, err + } + + return &v2Models.GitlabListRepositories{ + List: responses, + }, nil +} + // GitLabEnableRepository service function func (s *Service) GitLabEnableRepository(ctx context.Context, repositoryID string) error { return s.gitV2Repository.GitLabEnableRepositoryByID(ctx, repositoryID) diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index 707eec4a7..c6cc6e164 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -30,6 +30,7 @@ type RepositoryInterface interface { GitHubGetRepositoriesByCLAGroupEnabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByCLAGroupDisabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) ([]*repoModels.RepositoryDBModel, error) + GitHubGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*repoModels.RepositoryDBModel, error) GitLabEnableRepositoryByID(ctx context.Context, repositoryID string) error GitLabDisableRepositoryByID(ctx context.Context, repositoryID string) error @@ -202,6 +203,27 @@ func (r *Repository) GitHubGetRepositoriesByProjectSFID(ctx context.Context, pro return records, nil } +// GitHubGetRepositoriesByOrganizationName returns a list of repositories associated with the specified organization name +func (r *Repository) GitHubGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryOrganizationNameColumn).Equal(expression.Value(orgName)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) + + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryOrganizationNameIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + OrganizationName: orgName, + } + } + + // Some other error + return nil, err + } + + return records, nil +} + // GitLabAddRepository creates a new entry in the repositories table using the specified input parameters func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*repoModels.RepositoryDBModel, error) { f := logrus.Fields{ @@ -253,7 +275,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), // gitlab group/organization RepositoryCLAGroupID: utils.StringValue(input.RepositoryClaGroupID), RepositoryType: utils.GitLabLower, // should always be gitlab - Enabled: true, // default is enabled + Enabled: input.Enabled, // default is enabled Note: fmt.Sprintf("created on %s", currentTime), Version: "v1", } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index b59c5bab4..a4b8bda69 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -54,6 +54,7 @@ type ServiceInterface interface { GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*v2Models.GitlabRepository, error) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabListRepositories, error) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) + GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabListRepositories, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitlabOrganization) ([]*v2Models.GitlabRepository, error) GitLabEnableRepository(ctx context.Context, repositoryID string) error From 5f2d0bb171107de6c84931658f9e9d0149c48699 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 16 Aug 2021 16:21:27 -0700 Subject: [PATCH 0426/1276] Resolved Add GitLab Org Issue with No Auth (#3161) --- .../v2/gitlab_organizations/service.go | 40 ++++++++++--------- cla-backend-go/v2/repositories/repository.go | 4 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 2a6bad4d5..838cf3341 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -170,14 +170,6 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string List: make([]*models.GitlabProjectOrganization, 0), } - // Next, we need to load a bunch of additional data for the response including the GitLab status (if it's still connected/live, not renamed/moved), the CLA Group details, etc. - - //// A temp data model for holding the intermediate results - //type gitlabRepoInfo struct { - // orgName string - // repoInfo *v1Models.GitLabRepositoryInfo - //} - orgmap := make(map[string]*models.GitlabProjectOrganization) for _, org := range orgs.List { autoEnabledCLAGroupName := "" @@ -200,20 +192,21 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string reposFromOrg, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByOrganizationName(ctx, org.OrganizationName) if repoErr != nil { - log.WithFields(f).Errorf("fetching gitlab org repositories for GitLab Org: %s : %v", org.OrganizationName, repoErr) - continue + if errors.Is(repoErr, &utils.GitLabRepositoryNotFound{}) { + log.WithFields(f).Debugf("no repositories onboarded for GitLab Org: %s", org.OrganizationName) + } else { + log.WithFields(f).Debugf("unexpected error while fetching gitlab org repositories for GitLab Org: %s : %v", org.OrganizationName, repoErr) + } } - glClient, err := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) - rorg := &models.GitlabProjectOrganization{ AutoEnabled: org.AutoEnabled, AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, - AutoEnabledCLAGroupName: autoEnabledCLAGroupName, + AutoEnabledCLAGroupName: strings.TrimSpace(autoEnabledCLAGroupName), GitlabOrganizationName: org.OrganizationName, - Repositories: s.updateRepositoryStatus(glClient, toGitLabProjectResponse(reposFromOrg)), InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), ConnectionStatus: "", // updated below + Repositories: []*models.GitlabProjectRepository{}, } if orgDetailed.AuthInfo == "" { @@ -223,13 +216,22 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string log.WithFields(f).Errorf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) rorg.ConnectionStatus = utils.ConnectionFailure } else { - user, _, err := glClient.Users.CurrentUser() - if err != nil { - log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) + // We've been authenticated by the user - great, see if we can determine the list of repos... + glClient, clientErr := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) + if clientErr != nil { + log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, clientErr) rorg.ConnectionStatus = utils.ConnectionFailure } else { - log.WithFields(f).Debugf("connected to user : %s for gitlab org : %s", user.Name, org.OrganizationID) - rorg.ConnectionStatus = utils.Connected + rorg.Repositories = s.updateRepositoryStatus(glClient, toGitLabProjectResponse(reposFromOrg)) + + user, _, userErr := glClient.Users.CurrentUser() + if userErr != nil { + log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, userErr) + rorg.ConnectionStatus = utils.ConnectionFailure + } else { + log.WithFields(f).Debugf("connected to user : %s for gitlab org : %s", user.Name, org.OrganizationID) + rorg.ConnectionStatus = utils.Connected + } } } } diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index c6cc6e164..6db94ad38 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -372,7 +372,7 @@ func (r *Repository) getRepositoryWithConditionFilter(ctx context.Context, condi } if len(results.Items) == 0 { - log.WithFields(f).Warnf("no repositories found matching filter critera: %+v", queryInput) + log.WithFields(f).Debugf("no repositories found matching filter critera: %+v", queryInput) // Generic - no details as we don't know what filter content was provided return nil, &utils.GitLabRepositoryNotFound{} } @@ -425,7 +425,7 @@ func (r *Repository) getRepositoriesWithConditionFilter(ctx context.Context, con } if len(results.Items) == 0 { - log.WithFields(f).Warnf("no repositories found matching filter critera: %+v", queryInput) + log.WithFields(f).Debugf("no repositories found matching filter critera: %+v", queryInput) // Generic - no details as we don't know what filter content was provided return nil, &utils.GitLabRepositoryNotFound{} } From fac78d1704a6fc83d8bf68642ac37568c9bf2093 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 17 Aug 2021 18:55:32 +0300 Subject: [PATCH 0427/1276] making gitlab mr comment compatible with python version (#3162) --- cla-backend-go/gitlab/mr.go | 22 +-- cla-backend-go/v2/gitlab-activity/handlers.go | 10 +- cla-backend-go/v2/gitlab-activity/service.go | 140 ++++++++++++++---- .../v2/gitlab-activity/service_test.go | 84 +++++++++++ 4 files changed, 210 insertions(+), 46 deletions(-) diff --git a/cla-backend-go/gitlab/mr.go b/cla-backend-go/gitlab/mr.go index 0811613a1..7a454b893 100644 --- a/cla-backend-go/gitlab/mr.go +++ b/cla-backend-go/gitlab/mr.go @@ -72,15 +72,15 @@ func FetchMrParticipants(client *gitlab.Client, projectID int, mergeID int, uniq } // SetCommitStatus is responsible for setting the MR status for commit sha -func SetCommitStatus(client *gitlab.Client, projectID int, commitSha string, state gitlab.BuildStateValue, message string) error { +func SetCommitStatus(client *gitlab.Client, projectID int, commitSha string, state gitlab.BuildStateValue, message string, targetURL string) error { options := &gitlab.SetCommitStatusOptions{ State: state, - Name: gitlab.String("easyCLA Bot"), + Name: gitlab.String("EasyCLA Bot"), Description: gitlab.String(message), } - if state == gitlab.Failed { - options.TargetURL = gitlab.String("http://localhost:8080/gitlab/sign") + if targetURL != "" { + options.TargetURL = gitlab.String(targetURL) } _, _, err := client.Commits.SetCommitStatus(projectID, commitSha, options) @@ -92,11 +92,11 @@ func SetCommitStatus(client *gitlab.Client, projectID int, commitSha string, sta } // SetMrComment is responsible for setting the comment body for project and merge id -func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitlab.BuildStateValue, message string) error { - covered := ` - covered
    ` - failed := ` -covered
    ` +func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitlab.BuildStateValue, message string, targetURL string) error { + covered := fmt.Sprintf(` + covered
    `, targetURL) + failed := fmt.Sprintf(` +covered
    `, targetURL) var body string if state == gitlab.Failed { @@ -105,6 +105,10 @@ func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitla body = covered } + if message != "" { + body += "

    " + message + } + notes, _, err := client.Notes.ListMergeRequestNotes(projectID, mergeID, &gitlab.ListMergeRequestNotesOptions{}) if err != nil { return fmt.Errorf("fetching comments for project id : %d and merge id : %d : failed %v", projectID, mergeID, err) diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index 6f3723438..d2dd835ed 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -54,8 +54,8 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequest(reqID, msg)) } - if mergeEvent.ObjectAttributes.State != "opened" { - msg := fmt.Sprintf("parsing gitlab merge event failed, only opened accepted") + if mergeEvent.ObjectAttributes.State != "opened" && mergeEvent.ObjectAttributes.State != "update" && mergeEvent.ObjectAttributes.State != "reopen" { + msg := fmt.Sprintf("parsing gitlab merge event : %s failed, only [open, update, reopen] accepted", mergeEvent.ObjectAttributes.State) log.WithFields(f).Errorf(msg) return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( utils.ErrorResponseBadRequest(reqID, msg)) @@ -70,12 +70,6 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. } return gitlab_activity.NewGitlabActivityOK() - //return gitlab_activity.NewGitlabActivityOK().WithPayload(&models.SuccessResponse{ - // Code: "200", - // Message: "oauth credentials stored successfully", - // XRequestID: reqID, - //}) - }) } diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 625b6d82c..d868732df 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -34,6 +34,17 @@ import ( "github.com/xanzy/go-gitlab" ) +var ( + missingID = errors.New("user missing in easyCLA records") + missingCompanyAffiliation = errors.New("must confirm affiliation with their company") + missingCompanyApproval = errors.New("missing in company approval lists") +) + +type gatedGitlabUser struct { + *gitlab.User + err error +} + type Service interface { ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *gitlab.MergeEvent) error } @@ -102,7 +113,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git } // try to find the repository via the external id - gitlabRepo, err := s.getGitlabRepoByExternalID(ctx, gitlabOrg.OrganizationName, strconv.Itoa(projectID)) + gitlabRepo, err := s.getGitlabRepoByName(ctx, repositoryPath) if err != nil { return fmt.Errorf("finding internal repository for gitlab org name failed : %v", err) } @@ -125,43 +136,118 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git log.WithFields(f).Debugf("gitlabOrg : %s is associated with cla group id : %s", gitlabOrg.OrganizationName, claGroupID) log.WithFields(f).Debugf("found following participants for the MR : %d", len(participants)) - missingUsersMsg := "missing users : " - var missingUsers []string + missingCLAMsg := "Missing CLA Authorization" + signedCLAMsg := "EasyCLA check passed. You are authorized to contribute." + + var missingUsers []*gatedGitlabUser + var signedUsers []*gitlab.User for _, gitlabUser := range participants { if ok, err := s.hasUserSigned(ctx, claGroupID, gitlabUser); ok { log.WithFields(f).Infof("gitlabUser : %d:%s has signed", gitlabUser.ID, gitlabUser.Username) + signedUsers = append(signedUsers, gitlabUser) } else { - missingUsers = append(missingUsers, fmt.Sprintf("gitlabUser : %d:%s hasn't signed", gitlabUser.ID, gitlabUser.Username)) + missingUsers = append(missingUsers, &gatedGitlabUser{ + User: gitlabUser, + err: err, + }) log.WithFields(f).Errorf("gitlabUser : %d:%s hasn't signed, err : %v", gitlabUser.ID, gitlabUser.Username, err) } } + signURL := GetFullSignURL(gitlabOrg.OrganizationID, strconv.Itoa(int(gitlabRepo.RepositoryExternalID)), strconv.Itoa(mergeID)) + mrCommentContent := PrepareMrCommentContent(missingUsers, signedUsers, signURL) if len(missingUsers) > 0 { - for _, missing := range missingUsers { - missingUsersMsg += missing - } - log.WithFields(f).Errorf("mr faild with following users : %s", missingUsersMsg) - if err := gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Failed, missingUsersMsg); err != nil { + log.WithFields(f).Errorf("mr faild with following users : %s", mrCommentContent) + if err := gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Failed, missingCLAMsg, signURL); err != nil { return fmt.Errorf("setting commit status failed : %v", err) } - if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Failed, missingUsersMsg); err != nil { + if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Failed, mrCommentContent, signURL); err != nil { return fmt.Errorf("setting comment failed : %v", err) } return nil } - err = gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Success, "all signed passing") + err = gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Success, signedCLAMsg, "") if err != nil { return fmt.Errorf("setting commit status failed : %v", err) } - if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Success, missingUsersMsg); err != nil { + if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Success, mrCommentContent, signURL); err != nil { return fmt.Errorf("setting comment failed : %v", err) } return err } +func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*gitlab.User, signURL string) string { + var result string + failed := ":x:" + success := ":white_check_mark:" + + if len(signedUsers) > 0 { + result = "
      " + for _, signed := range signedUsers { + authorInfo := getAuthorInfo(signed) + result += fmt.Sprintf("
    • %s %s
    • ", success, authorInfo) + } + result += "
    " + } + + gitlabSupportURL := "https://about.gitlab.com/support" + easyCLASupportURL := "https://jira.linuxfoundation.org/servicedesk/customer/portal/4" + if len(missingUsers) > 0 { + result += "
      " + for _, missingUser := range missingUsers { + authorInfo := getAuthorInfo(missingUser.User) + if errors.Is(missingUser.err, missingID) { + msg := fmt.Sprintf(`
    • %s The commit associated with %s is missing the User's ID, preventing the EasyCLA check. + Consult GitLab Help to resolve. + For further assistance with EasyCLA, + please submit a support request ticket. +
    • `, failed, authorInfo, gitlabSupportURL, easyCLASupportURL) + result += msg + } else if errors.Is(missingUser.err, missingCompanyAffiliation) { + msg := fmt.Sprintf(`
    • %s is authorized, but they must confirm their affiliation with their company. + Start the authorization process + by clicking here, click "Corporate", + select the appropriate company from the list, then confirm + your affiliation on the page that appears. + For further assistance with EasyCLA, + please submit a support request ticket. +
    • `, authorInfo, signURL, easyCLASupportURL) + result += msg + + } else { + msg := fmt.Sprintf(`
    • %s - + %s's commit is not authorized under a signed CLA. + Please click here to be authorized. + For further assistance with EasyCLA, + please submit a support request ticket. +
    • `, signURL, failed, authorInfo, signURL, easyCLASupportURL) + result += msg + + } + } + result += "
    " + } + + return result +} + +func GetFullSignURL(gitlabOrganizationID string, gitlabRepositoryID string, mrID string) string { + return fmt.Sprintf("%s/v2/repository-provider/%s/sign/%s/%s/%s/#/", + config.GetConfig().APIGatewayURL, + utils.GitLabLower, + gitlabOrganizationID, + gitlabRepositoryID, + mrID, + ) +} + +func getAuthorInfo(gitlabUser *gitlab.User) string { + return fmt.Sprintf("%d:%s", gitlabUser.ID, gitlabUser.Username) +} + func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*common.GitlabOrganization, error) { repositoryPath := mergeEvent.Project.PathWithNamespace parts := strings.Split(repositoryPath, "/") @@ -184,17 +270,13 @@ func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeE return gitlabOrg, nil } -func (s service) getGitlabRepoByExternalID(ctx context.Context, orgName, gitlabRepoID string) (*models.GithubRepository, error) { - gitlabRepo, err := s.gitV2Repository.GitLabGetRepositoryByName(ctx, orgName) +func (s service) getGitlabRepoByName(ctx context.Context, repoNameWithPath string) (*models.GithubRepository, error) { + gitlabRepo, err := s.gitV2Repository.GitLabGetRepositoryByName(ctx, repoNameWithPath) if err != nil || gitlabRepo == nil { - return nil, fmt.Errorf("unable to locate GitLab repo for external id : %s, orgName : %s, failed : %v", gitlabRepoID, orgName, err) - } - - if gitlabRepo.RepositoryExternalID == gitlabRepoID && gitlabRepo.RepositoryType == "gitlab" { - return gitlabRepo.ToGitHubModel(), nil + return nil, fmt.Errorf("unable to locate GitLab repo for repoNameWithPath : %s, failed : %v", repoNameWithPath, err) } - return nil, fmt.Errorf("no repositories found for orgName : %s and gitlab external id : %s", orgName, gitlabRepoID) + return gitlabRepo.ToGitHubModel(), nil } func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUser *gitlab.User) (bool, error) { @@ -214,7 +296,7 @@ func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUse if userModel == nil { msg := fmt.Sprintf("gitlab user : %d:%s not found in easycla records", gitlabUser.ID, gitlabUser.Username) log.WithFields(f).Error(msg) - return false, fmt.Errorf(msg) + return false, missingID } log.WithFields(f).Debugf("found following easyCLA user for gitlab record, userID: %s, lfusername : %s", userModel.UserID, userModel.LfUsername) @@ -260,6 +342,11 @@ func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUse return false, fmt.Errorf(msg) } + if !IsUserApprovedForSignature(f, corporateSignature, userModel, gitlabUser) { + log.WithFields(f).Debugf("user is not approved in signature : %s", corporateSignature.SignatureID) + return false, missingCompanyApproval + } + employeeSignatures, err := s.signaturesRepository.GetProjectCompanyEmployeeSignatures(ctx, signatures1.GetProjectCompanyEmployeeSignaturesParams{ CompanyID: companyID, ProjectID: claGroupID, @@ -274,16 +361,11 @@ func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUse if len(employeeSignatures.Signatures) == 0 { msg := fmt.Sprintf("no employee signature records found for company : %s user : %s association", companyID, userModel.UserID) log.WithFields(f).Errorf(msg) - return false, fmt.Errorf(msg) - } - - if IsUserApprovedForSignature(f, corporateSignature, userModel, gitlabUser) { - log.WithFields(f).Debugf("user is approved in signature : %s", corporateSignature.SignatureID) - return true, nil + return false, missingCompanyAffiliation } - log.WithFields(f).Warnf("user not in one of the approval lists") - return false, fmt.Errorf("not signed") + log.WithFields(f).Warnf("is in signature approval list : %s and has employee signature", corporateSignature.SignatureID) + return true, nil } func (s service) findUserModelForGitlabUser(f logrus.Fields, gitlabUser *gitlab.User) (*models.User, bool, error) { diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index fd77b482b..7b651d5a1 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -4,6 +4,8 @@ package gitlab_activity import ( + "fmt" + "strings" "testing" "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" @@ -102,3 +104,85 @@ func TestIsUserApprovedForSignature(t *testing.T) { } } + +func TestPrepareMrCommentContent(t *testing.T) { + + signedContains := ":white_check_mark: %s" + missingUserContains := ":x: The commit associated with %s is missing the User's ID" + missingAffiliationContains := "%s is authorized, but they must confirm their affiliation" + missingApprovalContains := "%s's commit is not authorized under a signed CLA" + + testCases := []struct { + name string + signed []*gitlab.User + missing []*gatedGitlabUser + expectedMsgs []string + }{ + { + name: "all signed", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + {ID: 2, Username: "oracle"}, + }, + expectedMsgs: []string{signedContains, signedContains}, + }, + { + name: "missing id", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + }, + missing: []*gatedGitlabUser{ + {err: missingID, User: &gitlab.User{ID: 3, Username: "missing"}}, + }, + expectedMsgs: []string{signedContains, missingUserContains}, + }, + { + name: "missing affiliation", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + }, + missing: []*gatedGitlabUser{ + {err: missingCompanyAffiliation, User: &gitlab.User{ID: 4, Username: "affiliationUser"}}, + }, + expectedMsgs: []string{signedContains, missingAffiliationContains}, + }, + { + name: "missing approval", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + }, + missing: []*gatedGitlabUser{ + {err: missingCompanyApproval, User: &gitlab.User{ID: 5, Username: "approvalUser"}}, + }, + expectedMsgs: []string{signedContains, missingApprovalContains}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + result := PrepareMrCommentContent(tc.missing, tc.signed, "https://sign.com") + tt.Logf("the result is : %s", result) + parts := strings.Split(result, "
  • ") + assert.Len(tt, parts, len(tc.expectedMsgs)+1) + + var allUsers []*gitlab.User + + if len(tc.signed) > 0 { + for _, s := range tc.signed { + allUsers = append(allUsers, s) + } + } + + if len(tc.missing) > 0 { + for _, m := range tc.missing { + allUsers = append(allUsers, m.User) + } + } + + for i, p := range parts[1:] { + expected := fmt.Sprintf(tc.expectedMsgs[i], getAuthorInfo(allUsers[i])) + assert.Contains(tt, p, expected) + } + }) + } +} From c4948979b0e40c1868fcfbef31c398c7393d60dd Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 17 Aug 2021 17:17:56 -0700 Subject: [PATCH 0428/1276] Updated GitLab Org Data Model (#3165) --- cla-backend-go/swagger/cla.v2.yaml | 4 +- .../swagger/common/gitlab-organization.yaml | 33 +++-- .../common/gitlab-project-organization.yaml | 10 +- .../common/gitlab-repository-info.yaml | 17 +++ cla-backend-go/tests/gitlab_client_test.go | 134 +++++++++++++++--- cla-backend-go/v2/common/models.go | 38 ++++- cla-backend-go/v2/gitlab-activity/service.go | 2 +- .../v2/gitlab_organizations/constants.go | 37 +++++ .../v2/gitlab_organizations/repository.go | 78 +++++----- .../v2/gitlab_organizations/service.go | 13 +- .../v2/repositories/gitlab_services.go | 2 +- cla-backend-go/v2/repositories/service.go | 2 +- 12 files changed, 287 insertions(+), 83 deletions(-) create mode 100644 cla-backend-go/swagger/common/gitlab-repository-info.yaml create mode 100644 cla-backend-go/v2/gitlab_organizations/constants.go diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 638828d42..d8f01d922 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4556,8 +4556,8 @@ definitions: github-repository-info: $ref: './common/github-repository-info.yaml' - #company: - # $ref: './common/company.yaml' + gitlab-repository-info: + $ref: './common/gitlab-repository-info.yaml' total-count-metrics: type: object diff --git a/cla-backend-go/swagger/common/gitlab-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization.yaml index e344805bf..5e407708a 100644 --- a/cla-backend-go/swagger/common/gitlab-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization.yaml @@ -3,29 +3,37 @@ type: object properties: - organizationID: + organization_id: type: string description: internal id of the gitlab organization - dateCreated: + date_created: type: string example: "2020-02-06T09:31:49.245630+0000" minLength: 18 maxLength: 64 - dateModified: + date_modified: type: string example: "2020-02-06T09:31:49.245646+0000" minLength: 18 maxLength: 64 - organizationName: + organization_name: type: string example: "communitybridge" - organizationSfid: + organization_url: + type: string + description: The Gitlab Group/Organization url + example: "github.com/Linux Foundation/product/EasyCLA" + organization_full_path: + type: string + description: The Gitlab Group/Organization full path + example: "linuxfoundation/product/easycla" + organization_sfid: type: string example: "a0941000002wBz4AAA" version: type: string example: "v1" - projectSFID: + project_sfid: type: string example: "a0941000002wBz4AAA" enabled: @@ -36,14 +44,14 @@ properties: type: boolean description: Flag that indicates whether this Gitlab Organization is authorized with Gitlab, if false it might mean that Gitlab Oauth process is not compeleted yet or the token was revoked and user needs to go through the auth process again x-omitempty: false - autoEnabled: + auto_enabled: type: boolean description: Flag to indicate if this Gitlab Organization is configured to allow new repositories to be auto-enabled/auto-enrolled in EasyCLA. x-omitempty: false - autoEnabledClaGroupID: + auto_enabled_cla_group_id: type: string description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. - gitlabInfo: + gitlab_info: type: object properties: error: @@ -59,17 +67,16 @@ properties: bio: type: string x-nullable: true - htmlUrl: + html_url: type: string x-nullable: true example: "https://github.com/communitybridge" format: uri - installationURL: + installation_url: type: string x-nullable: true description: "if the Gitlab Organization is not connected yet can use this url to go through the process of authorizing the easyCLA bot" format: uri - repositories: type: object properties: @@ -79,4 +86,4 @@ properties: list: type: array items: - $ref: '#/definitions/github-repository-info' + $ref: '#/definitions/gitlab-repository-info' diff --git a/cla-backend-go/swagger/common/gitlab-project-organization.yaml b/cla-backend-go/swagger/common/gitlab-project-organization.yaml index e1701acef..f1062c3c1 100644 --- a/cla-backend-go/swagger/common/gitlab-project-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-project-organization.yaml @@ -22,7 +22,7 @@ properties: type: string x-nullable: true format: uri - gitlab_organization_name: + organization_name: type: string description: The Gitlab Organization name example: "kubernetes" @@ -31,6 +31,14 @@ properties: pattern: '^([\w\-\.]+){2,255}$' minLength: 2 maxLength: 255 + organization_url: + type: string + description: The Gitlab Group/Organization url + example: "github.com/Linux Foundation/product/EasyCLA" + organization_full_path: + type: string + description: The Gitlab Group/Organization full path + example: "linuxfoundation/product/easycla" connection_status: type: string enum: diff --git a/cla-backend-go/swagger/common/gitlab-repository-info.yaml b/cla-backend-go/swagger/common/gitlab-repository-info.yaml new file mode 100644 index 000000000..b88c4d414 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-repository-info.yaml @@ -0,0 +1,17 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + repository_gitlab_id: + type: integer + example: 250509778 + repository_name: + type: string + example: "communitybridge/easycla" + repository_type: + type: string + example: "github" + repository_url: + type: string + example: "https://github.com/communitybridge/easycla" diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index f313e52fc..851c0b61a 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -6,8 +6,14 @@ package tests import ( "encoding/json" "fmt" + "io" + "net/url" + "os" "testing" + ini "github.com/communitybridge/easycla/cla-backend-go/init" + "github.com/spf13/viper" + "github.com/communitybridge/easycla/cla-backend-go/utils" gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" @@ -17,30 +23,102 @@ import ( const enabled = false // nolint const group = "The Linux Foundation/product/EasyCLA" -const accessToken = "" +const accessInfo = "" + +const easyCLAGroupName = "linuxfoundation/product/easycla" + +func TestGitLabGetGroup(t *testing.T) { // no lint + + if enabled { // nolint + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() -func TestGitLabSearchGroup(t *testing.T) { // no lint + // Create a new GitLab App client instance + gitLabApp := gitlab2.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + // Create a new client + gitLabClient, err := gitlab2.NewGitlabOauthClient(accessInfo, gitLabApp) + assert.Nil(t, err, "GitLab OAuth Client Error is Nil") + assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") + + // Need to look up the GitLab Group/Organization to obtain the ID + groupModel, resp, getError := gitLabClient.Groups.GetGroup(url.QueryEscape(easyCLAGroupName)) + assert.Nil(t, getError, "GitLab GetGroup Error is Nil") + if resp.StatusCode < 200 || resp.StatusCode > 299 { + assert.Fail(t, fmt.Sprintf("unable to locate GitLab group by value: %s, status code: %d", easyCLAGroupName, resp.StatusCode)) + } + assert.NotNil(t, groupModel, "Group Model is not nil") + t.Logf("group name: %s, ID: %d, path: %s", groupModel.Name, groupModel.ID, groupModel.Path) + } +} + +func TestGitLabListGroups(t *testing.T) { // no lint if enabled { // nolint - // Get the client - gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) - assert.Nil(t, err, "GitLab OAuth Client") + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + + // Create a new GitLab App client instance + gitLabApp := gitlab2.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + // Create a new client + gitLabClient, err := gitlab2.NewGitlabOauthClient(accessInfo, gitLabApp) + assert.Nil(t, err, "GitLab OAuth Client Error is Nil") + assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") // Need to look up the GitLab Group/Organization to obtain the ID opts := &gitlab.ListGroupsOptions{ - ListOptions: gitlab.ListOptions{}, + ListOptions: gitlab.ListOptions{ + Page: 1, + PerPage: 100, + }, } groups, resp, searchErr := gitLabClient.Groups.ListGroups(opts) - assert.Nil(t, searchErr, "GitLab OAuth Client") + assert.Nil(t, searchErr, "GitLab List Groups Error is Nil") + if searchErr != nil { + t.Logf("list groups error: %+v", searchErr) + } if resp.StatusCode < 200 || resp.StatusCode > 299 { - assert.Fail(t, "unable to locate GitLab group by name: %s, status code: %d", group, resp.StatusCode) + respBody, readErr := io.ReadAll(resp.Body) + assert.Nil(t, readErr, "GitLab Response Body Read is Nil") + assert.Fail(t, fmt.Sprintf("unable to list GitLab groups, status code: %d, body: %s", resp.StatusCode, respBody)) } for _, g := range groups { - t.Logf("group name: %s, ID: %d, path: %s", g.Name, g.ID, g.Path) - } - if len(groups) != 1 { - - assert.Fail(t, fmt.Sprintf("expecting 1 result for GitLab group name '%s' search, found: %d - %+v", group, len(groups), groups)) + t.Logf("name: %s, id: %d, web url: %s, path: %s, full path: %s", g.Name, g.ID, g.WebURL, g.Path, g.FullPath) } } } @@ -48,9 +126,33 @@ func TestGitLabSearchGroup(t *testing.T) { // no lint func TestGitLabListProjects(t *testing.T) { // no lint if enabled { // nolint - // Get the client - gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(accessToken) - assert.Nil(t, err, "GitLab OAuth Client") + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + + // Create a new GitLab App client instance + gitLabApp := gitlab2.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + // Create a new client + gitLabClient, err := gitlab2.NewGitlabOauthClient(accessInfo, gitLabApp) + assert.Nil(t, err, "GitLab OAuth Client Error is Nil") + assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") // Query GitLab for repos - fetch the list of repositories available to the GitLab App listProjectsOpts := &gitlab.ListProjectsOptions{ diff --git a/cla-backend-go/v2/common/models.go b/cla-backend-go/v2/common/models.go index d3fc221bd..2e284076c 100644 --- a/cla-backend-go/v2/common/models.go +++ b/cla-backend-go/v2/common/models.go @@ -1,20 +1,22 @@ -package common - // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT +package common + import ( models2 "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" ) -// GitlabOrganization is data model for gitlab organizations -type GitlabOrganization struct { +// GitLabOrganization is data model for gitlab organizations +type GitLabOrganization struct { OrganizationID string `json:"organization_id"` ExternalGroupID int `json:"external_gitlab_group_id"` DateCreated string `json:"date_created,omitempty"` DateModified string `json:"date_modified,omitempty"` OrganizationName string `json:"organization_name,omitempty"` OrganizationNameLower string `json:"organization_name_lower,omitempty"` + OrganizationFullPath string `json:"organization_full_path,omitempty"` + OrganizationURL string `json:"organization_url,omitempty"` OrganizationSFID string `json:"organization_sfid,omitempty"` ProjectSFID string `json:"project_sfid"` Enabled bool `json:"enabled"` @@ -27,27 +29,49 @@ type GitlabOrganization struct { } // ToModel converts to models.GitlabOrganization -func ToModel(in *GitlabOrganization) *models2.GitlabOrganization { +func ToModel(in *GitLabOrganization) *models2.GitlabOrganization { return &models2.GitlabOrganization{ OrganizationID: in.OrganizationID, DateCreated: in.DateCreated, DateModified: in.DateModified, OrganizationName: in.OrganizationName, + OrganizationFullPath: in.OrganizationFullPath, + OrganizationURL: in.OrganizationURL, OrganizationSfid: in.OrganizationSFID, Version: in.Version, Enabled: in.Enabled, AutoEnabled: in.AutoEnabled, AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, - ProjectSFID: in.ProjectSFID, + ProjectSfid: in.ProjectSFID, // Not exposing ExternalGroupID } } // ToModels converts a list of GitLab organizations to a list of external GitLab organization response models -func ToModels(input []*GitlabOrganization) []*models2.GitlabOrganization { +func ToModels(input []*GitLabOrganization) []*models2.GitlabOrganization { out := make([]*models2.GitlabOrganization, 0) for _, in := range input { out = append(out, ToModel(in)) } return out } + +// GitLabAddOrganization is data model for GitLab add organization requests +type GitLabAddOrganization struct { + OrganizationID string `json:"organization_id"` + ExternalGroupID int `json:"external_gitlab_group_id"` + DateCreated string `json:"date_created,omitempty"` + DateModified string `json:"date_modified,omitempty"` + OrganizationName string `json:"organization_name,omitempty"` + OrganizationNameLower string `json:"organization_name_lower,omitempty"` + OrganizationURL string `json:"organization_url,omitempty"` + OrganizationSFID string `json:"organization_sfid,omitempty"` + ProjectSFID string `json:"project_sfid"` + Enabled bool `json:"enabled"` + AutoEnabled bool `json:"auto_enabled"` + BranchProtectionEnabled bool `json:"branch_protection_enabled"` + AutoEnabledClaGroupID string `json:"auto_enabled_cla_group_id,omitempty"` + AuthInfo string `json:"auth_info"` + AuthState string `json:"auth_state"` + Version string `json:"version,omitempty"` +} diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index d868732df..8949da8a5 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -248,7 +248,7 @@ func getAuthorInfo(gitlabUser *gitlab.User) string { return fmt.Sprintf("%d:%s", gitlabUser.ID, gitlabUser.Username) } -func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*common.GitlabOrganization, error) { +func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*common.GitLabOrganization, error) { repositoryPath := mergeEvent.Project.PathWithNamespace parts := strings.Split(repositoryPath, "/") organizationName := parts[0] diff --git a/cla-backend-go/v2/gitlab_organizations/constants.go b/cla-backend-go/v2/gitlab_organizations/constants.go new file mode 100644 index 000000000..d84966b2a --- /dev/null +++ b/cla-backend-go/v2/gitlab_organizations/constants.go @@ -0,0 +1,37 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_organizations + +const ( + // GitLabOrganizationsOrganizationIDColumn constant + GitLabOrganizationsOrganizationIDColumn = "organization_id" + // GitLabOrganizationsOrganizationSFIDColumn constant + GitLabOrganizationsOrganizationSFIDColumn = "organization_sfid" + // GitLabOrganizationsOrganizationNameColumn constant + GitLabOrganizationsOrganizationNameColumn = "organization_name" + // GitLabOrganizationsOrganizationNameLowerColumn constant + GitLabOrganizationsOrganizationNameLowerColumn = "organization_name_lower" + // GitLabOrganizationsEnabledColumn constant + GitLabOrganizationsEnabledColumn = "enabled" + // GitLabOrganizationsAutoEnabledColumn constant + GitLabOrganizationsAutoEnabledColumn = "auto_enabled" + // GitLabOrganizationsAutoEnabledCLAGroupIDColumn constant + GitLabOrganizationsAutoEnabledCLAGroupIDColumn = "auto_enabled_cla_group_id" + // GitLabOrganizationsBranchProtectionEnabledColumn constant + GitLabOrganizationsBranchProtectionEnabledColumn = "branch_protection_enabled" + // GitLabOrganizationsAuthInfoColumn constant + GitLabOrganizationsAuthInfoColumn = "auth_info" + // GitLabOrganizationsOrganizationURLColumn constant + GitLabOrganizationsOrganizationURLColumn = "organization_url" + // GitLabOrganizationsOrganizationFullPathColumn constant + GitLabOrganizationsOrganizationFullPathColumn = "organization_full_path" + // GitLabOrganizationsNoteColumn constant + GitLabOrganizationsNoteColumn = "note" + // GitLabOrganizationsDateCreatedColumn constant + GitLabOrganizationsDateCreatedColumn = "date_created" + // GitLabOrganizationsDateModifiedColumn constant + GitLabOrganizationsDateModifiedColumn = "date_modified" + // GitLabOrganizationsExternalGitLabGroupIDColumn constant + GitLabOrganizationsExternalGitLabGroupIDColumn = "external_gitlab_group_id" +) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index db9097b97..d848f6bf8 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -37,9 +37,9 @@ const ( type RepositoryInterface interface { AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) - GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) + GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models2.GitlabOrganization, error) - UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, gitLabGroupID int, authInfo string) error + UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } @@ -81,7 +81,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS if existingRecord != nil { log.WithFields(f).Debugf("An existing GitLab organization with name %s exists in our database", gitLabOrganizationName) // If everything matches... - if projectSFID == existingRecord.ProjectSFID { + if projectSFID == existingRecord.ProjectSfid { log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") enabledFlag := true updateErr := repo.UpdateGitlabOrganization(ctx, projectSFID, gitLabOrganizationName, @@ -113,7 +113,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS } enabled := true - gitlabOrg := &common.GitlabOrganization{ + gitlabOrg := &common.GitLabOrganization{ OrganizationID: organizationID.String(), DateCreated: currentTime, DateModified: currentTime, @@ -127,6 +127,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS BranchProtectionEnabled: aws.BoolValue(input.BranchProtectionEnabled), AuthState: authStateNonce.String(), Version: "v1", + // OrganizationURL: set later when we can authenticate to the API } log.WithFields(f).Debug("Encoding GitLab organization record for adding to the database...") @@ -165,7 +166,7 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s "projectSFID": projectSFID, } - condition := expression.Key("organization_sfid").Equal(expression.Value(projectSFID)) + condition := expression.Key(GitLabOrganizationsOrganizationSFIDColumn).Equal(expression.Value(projectSFID)) builder := expression.NewBuilder().WithKeyCondition(condition) filter := expression.Name("enabled").Equal(expression.Value(true)) @@ -202,7 +203,7 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s }, nil } - var resultOutput []*common.GitlabOrganization + var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { return nil, err @@ -223,7 +224,7 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr gitLabOrganizationName = strings.ToLower(gitLabOrganizationName) - condition := expression.Key("organization_name_lower").Equal(expression.Value(strings.ToLower(gitLabOrganizationName))) + condition := expression.Key(GitLabOrganizationsOrganizationNameLowerColumn).Equal(expression.Value(strings.ToLower(gitLabOrganizationName))) builder := expression.NewBuilder().WithKeyCondition(condition) // Use the nice builder to create the expression expr, err := builder.Build() @@ -252,7 +253,7 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr return nil, nil } - var resultOutput []*common.GitlabOrganization + var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) @@ -265,7 +266,7 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr } // GetGitlabOrganization by organization name -func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) { +func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.GetGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -275,7 +276,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza log.WithFields(f).Debug("Querying for GitLab organization by name...") result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ Key: map[string]*dynamodb.AttributeValue{ - "organization_id": { + GitLabOrganizationsOrganizationIDColumn: { S: aws.String(gitlabOrganizationID), }, }, @@ -289,7 +290,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza return nil, nil } - var org common.GitlabOrganization + var org common.GitLabOrganization err = dynamodbattribute.UnmarshalMap(result.Item, &org) if err != nil { log.WithFields(f).Warnf("error unmarshalling organization table data, error: %v", err) @@ -299,35 +300,40 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza } // UpdateGitlabOrganizationAuth updates the specified Gitlab organization oauth info -func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, gitLabGroupID int, authInfo string) error { +func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, + "organizationID": organizationID, + "organizationFullPath": organizationFullPath, + "organizationURL": organizationURL, "tableName": repo.gitlabOrgTableName, } _, currentTime := utils.CurrentTime() - gitlabOrg, lookupErr := repo.GetGitlabOrganization(ctx, gitlabOrganizationID) - if lookupErr != nil { - log.WithFields(f).Warnf("error looking up Gitlab organization by id, error: %+v", lookupErr) - return lookupErr - } - if gitlabOrg == nil { - lookupErr := errors.New("unable to lookup Gitlab organization by id") - log.WithFields(f).Warnf("error looking up Gitlab organization, error: %+v", lookupErr) + gitlabOrg, lookupErr := repo.GetGitlabOrganization(ctx, organizationID) + if lookupErr != nil || gitlabOrg == nil { + log.WithFields(f).Warnf("error looking up Gitlab organization by id: %s, error: %+v", organizationID, lookupErr) return lookupErr } expressionAttributeNames := map[string]*string{ - "#A": aws.String("auth_info"), - "#M": aws.String("date_modified"), - "#P": aws.String("external_gitlab_group_id"), + "#A": aws.String(GitLabOrganizationsAuthInfoColumn), + "#U": aws.String(GitLabOrganizationsOrganizationURLColumn), + "#FP": aws.String(GitLabOrganizationsOrganizationFullPathColumn), + "#M": aws.String(GitLabOrganizationsDateModifiedColumn), + "#P": aws.String(GitLabOrganizationsExternalGitLabGroupIDColumn), } expressionAttributeValues := map[string]*dynamodb.AttributeValue{ ":a": { S: aws.String(authInfo), }, + ":u": { + S: aws.String(organizationURL), + }, + ":fp": { + S: aws.String(organizationFullPath), + }, ":m": { S: aws.String(currentTime), }, @@ -336,11 +342,11 @@ func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabO }, } - updateExpression := "SET #A = :a, #M = :m, #P = :p" + updateExpression := "SET #A = :a, #U = :u, #FP = :fp, #M = :m, #P = :p" input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ - "organization_id": { + GitLabOrganizationsOrganizationIDColumn: { S: aws.String(gitlabOrg.OrganizationID), }, }, @@ -384,10 +390,10 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID } expressionAttributeNames := map[string]*string{ - "#A": aws.String("auto_enabled"), - "#C": aws.String("auto_enabled_cla_group_id"), - "#B": aws.String("branch_protection_enabled"), - "#M": aws.String("date_modified"), + "#A": aws.String(GitLabOrganizationsAutoEnabledColumn), + "#C": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), + "#B": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), + "#M": aws.String(GitLabOrganizationsDateModifiedColumn), } expressionAttributeValues := map[string]*dynamodb.AttributeValue{ ":a": { @@ -415,7 +421,7 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ - "organization_id": { + GitLabOrganizationsOrganizationIDColumn: { S: aws.String(gitlabOrg.OrganizationID), }, }, @@ -465,14 +471,14 @@ func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID _, err := repo.dynamoDBClient.UpdateItem( &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ - "organization_id": { + GitLabOrganizationsOrganizationIDColumn: { S: aws.String(gitlabOrganizationID), }, }, ExpressionAttributeNames: map[string]*string{ - "#E": aws.String("enabled"), - "#N": aws.String("note"), - "#D": aws.String("date_modified"), + "#E": aws.String(GitLabOrganizationsEnabledColumn), + "#N": aws.String(GitLabOrganizationsNoteColumn), + "#D": aws.String(GitLabOrganizationsDateModifiedColumn), }, ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":e": { @@ -498,7 +504,7 @@ func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID return nil } -func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*common.GitlabOrganization) []*models2.GitlabOrganization { +func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*common.GitLabOrganization) []*models2.GitlabOrganization { f := logrus.Fields{ "functionName": "buildGitlabOrganizationListModels", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 838cf3341..821340811 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -36,7 +36,7 @@ import ( type ServiceInterface interface { AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) - GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) + GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error @@ -116,7 +116,7 @@ func (s *Service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationI } // GetGitlabOrganizationByID returns the record associated with the GitLab Organization ID -func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitlabOrganization, error) { +func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -203,8 +203,11 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string AutoEnabled: org.AutoEnabled, AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, AutoEnabledCLAGroupName: strings.TrimSpace(autoEnabledCLAGroupName), - GitlabOrganizationName: org.OrganizationName, + OrganizationName: org.OrganizationName, + OrganizationURL: org.OrganizationURL, + OrganizationFullPath: org.OrganizationFullPath, InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), + BranchProtectionEnabled: false, ConnectionStatus: "", // updated below Repositories: []*models.GitlabProjectRepository{}, } @@ -242,7 +245,7 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string // Sort everything nicely sort.Slice(out.List, func(i, j int) bool { - return strings.ToLower(out.List[i].GitlabOrganizationName) < strings.ToLower(out.List[j].GitlabOrganizationName) + return strings.ToLower(out.List[i].OrganizationName) < strings.ToLower(out.List[j].OrganizationName) }) for _, orgList := range out.List { sort.Slice(orgList.Repositories, func(i, j int) bool { @@ -318,7 +321,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani } for _, g := range groups { if g.Name == gitLabOrgModel.OrganizationName { - return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted) + return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) } } diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index ed6d4d5b5..f300a62d8 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -32,7 +32,7 @@ func (s *Service) GitLabAddRepository(ctx context.Context, projectSFID string, i } // GitLabAddRepositoriesByApp adds the GitLab repositories based on the application credentials -func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *v2GitLabOrg.GitlabOrganization) ([]*v2Models.GitlabRepository, error) { +func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *v2GitLabOrg.GitLabOrganization) ([]*v2Models.GitlabRepository, error) { f := logrus.Fields{ "functionName": "v2.repositories.gitlab_services.GitLabAddRepositoriesByApp", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index a4b8bda69..bf4619c2e 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -56,7 +56,7 @@ type ServiceInterface interface { GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabListRepositories, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) - GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitlabOrganization) ([]*v2Models.GitlabRepository, error) + GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitLabOrganization) ([]*v2Models.GitlabRepository, error) GitLabEnableRepository(ctx context.Context, repositoryID string) error GitLabDisableRepository(ctx context.Context, repositoryID string) error GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error From 1785bc5f5e92a660600ad13fe0b27b7c8f0160ea Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 17 Aug 2021 18:26:50 -0700 Subject: [PATCH 0429/1276] Updated GitLab Repository Data Models/Responses (#3166) --- cla-backend-go/repositories/models.go | 1 + cla-backend-go/swagger/cla.v2.yaml | 2 +- .../swagger/common/gitlab-add-repository.yaml | 9 +++++++++ .../common/gitlab-project-organization.yaml | 8 ++++---- .../common/gitlab-project-repository.yaml | 9 +++++++++ .../swagger/common/gitlab-repository-info.yaml | 17 ++++++++++++++--- .../swagger/common/gitlab-repository.yaml | 4 ++++ .../v2/gitlab_organizations/service.go | 4 ++-- .../v2/repositories/gitlab_services.go | 4 +++- cla-backend-go/v2/repositories/repository.go | 2 ++ 10 files changed, 49 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/repositories/models.go b/cla-backend-go/repositories/models.go index 2390e7ce4..30d3b0c46 100644 --- a/cla-backend-go/repositories/models.go +++ b/cla-backend-go/repositories/models.go @@ -17,6 +17,7 @@ type RepositoryDBModel struct { RepositoryExternalID string `dynamodbav:"repository_external_id" json:"repository_external_id,omitempty"` // Integer value from GitHub RepositoryID string `dynamodbav:"repository_id" json:"repository_id,omitempty"` RepositoryName string `dynamodbav:"repository_name" json:"repository_name,omitempty"` + RepositoryFullPath string `dynamodbav:"repository_full_path" json:"repository_full_path,omitempty"` RepositoryOrganizationName string `dynamodbav:"repository_organization_name" json:"repository_organization_name,omitempty"` RepositoryCLAGroupID string `dynamodbav:"repository_project_id" json:"repository_project_id,omitempty"` RepositorySfdcID string `dynamodbav:"repository_sfdc_id" json:"repository_sfdc_id,omitempty"` diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index d8f01d922..b448ba864 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4455,7 +4455,7 @@ definitions: $ref: './common/gitlab-organization.yaml' gitlab-repository: - $ref: './common/github-repository.yaml' + $ref: './common/gitlab-repository.yaml' gitlab-create-organization: $ref: './common/gitlab-create-organization.yaml' diff --git a/cla-backend-go/swagger/common/gitlab-add-repository.yaml b/cla-backend-go/swagger/common/gitlab-add-repository.yaml index 7e908ead9..f3a2d115d 100644 --- a/cla-backend-go/swagger/common/gitlab-add-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-add-repository.yaml @@ -7,6 +7,7 @@ required: - repository_project_sfid - repository_cla_group_id - repository_name + - repository_full_path - repository_organization_name - repository_url properties: @@ -24,14 +25,22 @@ properties: repository_name: type: string description: The repository name + minLength: 3 example: 'easycla-test-repo-4' + repository_full_path: + type: string + description: The repository full path + minLength: 3 + example: 'linuxfoundation/product/easycla/easycla-test-repo-4' repository_organization_name: type: string description: The organization name associated with this repository + minLength: 3 example: 'The Linux Foundation/product/EasyCLA' repository_url: type: string description: The external repository URL + minLength: 8 example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' enabled: type: boolean diff --git a/cla-backend-go/swagger/common/gitlab-project-organization.yaml b/cla-backend-go/swagger/common/gitlab-project-organization.yaml index f1062c3c1..badacff1b 100644 --- a/cla-backend-go/swagger/common/gitlab-project-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-project-organization.yaml @@ -8,17 +8,17 @@ properties: type: boolean description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. x-omitempty: false - autoEnableCLAGroupID: + auto_enable_cla_group_id: type: string description: The CLA Group ID which is attached to the auto-enabled flag - autoEnabledCLAGroupName: + auto_enabled_cla_group_name: type: string description: The CLA Group name which is attached to the auto-enabled flag - branchProtectionEnabled: + branch_protection_enabled: type: boolean description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. x-omitempty: false - installationURL: + installation_url: type: string x-nullable: true format: uri diff --git a/cla-backend-go/swagger/common/gitlab-project-repository.yaml b/cla-backend-go/swagger/common/gitlab-project-repository.yaml index ac3ebdee1..d5828ce47 100644 --- a/cla-backend-go/swagger/common/gitlab-project-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-project-repository.yaml @@ -16,10 +16,19 @@ properties: repository_name: type: string description: 'GitLab Repository/Project name' + example: 'easycla-test-repo-4' + x-omitempty: false + repository_full_path: + type: string + description: The repository full path + example: 'linuxfoundation/product/easycla/easycla-test-repo-4' + minLength: 3 x-omitempty: false repository_url: type: string description: 'GitLab Repository/Project URL' + minLength: 8 + example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' x-omitempty: false cla_group_id: description: CLA Group ID diff --git a/cla-backend-go/swagger/common/gitlab-repository-info.yaml b/cla-backend-go/swagger/common/gitlab-repository-info.yaml index b88c4d414..3bf9699b1 100644 --- a/cla-backend-go/swagger/common/gitlab-repository-info.yaml +++ b/cla-backend-go/swagger/common/gitlab-repository-info.yaml @@ -5,13 +5,24 @@ type: object properties: repository_gitlab_id: type: integer - example: 250509778 + description: 'Repository GitLab ID value' + minimum: 1 + example: 2292 repository_name: type: string - example: "communitybridge/easycla" + description: 'GitLab Repository/Project name' + example: 'easycla-test-repo-4' + minLength: 3 repository_type: type: string example: "github" repository_url: type: string - example: "https://github.com/communitybridge/easycla" + description: 'GitLab Repository/Project URL' + minLength: 8 + example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' + repository_full_path: + type: string + description: The repository full path + example: 'linuxfoundation/product/easycla/easycla-test-repo-4' + minLength: 3 diff --git a/cla-backend-go/swagger/common/gitlab-repository.yaml b/cla-backend-go/swagger/common/gitlab-repository.yaml index 6af477008..8628be883 100644 --- a/cla-backend-go/swagger/common/gitlab-repository.yaml +++ b/cla-backend-go/swagger/common/gitlab-repository.yaml @@ -21,6 +21,10 @@ properties: type: string description: The repository name example: 'easycla-test-repo-4' + repository_full_path: + type: string + description: The repository full path + example: 'linuxfoundation/product/easycla/easycla-test-repo-4' repository_organization_name: type: string description: The organization name associated with this repository diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 821340811..fb0da1513 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -201,8 +201,8 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string rorg := &models.GitlabProjectOrganization{ AutoEnabled: org.AutoEnabled, - AutoEnableCLAGroupID: org.AutoEnabledClaGroupID, - AutoEnabledCLAGroupName: strings.TrimSpace(autoEnabledCLAGroupName), + AutoEnableClaGroupID: org.AutoEnabledClaGroupID, + AutoEnabledClaGroupName: strings.TrimSpace(autoEnabledCLAGroupName), OrganizationName: org.OrganizationName, OrganizationURL: org.OrganizationURL, OrganizationFullPath: org.OrganizationFullPath, diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index f300a62d8..56b810e86 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -100,7 +100,8 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel Enabled: false, // default is false Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), RepositoryExternalID: utils.Int64(int64(proj.ID)), - RepositoryName: utils.StringRef(proj.PathWithNamespace), + RepositoryName: utils.StringRef(proj.Name), + RepositoryFullPath: utils.StringRef(proj.PathWithNamespace), RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), RepositoryURL: utils.StringRef(proj.WebURL), @@ -225,6 +226,7 @@ func dbModelToGitLabRepository(dbModel *repoModels.RepositoryDBModel) (*v2Models RepositoryClaGroupID: dbModel.RepositoryCLAGroupID, // CLA Group ID RepositoryExternalID: gitLabExternalID, // GitLab unique gitV1Repository ID RepositoryName: dbModel.RepositoryName, // Short repository name + RepositoryFullPath: dbModel.RepositoryFullPath, // Full repository path RepositoryOrganizationName: dbModel.RepositoryOrganizationName, // Group/Organization name RepositoryURL: dbModel.RepositoryURL, // full url RepositoryType: dbModel.RepositoryType, // gitlab diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index 6db94ad38..f21a107d3 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -233,6 +233,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string "repositoryExternalID": utils.Int64Value(input.RepositoryExternalID), "repositoryURL": utils.StringValue(input.RepositoryURL), "repositoryName": utils.StringValue(input.RepositoryName), + "repositoryFullPath": utils.StringValue(input.RepositoryFullPath), "repositoryType": utils.GitLabLower, "repositoryCLAGroupID": utils.StringValue(input.RepositoryClaGroupID), "repositoryProjectSFID": utils.StringValue(input.RepositoryProjectSfid), @@ -271,6 +272,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string DateModified: currentTime, RepositoryExternalID: repositoryExternalIDString, RepositoryName: utils.StringValue(input.RepositoryName), + RepositoryFullPath: utils.StringValue(input.RepositoryFullPath), RepositoryURL: utils.StringValue(input.RepositoryURL), RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), // gitlab group/organization RepositoryCLAGroupID: utils.StringValue(input.RepositoryClaGroupID), From 0e5867a45fa7a2a886aecdc02093b6329bdd6303 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 17 Aug 2021 18:48:37 -0700 Subject: [PATCH 0430/1276] Added Repository Full Path (#3167) --- cla-backend-go/v2/gitlab_organizations/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index fb0da1513..0e7717498 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -410,6 +410,7 @@ func toGitLabProjectResponse(gitLabListRepos *models.GitlabListRepositories) []* RepositoryGitlabID: repo.RepositoryExternalID, RepositoryID: repo.RepositoryID, RepositoryName: repo.RepositoryName, + RepositoryFullPath: repo.RepositoryFullPath, RepositoryURL: repo.RepositoryURL, }) } From 19061e9d46e5e43cd09449afcca21a4b554c8d87 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 18 Aug 2021 17:10:25 +0300 Subject: [PATCH 0431/1276] gitlab repository webhook setup (#3168) --- .../cmd/dynamo_events_lambda/main.go | 9 +- cla-backend-go/gitlab/repository.go | 4 - cla-backend-go/gitlab/webhook.go | 81 +++++++ .../v2/dynamo_events/gitlab_webhooks.go | 212 ++++++++++++++++++ cla-backend-go/v2/dynamo_events/service.go | 19 +- cla-backend-go/v2/gitlab-activity/service.go | 14 +- .../v2/gitlab_organizations/repository.go | 16 +- .../v2/gitlab_organizations/service.go | 18 ++ 8 files changed, 349 insertions(+), 24 deletions(-) delete mode 100644 cla-backend-go/gitlab/repository.go create mode 100644 cla-backend-go/gitlab/webhook.go create mode 100644 cla-backend-go/v2/dynamo_events/gitlab_webhooks.go diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index ceb81722d..3b3b8a3c5 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -8,6 +8,8 @@ import ( "encoding/json" "os" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + "github.com/communitybridge/easycla/cla-backend-go/gitlab" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" @@ -90,11 +92,12 @@ func init() { claManagerRequestsRepo := cla_manager.NewRepository(awsSession, stage) approvalListRequestsRepo := approval_list.NewRepository(awsSession, stage) githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) + gitlabOrganizationRepo := gitlab_organizations.NewRepository(awsSession, stage) token.Init(configFile.Auth0Platform.ClientID, configFile.Auth0Platform.ClientSecret, configFile.Auth0Platform.URL, configFile.Auth0Platform.Audience) github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // initialize gitlab - _ = gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) + gitlabApp := gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) user_service.InitClient(configFile.APIGatewayURL, configFile.AcsAPIKey) project_service.InitClient(configFile.APIGatewayURL) @@ -138,12 +141,14 @@ func init() { projectClaGroupRepo, eventsRepo, projectRepo, + gitlabOrganizationRepo, projectService, githubOrganizationsService, repositoriesService, gerritService, claManagerRequestsRepo, - approvalListRequestsRepo) + approvalListRequestsRepo, + gitlabApp) } func handler(ctx context.Context, event events.DynamoDBEvent) { diff --git a/cla-backend-go/gitlab/repository.go b/cla-backend-go/gitlab/repository.go deleted file mode 100644 index 90f42708c..000000000 --- a/cla-backend-go/gitlab/repository.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package gitlab diff --git a/cla-backend-go/gitlab/webhook.go b/cla-backend-go/gitlab/webhook.go new file mode 100644 index 000000000..1efd1f0e2 --- /dev/null +++ b/cla-backend-go/gitlab/webhook.go @@ -0,0 +1,81 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "fmt" + + "github.com/xanzy/go-gitlab" +) + +// SetWebHook is responsible for adding the webhook for given projectID, if webhook is there already +// tries to set the attributes if anything is missing, should be idempotent operation +func SetWebHook(gitLabClient *gitlab.Client, hookURL string, projectID int, token string) error { + existingWebHook, err := findExistingWebHook(gitLabClient, hookURL, projectID) + if err != nil { + return err + } + + if existingWebHook == nil { + _, _, err = gitLabClient.Projects.AddProjectHook(projectID, &gitlab.AddProjectHookOptions{ + URL: gitlab.String(hookURL), + MergeRequestsEvents: gitlab.Bool(true), + PushEvents: gitlab.Bool(true), + EnableSSLVerification: gitlab.Bool(true), + Token: gitlab.String(token), + }) + if err != nil { + return fmt.Errorf("adding web hook for project : %d, failed : %v", projectID, err) + } + return nil + } + + if !existingWebHook.EnableSSLVerification || !existingWebHook.MergeRequestsEvents || !existingWebHook.PushEvents { + _, _, err = gitLabClient.Projects.EditProjectHook(projectID, existingWebHook.ID, &gitlab.EditProjectHookOptions{ + URL: gitlab.String(hookURL), + MergeRequestsEvents: gitlab.Bool(true), + PushEvents: gitlab.Bool(true), + EnableSSLVerification: gitlab.Bool(true), + Token: gitlab.String(token), + }) + if err != nil { + return fmt.Errorf("editing web hook for project : %d, failed : %v", projectID, err) + } + } + + return nil +} + +// RemoveWebHook removes existing webhook from the given project +func RemoveWebHook(gitLabClient *gitlab.Client, hookURL string, projectID int) error { + existingWebHook, err := findExistingWebHook(gitLabClient, hookURL, projectID) + if err != nil { + return err + } + + if existingWebHook == nil { + return nil + } + + _, err = gitLabClient.Projects.DeleteProjectHook(projectID, existingWebHook.ID) + return err + +} + +func findExistingWebHook(gitLabClient *gitlab.Client, hookURL string, projectID int) (*gitlab.ProjectHook, error) { + hooks, _, err := gitLabClient.Projects.ListProjectHooks(projectID, &gitlab.ListProjectHooksOptions{}) + if err != nil { + return nil, fmt.Errorf("fetching hooks for project : %d, failed : %v", projectID, err) + } + + var existingWebHook *gitlab.ProjectHook + for _, hook := range hooks { + if hook.URL == hookURL { + existingWebHook = hook + break + } + } + + return existingWebHook, nil +} diff --git a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go new file mode 100644 index 000000000..12e5276be --- /dev/null +++ b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go @@ -0,0 +1,212 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package dynamo_events + +import ( + "fmt" + "strconv" + + "github.com/aws/aws-lambda-go/events" + "github.com/communitybridge/easycla/cla-backend-go/config" + "github.com/communitybridge/easycla/cla-backend-go/gitlab" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/repositories" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" +) + +func (s *service) GitLabRepoAddedWebhookEventHandler(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "GitLabRepoAddedWebhookEventHandler", + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + var newRepoModel repositories.RepositoryDBModel + + log.WithFields(f).Debugf("processing record %s event...", event.EventName) + err := unmarshalStreamImage(event.Change.NewImage, &newRepoModel) + if err != nil { + log.WithFields(f).Warnf("problem unmarshalling the new repository model event, error: %+v", err) + return err + } + + if !s.isGitlabRepo(log.WithFields(f), &newRepoModel) { + return nil + } + + if !newRepoModel.Enabled { + log.WithFields(f).Debugf("gitlab repo is not enabled, nothing to do at this point") + return nil + } + + repositoryID := newRepoModel.RepositoryID + repositoryName := newRepoModel.RepositoryName + repositoryExternalID := newRepoModel.RepositoryExternalID + + log.WithFields(f).Debugf("adding webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) + + gitlabOrg, err := s.gitLabOrgRepo.GetGitlabOrganizationByName(ctx, newRepoModel.RepositoryOrganizationName) + if err != nil { + return fmt.Errorf("fetching gitlab org : %s failed : %v", newRepoModel.RepositoryOrganizationName, err) + } + + gitLabClient, err := gitlab.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + if err != nil { + return fmt.Errorf("initializing GitLab client failed : %v", err) + } + + repositoryExternalIDInt, err := strconv.Atoi(repositoryExternalID) + if err != nil { + return fmt.Errorf("parding external repository id failed : %v", err) + } + + conf := config.GetConfig() + if err := gitlab.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { + log.WithFields(f).Errorf("adding gitlab webhook failed : %v", err) + } + + log.WithFields(f).Debugf("gitlab webhhok added succesfully for repository") + return nil +} + +func (s *service) GitlabRepoModifiedWebhookEventHandler(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "GitlabRepoModifiedWebhookEventHandler", + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + var newRepoModel repositories.RepositoryDBModel + var oldRepoModel repositories.RepositoryDBModel + + log.WithFields(f).Debugf("processing record %s event...", event.EventName) + err := unmarshalStreamImage(event.Change.OldImage, &oldRepoModel) + if err != nil { + log.WithFields(f).Warnf("problem unmarshalling the new repository model event, error: %+v", err) + return err + } + + err = unmarshalStreamImage(event.Change.NewImage, &newRepoModel) + if err != nil { + log.WithFields(f).Warnf("problem unmarshalling the old repository model event, error: %+v", err) + return err + } + + if !s.isGitlabRepo(log.WithFields(f), &newRepoModel) { + return nil + } + + if newRepoModel.Enabled == oldRepoModel.Enabled { + log.WithFields(f).Debugf("only changes of Enabled field are processed") + return nil + } + + repositoryID := oldRepoModel.RepositoryID + repositoryName := oldRepoModel.RepositoryName + repositoryExternalID := oldRepoModel.RepositoryExternalID + + if newRepoModel.Enabled { + log.WithFields(f).Debugf("adding webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) + } else { + log.WithFields(f).Debugf("removing webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) + } + + gitlabOrg, err := s.gitLabOrgRepo.GetGitlabOrganizationByName(ctx, oldRepoModel.RepositoryOrganizationName) + if err != nil { + return fmt.Errorf("fetching gitlab org : %s failed : %v", oldRepoModel.RepositoryOrganizationName, err) + } + + gitLabClient, err := gitlab.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + if err != nil { + return fmt.Errorf("initializing GitLab client failed : %v", err) + } + + repositoryExternalIDInt, err := strconv.Atoi(repositoryExternalID) + if err != nil { + return fmt.Errorf("parding external repository id failed : %v", err) + } + + conf := config.GetConfig() + + if newRepoModel.Enabled { + if err := gitlab.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { + log.WithFields(f).Errorf("adding gitlab webhook failed : %v", err) + } + } else { + if err := gitlab.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { + log.WithFields(f).Errorf("removing gitlab webhook failed : %v", err) + } + } + + log.WithFields(f).Debugf("gitlab webhhok processed succesfully for repository") + return nil +} + +func (s *service) GitLabRepoRemovedWebhookEventHandler(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "GitLabRepoRemovedWebhookEventHandler", + "eventID": event.EventID, + "eventName": event.EventName, + "eventSource": event.EventSource, + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + var oldRepoModel repositories.RepositoryDBModel + + log.WithFields(f).Debugf("processing record %s event...", event.EventName) + err := unmarshalStreamImage(event.Change.OldImage, &oldRepoModel) + if err != nil { + log.WithFields(f).Warnf("problem unmarshalling the old repository model event, error: %+v", err) + return err + } + + if !s.isGitlabRepo(log.WithFields(f), &oldRepoModel) { + return nil + } + + repositoryID := oldRepoModel.RepositoryID + repositoryName := oldRepoModel.RepositoryName + repositoryExternalID := oldRepoModel.RepositoryExternalID + + log.WithFields(f).Debugf("removing webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) + + gitlabOrg, err := s.gitLabOrgRepo.GetGitlabOrganizationByName(ctx, oldRepoModel.RepositoryOrganizationName) + if err != nil { + return fmt.Errorf("fetching gitlab org : %s failed : %v", oldRepoModel.RepositoryOrganizationName, err) + } + + gitLabClient, err := gitlab.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + if err != nil { + return fmt.Errorf("initializing GitLab client failed : %v", err) + } + + repositoryExternalIDInt, err := strconv.Atoi(repositoryExternalID) + if err != nil { + return fmt.Errorf("parding external repository id failed : %v", err) + } + + conf := config.GetConfig() + if err := gitlab.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { + log.WithFields(f).Errorf("removing gitlab webhook failed : %v", err) + } + + log.WithFields(f).Debugf("gitlab webhhok removed succesfully for repository") + return nil +} + +func (s *service) isGitlabRepo(logEntry *logrus.Entry, repoModel *repositories.RepositoryDBModel) bool { + if repoModel.RepositoryType != utils.GitLabLower { + logEntry.Debugf("only processing gitlab instances") + return false + } + return true +} diff --git a/cla-backend-go/v2/dynamo_events/service.go b/cla-backend-go/v2/dynamo_events/service.go index 896167d40..83a27c194 100644 --- a/cla-backend-go/v2/dynamo_events/service.go +++ b/cla-backend-go/v2/dynamo_events/service.go @@ -11,6 +11,9 @@ import ( "strings" "sync" + gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + "github.com/communitybridge/easycla/cla-backend-go/gerrits" "github.com/communitybridge/easycla/cla-backend-go/repositories" @@ -56,6 +59,7 @@ type service struct { companyService v2Company.Service projectsClaGroupRepo projects_cla_groups.Repository eventsRepo claevent.Repository + gitLabOrgRepo gitlab_organizations.RepositoryInterface projectRepo project.ProjectRepository projectService project.Service githubOrgService github_organizations.ServiceInterface @@ -64,6 +68,7 @@ type service struct { autoEnableService *autoEnableServiceProvider claManagerRequestsRepo cla_manager.IRepository approvalListRequestsRepo approval_list.IRepository + gitLabApp *gitlab2.App } // Service implements DynamoDB stream event handler service @@ -79,12 +84,14 @@ func NewService(stage string, pcgRepo projects_cla_groups.Repository, eventsRepo claevent.Repository, projectRepo project.ProjectRepository, + gitLabOrgRepo gitlab_organizations.RepositoryInterface, projService project.Service, githubOrgService github_organizations.ServiceInterface, repositoryService repositories.Service, gerritService gerrits.Service, claManagerRequestsRepo cla_manager.IRepository, - approvalListRequestsRepo approval_list.IRepository) Service { + approvalListRequestsRepo approval_list.IRepository, + gitLabApp *gitlab2.App) Service { signaturesTable := fmt.Sprintf("cla-%s-signatures", stage) eventsTable := fmt.Sprintf("cla-%s-events", stage) @@ -102,6 +109,7 @@ func NewService(stage string, projectsClaGroupRepo: pcgRepo, eventsRepo: eventsRepo, projectRepo: projectRepo, + gitLabOrgRepo: gitLabOrgRepo, projectService: projService, githubOrgService: githubOrgService, repositoryService: repositoryService, @@ -109,6 +117,7 @@ func NewService(stage string, autoEnableService: &autoEnableServiceProvider{repositoryService: repositoryService}, claManagerRequestsRepo: claManagerRequestsRepo, approvalListRequestsRepo: approvalListRequestsRepo, + gitLabApp: gitLabApp, } s.registerCallback(signaturesTable, Modify, s.SignatureSignedEvent) @@ -140,6 +149,10 @@ func NewService(stage string, s.registerCallback(repositoryTableName, Modify, s.GithubRepoModifyAddEvent) s.registerCallback(repositoryTableName, Remove, s.GithubRepoModifyAddEvent) + s.registerCallback(repositoryTableName, Insert, s.GitLabRepoAddedWebhookEventHandler) + s.registerCallback(repositoryTableName, Modify, s.GitlabRepoModifiedWebhookEventHandler) + s.registerCallback(repositoryTableName, Remove, s.GitLabRepoRemovedWebhookEventHandler) + // Check and enable/disable the branch protection when a project s.registerCallback(repositoryTableName, Insert, s.EnableBranchProtectionServiceHandler) s.registerCallback(repositoryTableName, Remove, s.DisableBranchProtectionServiceHandler) @@ -168,9 +181,7 @@ func (s *service) ProcessEvents(dynamoDBEvents events.DynamoDBEvent) { // Dumping the event is super verbose // "event": event, } - // Generates a ton of output - // b, _ := json.Marshal(events) // nolint - //fields["events_data"] = string(b) + log.WithFields(fields).Debug("processing event record") key := fmt.Sprintf("%s:%s", tableName, event.EventName) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 8949da8a5..222653083 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -113,7 +113,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git } // try to find the repository via the external id - gitlabRepo, err := s.getGitlabRepoByName(ctx, repositoryPath) + gitlabRepo, err := s.getGitlabRepoByName(ctx, repositoryName) if err != nil { return fmt.Errorf("finding internal repository for gitlab org name failed : %v", err) } @@ -253,18 +253,18 @@ func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeE parts := strings.Split(repositoryPath, "/") organizationName := parts[0] - gitlabOrgs, err := s.gitlabRepository.GetGitlabOrganizationByName(ctx, organizationName) - if err != nil || gitlabOrgs == nil { + gitlabOrg, err := s.gitlabRepository.GetGitlabOrganizationByName(ctx, organizationName) + if err != nil || gitlabOrg == nil { // try getting it with project name as well - gitlabOrgs, err = s.gitlabRepository.GetGitlabOrganizationByName(ctx, mergeEvent.Project.Namespace) - if err != nil || gitlabOrgs == nil { + gitlabOrg, err = s.gitlabRepository.GetGitlabOrganizationByName(ctx, mergeEvent.Project.Namespace) + if err != nil || gitlabOrg == nil { return nil, fmt.Errorf("gitlab org : %s doesn't exist : %v", organizationName, err) } } - gitlabOrg, err := s.gitlabRepository.GetGitlabOrganization(ctx, gitlabOrgs.OrganizationID) + gitlabOrg, err = s.gitlabRepository.GetGitlabOrganization(ctx, gitlabOrg.OrganizationID) if err != nil { - return nil, fmt.Errorf("fetching gitlab org : %s failed : %v", gitlabOrgs.OrganizationID, err) + return nil, fmt.Errorf("fetching gitlab org : %s failed : %v", gitlabOrg.OrganizationID, err) } return gitlabOrg, nil diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index d848f6bf8..09dc1ac1b 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -38,7 +38,7 @@ type RepositoryInterface interface { AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models2.GitlabOrganization, error) + GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error @@ -81,7 +81,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS if existingRecord != nil { log.WithFields(f).Debugf("An existing GitLab organization with name %s exists in our database", gitLabOrganizationName) // If everything matches... - if projectSFID == existingRecord.ProjectSfid { + if projectSFID == existingRecord.ProjectSFID { log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") enabledFlag := true updateErr := repo.UpdateGitlabOrganization(ctx, projectSFID, gitLabOrganizationName, @@ -91,7 +91,11 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS } // Return the updated record - return repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName) + if gitlabOrg, err := repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName); err != nil { + return nil, err + } else { + return common.ToModel(gitlabOrg), nil + } } log.WithFields(f).Debug("Existing GitLab organization with different project SFID - won't be able to update it - will return conflict") @@ -215,7 +219,7 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s } // GetGitlabOrganizationByName get GitLab organization by name -func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models2.GitlabOrganization, error) { +func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v1.gitlab_organizations.repository.GetGitlabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -260,9 +264,7 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr return nil, err } - log.WithFields(f).Debug("building response model...") - gitlabOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) - return gitlabOrgList[0], nil + return resultOutput[0], nil } // GetGitlabOrganization by organization name diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 0e7717498..387f5a879 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -37,6 +37,7 @@ type ServiceInterface interface { AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) + GetGitlabOrganizationByName(ctx context.Context, gitlabOrganizationName string) (*models.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error @@ -132,6 +133,23 @@ func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizat return dbModel, nil } +func (s *Service) GetGitlabOrganizationByName(ctx context.Context, gitlabOrganizationName string) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabOrganizationID": gitlabOrganizationName, + } + + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationName) + dbModel, err := s.repo.GetGitlabOrganizationByName(ctx, gitlabOrganizationName) + if err != nil { + return nil, err + } + + return common.ToModel(dbModel), nil + +} + // GetGitlabOrganizations returns a collection of GitLab organizations based on the specified project SFID value func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ From 9167d57f20cc77f10c7028d82df0f5cb97d7b2a5 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 18 Aug 2021 18:34:43 +0300 Subject: [PATCH 0432/1276] Feature/Gitlab Redirect Contributor console (#3164) Co-authored-by: Harold Wanyama --- cla-backend-go/cmd/server.go | 7 + cla-backend-go/config/config.go | 2 + cla-backend-go/config/ssm.go | 3 + cla-backend-go/swagger/cla.v2.yaml | 40 ++++- cla-backend-go/v2/gitlab_sign/handlers.go | 47 ++++++ cla-backend-go/v2/gitlab_sign/service.go | 190 ++++++++++++++++++++++ cla-backend-go/v2/store/repository.go | 82 ++++++++++ 7 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 cla-backend-go/v2/gitlab_sign/handlers.go create mode 100644 cla-backend-go/v2/gitlab_sign/service.go create mode 100644 cla-backend-go/v2/store/repository.go diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 47a3fb09c..a6888892d 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -21,6 +21,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/gitlab" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_sign" "github.com/communitybridge/easycla/cla-backend-go/emails" @@ -92,6 +93,7 @@ import ( v2ClaManager "github.com/communitybridge/easycla/cla-backend-go/v2/cla_manager" v2Company "github.com/communitybridge/easycla/cla-backend-go/v2/company" v2Health "github.com/communitybridge/easycla/cla-backend-go/v2/health" + "github.com/communitybridge/easycla/cla-backend-go/v2/store" v2Template "github.com/communitybridge/easycla/cla-backend-go/v2/template" "github.com/go-openapi/loads" @@ -255,6 +257,7 @@ func server(localMode bool) http.Handler { githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) gitlabOrganizationRepo := gitlab_organizations.NewRepository(awsSession, stage) claManagerReqRepo := cla_manager.NewRepository(awsSession, stage) + storeRepository := store.NewRepository(awsSession, stage) // Our service layer handlers eventsService := events.NewService(eventsRepo, combinedRepo{ @@ -306,6 +309,7 @@ func server(localMode bool) http.Handler { githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo) gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo) gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, gitV1Repository, gitV2Repository, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) + gitlabSignService := gitlab_sign.NewService(v2RepositoriesService, gitlabOrganizationRepo, usersService, storeRepository) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(gitV1Repository, githubOrganizationsRepo, eventsService, autoEnableService, emailService) @@ -345,6 +349,9 @@ func server(localMode bool) http.Handler { v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) + gitlab_organizations.Configure(v2API, gitlabOrganizationsService, v2RepositoriesService, eventsService) + gitlab_sign.Configure(v2API, gitlabSignService, eventsService, configFile.CLAContributorv2Base) + gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gitlab_organizations.Configure(v2API, gitlabOrganizationsService, v2RepositoriesService, eventsService) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 47407cf23..42e55caec 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -67,6 +67,8 @@ type Config struct { CorporateConsoleV1URL string `json:"corporateConsoleV1URL"` CorporateConsoleV2URL string `json:"corporateConsoleV2URL"` + CLAContributorv2Base string `json:"cla-contributor-v2-base"` + // SNSEventTopic the topic ARN for events SNSEventTopicARN string `json:"snsEventTopicARN"` diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index b0f80e695..c28129a13 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -78,6 +78,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-corporate-base-%s", stage), fmt.Sprintf("cla-corporate-v1-base-%s", stage), fmt.Sprintf("cla-corporate-v2-base-%s", stage), + fmt.Sprintf("cla-contributor-v2-base-%s", stage), fmt.Sprintf("cla-doc-raptor-api-key-%s", stage), fmt.Sprintf("cla-session-store-table-%s", stage), fmt.Sprintf("cla-ses-sender-email-address-%s", stage), @@ -172,6 +173,8 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.Gitlab.RedirectURI = resp.value case fmt.Sprintf("cla-gitlab-app-web-hook-uri-%s", stage): config.Gitlab.WebHookURI = resp.value + case fmt.Sprintf("cla-contributor-v2-base-%s", stage): + config.CLAContributorv2Base = resp.value case fmt.Sprintf("cla-corporate-base-%s", stage): config.CorporateConsoleURL = resp.value diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index b448ba864..3a6dcad53 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -4000,7 +4000,27 @@ paths: $ref: '#/responses/internal-server-error' tags: - gitlab-activity - + + /repository-provider/gitlab/sign/{organizationID}/{gitlabRepositoryID}/{mergeRequestID}: + get: + summary: Gitlab sign request handler + description: Endpoint that will initiate a CLA Signature for the User + security: [ ] + operationId: signRequest + parameters: + - $ref: "#/parameters/x-request-id" + - $ref: "#/parameters/path-gitlabOrganizationID" + - $ref: "#/parameters/path-gitlabRepositoryID" + - $ref: "#/parameters/path-mergeRequestID" + responses: + '200': + description: 'Success' + '400': + $ref: '#/responses/invalid-request' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-sign responses: unauthorized: @@ -4276,6 +4296,24 @@ parameters: pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?4[a-fA-F0-9]{3}-?[89ab][a-fA-F0-9]{3}-?[a-fA-F0-9]{12}$' # uuidv4 minLength: 5 maxLength: 255 + path-gitlabOrganizationID: + name: organizationID + description: GitLab organization ID + type: string + in: path + required: true + path-mergeRequestID: + name: mergeRequestID + description: GitLab Merge Request identifier + type: string + in: path + required: true + path-gitlabRepositoryID: + name: gitlabRepositoryID + type: string + description: GitLab Repository/Project identifier + in: path + required: true gerritHost: name: gerritHost description: host of the gerrit server diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go new file mode 100644 index 000000000..acd2ebeed --- /dev/null +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -0,0 +1,47 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_sign + +import ( + "context" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_sign" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/go-openapi/runtime/middleware" + "github.com/sirupsen/logrus" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) + +func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service, contributorConsoleV2Base string) { + api.GitlabSignSignRequestHandler = gitlab_sign.SignRequestHandlerFunc( + func(srp gitlab_sign.SignRequestParams) middleware.Responder { + reqID := utils.GetRequestID(srp.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) + + f := logrus.Fields{ + "functionName": "v2.gitlab_sign.handlers.GitlabSignSignRequestHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "installationID": srp.OrganizationID, + "repositoryID": srp.GitlabRepositoryID, + "mergeRequestID": srp.MergeRequestID, + } + + log.WithFields(f).Debugf("Initiating Gitlab sign request for : %+v ", srp) + + err := service.GitlabSignRequest(ctx, srp.HTTPRequest, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID, contributorConsoleV2Base, eventService) + + if err != nil { + msg := fmt.Sprintf("problem initiating sign request for :%+v", srp) + log.WithFields(f).Debugf(msg) + return gitlab_sign.NewSignRequestBadRequest().WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + return gitlab_sign.NewSignRequestOK() + }) +} diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go new file mode 100644 index 000000000..5edc8e2f1 --- /dev/null +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -0,0 +1,190 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab_sign + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/sirupsen/logrus" + + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + v2Gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/users" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" + "github.com/communitybridge/easycla/cla-backend-go/v2/store" + "github.com/xanzy/go-gitlab" +) + +type service struct { + repoService repositories.ServiceInterface + gitlabOrgRepo gitlab_organizations.RepositoryInterface + userService users.Service + gitlabApp v2Gitlab.App + storeRepo store.Repository +} + +type Service interface { + GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) error +} + +func NewService(gitlabRepositoryService repositories.ServiceInterface, gitlabOrgRepository gitlab_organizations.RepositoryInterface, userService users.Service, storeRepo store.Repository) Service { + return &service{ + repoService: gitlabRepositoryService, + gitlabOrgRepo: gitlabOrgRepository, + userService: userService, + storeRepo: storeRepo, + } +} + +func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) error { + f := logrus.Fields{ + "functionName": "v2.gitlab_sign.service.GitlabSignRequest", + "organizationID": organizationID, + "repositoryID": repositoryID, + "mergeRequestID": mergeRequestID, + } + + organization, err := s.gitlabOrgRepo.GetGitlabOrganization(ctx, organizationID) + if err != nil { + log.WithFields(f).Debugf("unable to get gitlab organiztion by ID: %s, error: %+v ", organizationID, err) + return nil + } + + if organization.AuthInfo == "" { + msg := fmt.Sprintf("organization: %s has no auth details", organizationID) + log.WithFields(f).Debug(msg) + return nil + } + gitlabClient, err := v2Gitlab.NewGitlabOauthClient(organization.AuthInfo, &s.gitlabApp) + if err != nil { + log.WithFields(f).Debugf("initializaing gitlab client for gitlab org: %s failed: %v", organizationID, err) + return nil + } + + mergeRequestIDInt, err := strconv.Atoi(mergeRequestID) + if err != nil { + log.WithFields(f).Debugf("unable to convert organization string value : %s to Int", organizationID) + return err + } + + log.WithFields(f).Debug("Determining return URL from the inbound request ...") + mergeRequest, _, err := gitlabClient.MergeRequests.GetMergeRequest(repositoryID, mergeRequestIDInt, &gitlab.GetMergeRequestsOptions{}) + if err != nil || mergeRequest == nil { + log.WithFields(f).Debugf("unable to fetch MR Web URL: mergeRequestID: %s ", mergeRequestID) + return err + } + + originURL := mergeRequest.WebURL + log.WithFields(f).Debugf("Return URL from the inbound request is : %s ", originURL) + + err = s.redirectToConsole(ctx, req, gitlabClient, repositoryID, mergeRequestID, originURL, contributorConsoleV2Base, eventService) + if err != nil { + log.WithFields(f).Debug("unable to redirect to contributor console") + return err + } + + return nil +} + +func (s service) redirectToConsole(ctx context.Context, req *http.Request, gitlabClient *gitlab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) error { + f := logrus.Fields{ + "functionName": "v2.gitlab_sign.service.redirectToConsole", + "repositoryID": repositoryID, + "mergeRequestID": mergeRequestID, + "originURL": originURL, + } + + claUser, err := s.getOrCreateUser(ctx, gitlabClient, eventService) + if err != nil { + msg := fmt.Sprintf("unable to get or create user : %+v ", err) + log.WithFields(f).Warn(msg) + return err + } + + gitlabRepo, err := s.repoService.GitHubGetRepository(ctx, repositoryID) + if err != nil { + msg := fmt.Sprintf("unable to find repository by ID: %s , error: %+v ", repositoryID, err) + log.WithFields(f).Warn(msg) + return err + } + + // set active signature metadata to track the user signing process + key := fmt.Sprintf("active_signature:%s", claUser.UserID) + var value map[string]string + value["user_id"] = claUser.UserID + value["project_id"] = gitlabRepo.RepositoryClaGroupID + value["repository_id"] = repositoryID + value["merge_request_id"] = mergeRequestID + expire := time.Now().AddDate(0, 0, 1).Unix() + + jsonVal, _ := json.Marshal(value) + + err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, jsonVal) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to save signature metadata") + } + + params := "redirect=" + url.QueryEscape(originURL) + consoleURL := fmt.Sprintf("https://%s/#/cla/project/%s/user/%s?%s", contributorBaseURL, gitlabRepo.RepositoryClaGroupID, claUser.UserID, params) + _, err = http.Get(consoleURL) + + if err != nil { + msg := fmt.Sprintf("unable to redirect to : %s , error: %+v ", consoleURL, err) + log.WithFields(f).Warn(msg) + return err + } + + return nil +} + +func (s service) getOrCreateUser(ctx context.Context, gitlabClient *gitlab.Client, eventsService events.Service) (*models.User, error) { + + f := logrus.Fields{ + "functionName": "v2.gitlab_sign.service.getOrCreateUser", + } + + gitlabUser, _, err := gitlabClient.Users.CurrentUser() + if err != nil { + log.WithFields(f).Debugf("getting gitlab current user for failed : %v ", err) + return nil, err + } + + claUser, err := s.userService.GetUserByGitlabID(gitlabUser.ID) + if err != nil { + log.WithFields(f).Debugf("unable to get CLA user by github ID: %d , error: %+v ", gitlabUser.ID, err) + log.WithFields(f).Infof("creating user record for gitlab user : %+v ", gitlabUser) + user := &models.User{ + GitlabID: fmt.Sprintf("%d", gitlabUser.ID), + GitlabUsername: gitlabUser.Username, + Emails: []string{gitlabUser.Email}, + } + claUser, userErr := s.userService.CreateUser(user, nil) + if err != nil { + log.WithFields(f).Debugf("unable to create claUser with details : %+v, error: %+v", user, userErr) + return nil, userErr + } + + // Log the event + eventsService.LogEvent(&events.LogEventArgs{ + EventType: events.UserCreated, + UserID: user.UserID, + UserModel: user, + EventData: &events.UserCreatedEventData{}, + }) + return claUser, nil + + } + + return claUser, nil + +} diff --git a/cla-backend-go/v2/store/repository.go b/cla-backend-go/v2/store/repository.go new file mode 100644 index 000000000..64785faad --- /dev/null +++ b/cla-backend-go/v2/store/repository.go @@ -0,0 +1,82 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package store + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/communitybridge/easycla/cla-backend-go/utils" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) + +//DBStore represents DB Model for the store table +type DBStore struct { + Key string + Value []byte + Expire int64 +} + +// Repository interface +type Repository interface { + SetActiveSignatureMetaData(ctx context.Context, key string, expire int64, value []byte) error +} + +type repo struct { + stage string + dynamoDBClient *dynamodb.DynamoDB + storeTableName string +} + +//NewRepository initiates Store repository instance +func NewRepository(awsSession *session.Session, stage string) Repository { + return repo{ + stage: stage, + dynamoDBClient: dynamodb.New(awsSession), + storeTableName: fmt.Sprintf("cla-%s-store", stage), + } +} + +// SetActiveSignatureMetaData sets active signature meta data +func (r repo) SetActiveSignatureMetaData(ctx context.Context, key string, expire int64, value []byte) error { + f := logrus.Fields{ + "functionName": "v2.store.repository.SetActiveSignatureMetaData", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "key": key, + "value": value, + "expire": expire, + } + + store := DBStore{ + Key: key, + Value: value, + Expire: expire, + } + + v, err := dynamodbattribute.MarshalMap(store) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem marshalling store record") + return err + } + + _, err = r.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ + Item: v, + TableName: &r.storeTableName, + }) + + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to save store record") + return err + } + + log.WithFields(f).Debugf("Signature meta record data saved: %+v ", store) + + return nil +} From 2c34f4a4888d94118b78e14bf415f192075004a4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 18 Aug 2021 17:24:59 -0700 Subject: [PATCH 0433/1276] Updated GitLab Add Repo to CLA Group API (#3169) --- .../cmd/dynamo_events_lambda/main.go | 2 +- cla-backend-go/cmd/server.go | 16 +- cla-backend-go/gitlab/organization.go | 78 -------- cla-backend-go/{gitlab => gitlab_api}/auth.go | 0 .../{gitlab => gitlab_api}/client.go | 10 +- cla-backend-go/gitlab_api/client_groups.go | 139 ++++++++++++++ cla-backend-go/gitlab_api/client_projects.go | 114 ++++++++++++ .../{gitlab => gitlab_api}/client_test.go | 0 cla-backend-go/{gitlab => gitlab_api}/init.go | 0 cla-backend-go/{gitlab => gitlab_api}/mr.go | 0 .../{gitlab => gitlab_api}/webhook.go | 0 cla-backend-go/swagger/cla.v1.yaml | 6 +- cla-backend-go/swagger/cla.v2.yaml | 38 ++-- ...n.yaml => github-organization-create.yaml} | 0 ...n.yaml => github-organization-update.yaml} | 0 ...ies.yaml => github-repositories-list.yaml} | 0 .../swagger/common/gitlab-add-repository.yaml | 51 ------ ...n.yaml => gitlab-organization-create.yaml} | 0 ...n.yaml => gitlab-organization-update.yaml} | 0 .../swagger/common/gitlab-organization.yaml | 3 + .../common/gitlab-repositories-add.yaml | 22 +++ ...ies.yaml => gitlab-repositories-list.yaml} | 0 cla-backend-go/tests/gitlab_client_test.go | 14 +- cla-backend-go/utils/constants.go | 3 + .../v2/dynamo_events/gitlab_webhooks.go | 16 +- cla-backend-go/v2/dynamo_events/service.go | 6 +- cla-backend-go/v2/gitlab-activity/service.go | 20 +- .../v2/gitlab_organizations/handlers.go | 12 +- .../v2/gitlab_organizations/service.go | 58 +++--- cla-backend-go/v2/gitlab_sign/service.go | 6 +- .../v2/repositories/gitlab_services.go | 171 ++++++++++-------- cla-backend-go/v2/repositories/handlers.go | 44 ++--- cla-backend-go/v2/repositories/repository.go | 54 ++---- cla-backend-go/v2/repositories/service.go | 47 +++-- 34 files changed, 540 insertions(+), 390 deletions(-) delete mode 100644 cla-backend-go/gitlab/organization.go rename cla-backend-go/{gitlab => gitlab_api}/auth.go (100%) rename cla-backend-go/{gitlab => gitlab_api}/client.go (94%) create mode 100644 cla-backend-go/gitlab_api/client_groups.go create mode 100644 cla-backend-go/gitlab_api/client_projects.go rename cla-backend-go/{gitlab => gitlab_api}/client_test.go (100%) rename cla-backend-go/{gitlab => gitlab_api}/init.go (100%) rename cla-backend-go/{gitlab => gitlab_api}/mr.go (100%) rename cla-backend-go/{gitlab => gitlab_api}/webhook.go (100%) rename cla-backend-go/swagger/common/{github-create-organization.yaml => github-organization-create.yaml} (100%) rename cla-backend-go/swagger/common/{github-update-organization.yaml => github-organization-update.yaml} (100%) rename cla-backend-go/swagger/common/{github-list-repositories.yaml => github-repositories-list.yaml} (100%) delete mode 100644 cla-backend-go/swagger/common/gitlab-add-repository.yaml rename cla-backend-go/swagger/common/{gitlab-create-organization.yaml => gitlab-organization-create.yaml} (100%) rename cla-backend-go/swagger/common/{gitlab-update-organization.yaml => gitlab-organization-update.yaml} (100%) create mode 100644 cla-backend-go/swagger/common/gitlab-repositories-add.yaml rename cla-backend-go/swagger/common/{gitlab-list-repositories.yaml => gitlab-repositories-list.yaml} (100%) diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 3b3b8a3c5..7226e2699 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -10,7 +10,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" - "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index a6888892d..2d38fed75 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -20,7 +20,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" - "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_sign" "github.com/communitybridge/easycla/cla-backend-go/emails" @@ -56,7 +56,7 @@ import ( lfxAuth "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/docs" - "github.com/communitybridge/easycla/cla-backend-go/repositories" + v1Repositories "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/communitybridge/easycla/cla-backend-go/utils" v2Docs "github.com/communitybridge/easycla/cla-backend-go/v2/docs" v2Events "github.com/communitybridge/easycla/cla-backend-go/v2/events" @@ -244,7 +244,7 @@ func server(localMode bool) http.Handler { // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) usersRepo := users.NewRepository(awsSession, stage) - gitV1Repository := repositories.NewRepository(awsSession, stage) + gitV1Repository := v1Repositories.NewRepository(awsSession, stage) gitV2Repository := v2Repositories.NewRepository(awsSession, stage) gerritRepo := gerrits.NewRepository(awsSession, stage) templateRepo := template.NewRepository(awsSession, stage) @@ -300,8 +300,8 @@ func server(localMode bool) http.Handler { v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation) v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, v1ProjectClaGroupRepo, signaturesRepo, usersService) v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, v1ProjectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) - v1RepositoriesService := repositories.NewService(gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo) - v2RepositoriesService := v2Repositories.NewService(gitV1Repository, gitV2Repository, v1ProjectClaGroupRepo, githubOrganizationsRepo) + v1RepositoriesService := v1Repositories.NewService(gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo) + v2RepositoriesService := v2Repositories.NewService(gitV1Repository, gitV2Repository, v1ProjectClaGroupRepo, githubOrganizationsRepo, gitlabOrganizationRepo, eventsService) v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, v1ProjectClaGroupRepo) v1ApprovalListService := approval_list.NewService(approvalListRepo, v1ProjectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, v1CLAGroupRepo, signaturesRepo, emailTemplateService, configFile.CorporateConsoleV2URL, http.DefaultClient) authorizer := auth.NewAuthorizer(authValidator, userRepo) @@ -349,13 +349,11 @@ func server(localMode bool) http.Handler { v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) - gitlab_organizations.Configure(v2API, gitlabOrganizationsService, v2RepositoriesService, eventsService) + gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) gitlab_sign.Configure(v2API, gitlabSignService, eventsService, configFile.CLAContributorv2Base) gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) - repositories.Configure(api, v1RepositoriesService, eventsService) + v1Repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) - gitlab_organizations.Configure(v2API, gitlabOrganizationsService, v2RepositoriesService, eventsService) - gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) v2Gerrits.Configure(v2API, gerritService, v1ProjectService, eventsService, v1ProjectClaGroupRepo) v2Company.Configure(v2API, v2CompanyService, v1ProjectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleV1URL) diff --git a/cla-backend-go/gitlab/organization.go b/cla-backend-go/gitlab/organization.go deleted file mode 100644 index f09b096dc..000000000 --- a/cla-backend-go/gitlab/organization.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -package gitlab - -import ( - "fmt" - - log "github.com/communitybridge/easycla/cla-backend-go/logging" - - "github.com/xanzy/go-gitlab" -) - -// UserGroup represents gitlab group -type UserGroup struct { - Name string - FullPath string -} - -// GetGroupByName gets a gitlab Group by the given name -func GetGroupByName(client *gitlab.Client, name string) (*gitlab.Group, error) { - groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{}) - if err != nil { - return nil, fmt.Errorf("fetching groups failed : %v", err) - } - - for _, group := range groups { - if group.Name == name { - return group, nil - } - } - - return nil, nil -} - -// ListUserProjectGroups fetches the unique groups of a gitlab users groups, -// note: it doesn't list the projects/groups the user is member of ..., it's very limited -func ListUserProjectGroups(client *gitlab.Client, userID int) ([]*UserGroup, error) { - listOptions := &gitlab.ListProjectsOptions{ - ListOptions: gitlab.ListOptions{ - PerPage: 100, - }} - - userGroupsMap := map[string]*UserGroup{} - for { - log.Debugf("fetching projects for user id : %d with options : %v", userID, listOptions.ListOptions) - projects, resp, err := client.Projects.ListUserProjects(userID, listOptions) - if err != nil { - return nil, fmt.Errorf("listing user : %d projects failed : %v", userID, err) - } - log.Printf("fetched %d projects for the user ", len(projects)) - - if len(projects) == 0 { - break - } - - for _, p := range projects { - log.Debugf("checking following project : %s", p.PathWithNamespace) - log.Debugf("fetched following namespace : %+v", p.Namespace) - userGroupsMap[p.Namespace.FullPath] = &UserGroup{ - Name: p.Namespace.Name, - FullPath: p.Namespace.FullPath, - } - } - - if listOptions.Page >= resp.NextPage { - break - } - listOptions.Page = resp.NextPage - } - - var userGroups []*UserGroup - for _, v := range userGroupsMap { - userGroups = append(userGroups, v) - } - - return userGroups, nil -} diff --git a/cla-backend-go/gitlab/auth.go b/cla-backend-go/gitlab_api/auth.go similarity index 100% rename from cla-backend-go/gitlab/auth.go rename to cla-backend-go/gitlab_api/auth.go diff --git a/cla-backend-go/gitlab/client.go b/cla-backend-go/gitlab_api/client.go similarity index 94% rename from cla-backend-go/gitlab/client.go rename to cla-backend-go/gitlab_api/client.go index 145a9e73c..f976eb857 100644 --- a/cla-backend-go/gitlab/client.go +++ b/cla-backend-go/gitlab_api/client.go @@ -15,7 +15,7 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/xanzy/go-gitlab" + goGitLab "github.com/xanzy/go-gitlab" ) // OauthSuccessResponse is success response from Gitlab @@ -28,19 +28,19 @@ type OauthSuccessResponse struct { } // NewGitlabOauthClient creates a new gitlab client from the given oauth info, authInfo is encrypted -func NewGitlabOauthClient(authInfo string, gitLabApp *App) (*gitlab.Client, error) { +func NewGitlabOauthClient(authInfo string, gitLabApp *App) (*goGitLab.Client, error) { oauthResp, err := DecryptAuthInfo(authInfo, gitLabApp) if err != nil { return nil, err } log.Infof("creating oauth client with access token : %s", oauthResp.AccessToken) - return gitlab.NewOAuthClient(oauthResp.AccessToken) + return goGitLab.NewOAuthClient(oauthResp.AccessToken) } // NewGitlabOauthClientFromAccessToken creates a new gitlab client from the given access token -func NewGitlabOauthClientFromAccessToken(accessToken string) (*gitlab.Client, error) { - return gitlab.NewOAuthClient(accessToken) +func NewGitlabOauthClientFromAccessToken(accessToken string) (*goGitLab.Client, error) { + return goGitLab.NewOAuthClient(accessToken) } // EncryptAuthInfo encrypts the oauth response into a string diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go new file mode 100644 index 000000000..7f2dda5d7 --- /dev/null +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -0,0 +1,139 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "context" + "errors" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + + goGitLab "github.com/xanzy/go-gitlab" +) + +// UserGroup represents gitlab group +type UserGroup struct { + Name string + FullPath string +} + +// GetGroupsListAll returns a complete list of GitLab groups for which the client as authorization/visibility +func GetGroupsListAll(ctx context.Context, client *goGitLab.Client) ([]*goGitLab.Group, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.GetGroupsListAll", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + // https://docs.gitlab.com/ee/api/groups.html#list-groups + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + listGroupsOpts := &goGitLab.ListGroupsOptions{ + ListOptions: goGitLab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, // max is 100 + }, + AllAvailable: utils.Bool(true), // Show all the groups you have access to (defaults to false for authenticated users, true for administrators); Attributes owned and min_access_level have precedence + MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. + } + + var groupList []*goGitLab.Group + for { + groups, resp, listGroupsErr := client.Groups.ListGroups(listGroupsOpts) + if listGroupsErr != nil { + msg := fmt.Sprintf("unable to list groups, error: %+v", listGroupsErr) + log.WithFields(f).WithError(listGroupsErr).Warn(msg) + return nil, errors.New(msg) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to list groups, status code: %d", resp.StatusCode) + log.WithFields(f).WithError(listGroupsErr).Warn(msg) + return nil, errors.New(msg) + } + + // Append to our response + groupList = append(groupList, groups...) + + // Do we have any records to process? + if resp.NextPage == 0 { + break + } + } + + return groupList, nil +} + +// GetGroupByName gets a gitlab Group by the given name +func GetGroupByName(ctx context.Context, client *goGitLab.Client, name string) (*goGitLab.Group, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.GetGroupByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + groups, _, err := client.Groups.ListGroups(&goGitLab.ListGroupsOptions{}) + if err != nil { + msg := fmt.Sprintf("problem fetching groups, error: %+v", err) + log.WithFields(f).WithError(err).Warn(msg) + return nil, errors.New(msg) + } + + for _, group := range groups { + if group.Name == name { + return group, nil + } + } + + return nil, nil +} + +// ListUserProjectGroups fetches the unique groups of a gitlab users groups, +// note: it doesn't list the projects/groups the user is member of ..., it's very limited +func ListUserProjectGroups(ctx context.Context, client *goGitLab.Client, userID int) ([]*UserGroup, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.ListUserProjectGroups", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + listOptions := &goGitLab.ListProjectsOptions{ + ListOptions: goGitLab.ListOptions{ + PerPage: 100, + }} + + userGroupsMap := map[string]*UserGroup{} + for { + log.WithFields(f).Debugf("fetching projects for user id : %d with options : %v", userID, listOptions.ListOptions) + projects, resp, err := client.Projects.ListUserProjects(userID, listOptions) + if err != nil { + return nil, fmt.Errorf("listing user : %d projects failed : %v", userID, err) + } + log.Debugf("fetched %d projects for the user ", len(projects)) + + if len(projects) == 0 { + break + } + + for _, p := range projects { + log.Debugf("checking following project : %s", p.PathWithNamespace) + log.Debugf("fetched following namespace : %+v", p.Namespace) + userGroupsMap[p.Namespace.FullPath] = &UserGroup{ + Name: p.Namespace.Name, + FullPath: p.Namespace.FullPath, + } + } + + if listOptions.Page >= resp.NextPage { + break + } + listOptions.Page = resp.NextPage + } + + var userGroups []*UserGroup + for _, v := range userGroupsMap { + userGroups = append(userGroups, v) + } + + return userGroups, nil +} diff --git a/cla-backend-go/gitlab_api/client_projects.go b/cla-backend-go/gitlab_api/client_projects.go new file mode 100644 index 000000000..9e7c6d654 --- /dev/null +++ b/cla-backend-go/gitlab_api/client_projects.go @@ -0,0 +1,114 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "context" + "errors" + "fmt" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + + goGitLab "github.com/xanzy/go-gitlab" +) + +// GetProjectListAll returns a complete list of GitLab projects for which the client as authorization/visibility +func GetProjectListAll(ctx context.Context, client *goGitLab.Client) ([]*goGitLab.Project, error) { + // https://docs.gitlab.com/ce/api/projects.html#list-projects + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + listProjectsOpts := &goGitLab.ListProjectsOptions{ + ListOptions: goGitLab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, // max is 100 + }, + SearchNamespaces: utils.Bool(true), // Include ancestor namespaces when matching search criteria. Default is false. + Membership: utils.Bool(true), // Limit by projects that the current user is a member of. + MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. + } + + return getProjectListWithOptions(ctx, client, listProjectsOpts) +} + +// GetProjectListByOrgName returns a list of GitLab projects under the specified Organization +func GetProjectListByOrgName(ctx context.Context, client *goGitLab.Client, organizationName string) ([]*goGitLab.Project, error) { + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + listProjectsOpts := &goGitLab.ListProjectsOptions{ + ListOptions: goGitLab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, // max is 100 + }, + Search: utils.StringRef(organizationName), // filter by our organization name + SearchNamespaces: utils.Bool(true), // Include ancestor namespaces when matching search criteria. Default is false. + Membership: utils.Bool(true), // Limit by projects that the current user is a member of. + MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. + } + + return getProjectListWithOptions(ctx, client, listProjectsOpts) +} + +// getProjectListWithOptions returns a list of GitLab projects using the specified filter +func getProjectListWithOptions(ctx context.Context, client *goGitLab.Client, opts *goGitLab.ListProjectsOptions) ([]*goGitLab.Project, error) { + f := logrus.Fields{ + "functionName": "gitlab.client.getProjectListWithOptions", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + var projectList []*goGitLab.Project + for { + // Need to use this func to get the list of projects the user has access to, see: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63811 + projects, resp, listProjectsErr := client.Projects.ListProjects(opts) + if listProjectsErr != nil { + msg := fmt.Sprintf("unable to list projects, error: %+v", listProjectsErr) + log.WithFields(f).WithError(listProjectsErr).Warn(msg) + return nil, errors.New(msg) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to list projects, status code: %d", resp.StatusCode) + log.WithFields(f).WithError(listProjectsErr).Warn(msg) + return nil, errors.New(msg) + } + + // Append to our response + projectList = append(projectList, projects...) + + // Do we have any records to process? + if resp.NextPage == 0 { + break + } + } + + return projectList, nil +} + +// GetProjectByID returns the GitLab project for the specified ID +func GetProjectByID(ctx context.Context, client *goGitLab.Client, gitLabProjectID int) (*goGitLab.Project, error) { + f := logrus.Fields{ + "functionName": "gitlab.client.GetProjectByID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabProjectID": gitLabProjectID, + } + + // Query GitLab for repos - fetch the list of repositories available to the GitLab App + project, resp, getProjectErr := client.Projects.GetProject(gitLabProjectID, &goGitLab.GetProjectOptions{}) + if getProjectErr != nil { + msg := fmt.Sprintf("unable to get project by ID: %d, error: %+v", gitLabProjectID, getProjectErr) + log.WithFields(f).WithError(getProjectErr).Warn(msg) + return nil, errors.New(msg) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to get project by ID: %d, status code: %d", gitLabProjectID, resp.StatusCode) + log.WithFields(f).WithError(getProjectErr).Warn(msg) + return nil, errors.New(msg) + } + if project == nil { + msg := fmt.Sprintf("unable to get project by ID: %d, project is empty", gitLabProjectID) + log.WithFields(f).WithError(getProjectErr).Warn(msg) + return nil, errors.New(msg) + } + + return project, nil +} diff --git a/cla-backend-go/gitlab/client_test.go b/cla-backend-go/gitlab_api/client_test.go similarity index 100% rename from cla-backend-go/gitlab/client_test.go rename to cla-backend-go/gitlab_api/client_test.go diff --git a/cla-backend-go/gitlab/init.go b/cla-backend-go/gitlab_api/init.go similarity index 100% rename from cla-backend-go/gitlab/init.go rename to cla-backend-go/gitlab_api/init.go diff --git a/cla-backend-go/gitlab/mr.go b/cla-backend-go/gitlab_api/mr.go similarity index 100% rename from cla-backend-go/gitlab/mr.go rename to cla-backend-go/gitlab_api/mr.go diff --git a/cla-backend-go/gitlab/webhook.go b/cla-backend-go/gitlab_api/webhook.go similarity index 100% rename from cla-backend-go/gitlab/webhook.go rename to cla-backend-go/gitlab_api/webhook.go diff --git a/cla-backend-go/swagger/cla.v1.yaml b/cla-backend-go/swagger/cla.v1.yaml index 5b57cca9d..b94962ddb 100644 --- a/cla-backend-go/swagger/cla.v1.yaml +++ b/cla-backend-go/swagger/cla.v1.yaml @@ -2857,7 +2857,7 @@ definitions: $ref: './common/create-cla-group-template.yaml' update-github-organization: - $ref: './common/github-update-organization.yaml' + $ref: './common/github-organization-update.yaml' template-pdfs: $ref: './common/template-pdfs.yaml' @@ -3104,7 +3104,7 @@ definitions: $ref: './common/github-organizations.yaml' github-create-organization: - $ref: './common/github-create-organization.yaml' + $ref: './common/github-organization-create.yaml' github-organization: $ref: './common/github-organization.yaml' @@ -3113,7 +3113,7 @@ definitions: $ref: './common/github-repository-info.yaml' github-list-repositories: - $ref: './common/github-list-repositories.yaml' + $ref: './common/github-repositories-list.yaml' org-list: $ref: './common/org-list.yaml' diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 3a6dcad53..05ee68c21 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1707,7 +1707,7 @@ paths: - in: body name: body schema: - $ref: '#/definitions/gitlab-update-organization' + $ref: '#/definitions/gitlab-organization-update' required: true responses: '200': @@ -1777,9 +1777,9 @@ paths: type: string required: true - in: body - name: gitlab-add-repository + name: gitlab-repositories-add schema: - $ref: '#/definitions/gitlab-add-repository' + $ref: '#/definitions/gitlab-repositories-add' required: true responses: '200': @@ -1789,7 +1789,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/gitlab-repository' + $ref: '#/definitions/gitlab-repositories-list' '400': $ref: '#/responses/invalid-request' '401': @@ -1825,7 +1825,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/gitlab-list-repositories' + $ref: '#/definitions/gitlab-repositories-list' '400': $ref: '#/responses/invalid-request' '401': @@ -4000,7 +4000,7 @@ paths: $ref: '#/responses/internal-server-error' tags: - gitlab-activity - + /repository-provider/gitlab/sign/{organizationID}/{gitlabRepositoryID}/{mergeRequestID}: get: summary: Gitlab sign request handler @@ -4478,13 +4478,13 @@ definitions: $ref: './common/github-repository.yaml' github-create-organization: - $ref: './common/github-create-organization.yaml' + $ref: './common/github-organization-create.yaml' github-update-organization: - $ref: './common/github-update-organization.yaml' + $ref: './common/github-organization-update.yaml' github-list-repositories: - $ref: './common/github-list-repositories.yaml' + $ref: './common/github-repositories-list.yaml' # --------------------------------------------------------------------------- # GitLab Definitions @@ -4492,20 +4492,20 @@ definitions: gitlab-organization: $ref: './common/gitlab-organization.yaml' - gitlab-repository: - $ref: './common/gitlab-repository.yaml' - gitlab-create-organization: - $ref: './common/gitlab-create-organization.yaml' + $ref: './common/gitlab-organization-create.yaml' - gitlab-update-organization: - $ref: './common/github-update-organization.yaml' + gitlab-organization-update: + $ref: './common/gitlab-organization-update.yaml' + + gitlab-repository: + $ref: './common/gitlab-repository.yaml' - gitlab-list-repositories: - $ref: './common/gitlab-list-repositories.yaml' + gitlab-repositories-list: + $ref: './common/gitlab-repositories-list.yaml' - gitlab-add-repository: - $ref: './common/gitlab-add-repository.yaml' + gitlab-repositories-add: + $ref: './common/gitlab-repositories-add.yaml' # --------------------------------------------------------------------------- # CLA Group Definitions diff --git a/cla-backend-go/swagger/common/github-create-organization.yaml b/cla-backend-go/swagger/common/github-organization-create.yaml similarity index 100% rename from cla-backend-go/swagger/common/github-create-organization.yaml rename to cla-backend-go/swagger/common/github-organization-create.yaml diff --git a/cla-backend-go/swagger/common/github-update-organization.yaml b/cla-backend-go/swagger/common/github-organization-update.yaml similarity index 100% rename from cla-backend-go/swagger/common/github-update-organization.yaml rename to cla-backend-go/swagger/common/github-organization-update.yaml diff --git a/cla-backend-go/swagger/common/github-list-repositories.yaml b/cla-backend-go/swagger/common/github-repositories-list.yaml similarity index 100% rename from cla-backend-go/swagger/common/github-list-repositories.yaml rename to cla-backend-go/swagger/common/github-repositories-list.yaml diff --git a/cla-backend-go/swagger/common/gitlab-add-repository.yaml b/cla-backend-go/swagger/common/gitlab-add-repository.yaml deleted file mode 100644 index f3a2d115d..000000000 --- a/cla-backend-go/swagger/common/gitlab-add-repository.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT - -type: object -required: - - repository_external_id - - repository_project_sfid - - repository_cla_group_id - - repository_name - - repository_full_path - - repository_organization_name - - repository_url -properties: - repository_external_id: - type: integer - description: The repository ID from the external service, such as GitHub or GitLab - minimum: 1 - example: 7 - repository_project_sfid: - description: Project SFID - $ref: './common/properties/external-id.yaml' - repository_cla_group_id: - description: CLA Group ID - $ref: './common/properties/internal-id.yaml' - repository_name: - type: string - description: The repository name - minLength: 3 - example: 'easycla-test-repo-4' - repository_full_path: - type: string - description: The repository full path - minLength: 3 - example: 'linuxfoundation/product/easycla/easycla-test-repo-4' - repository_organization_name: - type: string - description: The organization name associated with this repository - minLength: 3 - example: 'The Linux Foundation/product/EasyCLA' - repository_url: - type: string - description: The external repository URL - minLength: 8 - example: 'https://gitlab.com/linuxfoundation/product/easycla/easycla-test-repo-4' - enabled: - type: boolean - description: Flag to indicate if this repository is enabled or not. Repositories may become disabled if they have been moved or deleted from GitHub or GitLab. - x-omitempty: false - note: - description: optional note added to the record - type: string diff --git a/cla-backend-go/swagger/common/gitlab-create-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization-create.yaml similarity index 100% rename from cla-backend-go/swagger/common/gitlab-create-organization.yaml rename to cla-backend-go/swagger/common/gitlab-organization-create.yaml diff --git a/cla-backend-go/swagger/common/gitlab-update-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization-update.yaml similarity index 100% rename from cla-backend-go/swagger/common/gitlab-update-organization.yaml rename to cla-backend-go/swagger/common/gitlab-organization-update.yaml diff --git a/cla-backend-go/swagger/common/gitlab-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization.yaml index 5e407708a..faecadbee 100644 --- a/cla-backend-go/swagger/common/gitlab-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization.yaml @@ -51,6 +51,9 @@ properties: auto_enabled_cla_group_id: type: string description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. + auth_info: + type: string + description: auth info gitlab_info: type: object properties: diff --git a/cla-backend-go/swagger/common/gitlab-repositories-add.yaml b/cla-backend-go/swagger/common/gitlab-repositories-add.yaml new file mode 100644 index 000000000..fa1e32043 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-repositories-add.yaml @@ -0,0 +1,22 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +required: + - gitlab_organization_name + - cla_group_id +properties: + repository_gitlab_ids: + type: array + items: + description: the repository external identifier, such as the GitLab ID of the repository + type: integer + minimum: 1 + example: 7 + gitlab_organization_name: + type: string + description: The organization name associated with this repository + example: 'The Linux Foundation/product/EasyCLA' + cla_group_id: + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' diff --git a/cla-backend-go/swagger/common/gitlab-list-repositories.yaml b/cla-backend-go/swagger/common/gitlab-repositories-list.yaml similarity index 100% rename from cla-backend-go/swagger/common/gitlab-list-repositories.yaml rename to cla-backend-go/swagger/common/gitlab-repositories-list.yaml diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index 851c0b61a..46eda1e9e 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -16,7 +16,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" - gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/stretchr/testify/assert" "github.com/xanzy/go-gitlab" ) @@ -51,10 +51,10 @@ func TestGitLabGetGroup(t *testing.T) { // no lint config := ini.GetConfig() // Create a new GitLab App client instance - gitLabApp := gitlab2.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) // Create a new client - gitLabClient, err := gitlab2.NewGitlabOauthClient(accessInfo, gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(accessInfo, gitLabApp) assert.Nil(t, err, "GitLab OAuth Client Error is Nil") assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") @@ -93,10 +93,10 @@ func TestGitLabListGroups(t *testing.T) { // no lint config := ini.GetConfig() // Create a new GitLab App client instance - gitLabApp := gitlab2.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) // Create a new client - gitLabClient, err := gitlab2.NewGitlabOauthClient(accessInfo, gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(accessInfo, gitLabApp) assert.Nil(t, err, "GitLab OAuth Client Error is Nil") assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") @@ -147,10 +147,10 @@ func TestGitLabListProjects(t *testing.T) { // no lint config := ini.GetConfig() // Create a new GitLab App client instance - gitLabApp := gitlab2.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) // Create a new client - gitLabClient, err := gitlab2.NewGitlabOauthClient(accessInfo, gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(accessInfo, gitLabApp) assert.Nil(t, err, "GitLab OAuth Client Error is Nil") assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index 1f29cf8bd..e3be79cca 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -60,6 +60,9 @@ const ProjectFundedSupportedByParent = "Supported by Parent Project" // XREQUESTID is the client request id - used to trace a client request through the system/logs const XREQUESTID = "x-request-id" +// CtxAuthUser the key for the authenticated user in the context +const CtxAuthUser = "authUser" + // CLAProjectManagerRole CLA project manager role identifier const CLAProjectManagerRole = "project-manager" diff --git a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go index 12e5276be..49c3160ba 100644 --- a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go +++ b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-lambda-go/events" "github.com/communitybridge/easycla/cla-backend-go/config" - "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -55,7 +55,7 @@ func (s *service) GitLabRepoAddedWebhookEventHandler(event events.DynamoDBEventR return fmt.Errorf("fetching gitlab org : %s failed : %v", newRepoModel.RepositoryOrganizationName, err) } - gitLabClient, err := gitlab.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) if err != nil { return fmt.Errorf("initializing GitLab client failed : %v", err) } @@ -66,7 +66,7 @@ func (s *service) GitLabRepoAddedWebhookEventHandler(event events.DynamoDBEventR } conf := config.GetConfig() - if err := gitlab.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { + if err := gitlab_api.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { log.WithFields(f).Errorf("adding gitlab webhook failed : %v", err) } @@ -124,7 +124,7 @@ func (s *service) GitlabRepoModifiedWebhookEventHandler(event events.DynamoDBEve return fmt.Errorf("fetching gitlab org : %s failed : %v", oldRepoModel.RepositoryOrganizationName, err) } - gitLabClient, err := gitlab.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) if err != nil { return fmt.Errorf("initializing GitLab client failed : %v", err) } @@ -137,11 +137,11 @@ func (s *service) GitlabRepoModifiedWebhookEventHandler(event events.DynamoDBEve conf := config.GetConfig() if newRepoModel.Enabled { - if err := gitlab.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { + if err := gitlab_api.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { log.WithFields(f).Errorf("adding gitlab webhook failed : %v", err) } } else { - if err := gitlab.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { + if err := gitlab_api.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { log.WithFields(f).Errorf("removing gitlab webhook failed : %v", err) } } @@ -184,7 +184,7 @@ func (s *service) GitLabRepoRemovedWebhookEventHandler(event events.DynamoDBEven return fmt.Errorf("fetching gitlab org : %s failed : %v", oldRepoModel.RepositoryOrganizationName, err) } - gitLabClient, err := gitlab.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) if err != nil { return fmt.Errorf("initializing GitLab client failed : %v", err) } @@ -195,7 +195,7 @@ func (s *service) GitLabRepoRemovedWebhookEventHandler(event events.DynamoDBEven } conf := config.GetConfig() - if err := gitlab.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { + if err := gitlab_api.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { log.WithFields(f).Errorf("removing gitlab webhook failed : %v", err) } diff --git a/cla-backend-go/v2/dynamo_events/service.go b/cla-backend-go/v2/dynamo_events/service.go index 83a27c194..ce2d01794 100644 --- a/cla-backend-go/v2/dynamo_events/service.go +++ b/cla-backend-go/v2/dynamo_events/service.go @@ -11,7 +11,7 @@ import ( "strings" "sync" - gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/gerrits" @@ -68,7 +68,7 @@ type service struct { autoEnableService *autoEnableServiceProvider claManagerRequestsRepo cla_manager.IRepository approvalListRequestsRepo approval_list.IRepository - gitLabApp *gitlab2.App + gitLabApp *gitlab_api.App } // Service implements DynamoDB stream event handler service @@ -91,7 +91,7 @@ func NewService(stage string, gerritService gerrits.Service, claManagerRequestsRepo cla_manager.IRepository, approvalListRequestsRepo approval_list.IRepository, - gitLabApp *gitlab2.App) Service { + gitLabApp *gitlab_api.App) Service { signaturesTable := fmt.Sprintf("cla-%s-signatures", stage) eventsTable := fmt.Sprintf("cla-%s-events", stage) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 222653083..6d4d61340 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -20,7 +20,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" - gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/communitybridge/easycla/cla-backend-go/signatures" @@ -58,7 +58,7 @@ type service struct { projectsCLAGroupsRepository projects_cla_groups.Repository companyRepository company.IRepository signatureRepository signatures.SignatureRepository - gitLabApp *gitlab2.App + gitLabApp *gitlab_api.App } func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRepository repositories.RepositoryInterface, gitV2Repository gitV2Repositories.RepositoryInterface, usersRepository users.UserRepository, signaturesRepository signatures.SignatureRepository, projectsCLAGroupsRepository projects_cla_groups.Repository, @@ -72,7 +72,7 @@ func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRe projectsCLAGroupsRepository: projectsCLAGroupsRepository, companyRepository: companyRepository, signatureRepository: signatureRepository, - gitLabApp: gitlab2.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), + gitLabApp: gitlab_api.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } @@ -102,12 +102,12 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git log.WithFields(f).Debugf("internal gitlab org : %s:%s is associated with external path : %s", gitlabOrg.OrganizationID, gitlabOrg.OrganizationName, repositoryPath) - gitlabClient, err := gitlab2.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + gitlabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) if err != nil { return fmt.Errorf("initializing gitlab client : %v", err) } - _, err = gitlab2.FetchMrInfo(gitlabClient, projectID, mergeID) + _, err = gitlab_api.FetchMrInfo(gitlabClient, projectID, mergeID) if err != nil { return fmt.Errorf("fetching info for mr : %d and project : %d: %s, failed : %v", mergeID, projectID, projectName, err) } @@ -119,7 +119,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git } log.WithFields(f).Debugf("internal gitlab repository found with id : %s", gitlabRepo.RepositoryID) - participants, err := gitlab2.FetchMrParticipants(gitlabClient, projectID, mergeID, true) + participants, err := gitlab_api.FetchMrParticipants(gitlabClient, projectID, mergeID, true) if err != nil { return fmt.Errorf("fetching mr participants : %v", err) } @@ -158,22 +158,22 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git mrCommentContent := PrepareMrCommentContent(missingUsers, signedUsers, signURL) if len(missingUsers) > 0 { log.WithFields(f).Errorf("mr faild with following users : %s", mrCommentContent) - if err := gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Failed, missingCLAMsg, signURL); err != nil { + if err := gitlab_api.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Failed, missingCLAMsg, signURL); err != nil { return fmt.Errorf("setting commit status failed : %v", err) } - if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Failed, mrCommentContent, signURL); err != nil { + if err := gitlab_api.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Failed, mrCommentContent, signURL); err != nil { return fmt.Errorf("setting comment failed : %v", err) } return nil } - err = gitlab2.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Success, signedCLAMsg, "") + err = gitlab_api.SetCommitStatus(gitlabClient, projectID, lastCommitSha, gitlab.Success, signedCLAMsg, "") if err != nil { return fmt.Errorf("setting commit status failed : %v", err) } - if err := gitlab2.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Success, mrCommentContent, signURL); err != nil { + if err := gitlab_api.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Success, mrCommentContent, signURL); err != nil { return fmt.Errorf("setting comment failed : %v", err) } return err diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index e46897e1b..1fa4d713b 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -15,7 +15,7 @@ import ( project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" - "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/gofrs/uuid" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -29,12 +29,11 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/utils" - v2GitRepo "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" "github.com/go-openapi/runtime/middleware" ) // Configure setups handlers on api with service -func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Service v2GitRepo.ServiceInterface, eventService events.Service) { +func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventService events.Service) { api.GitlabOrganizationsGetProjectGitlabOrganizationsHandler = gitlab_organizations.GetProjectGitlabOrganizationsHandlerFunc( func(params gitlab_organizations.GetProjectGitlabOrganizationsParams, authUser *auth.User) middleware.Responder { @@ -293,7 +292,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Servic } // now fetch the oauth credentials and store to db - oauthResp, err := gitlab.FetchOauthCredentials(params.Code) + oauthResp, err := gitlab_api.FetchOauthCredentials(params.Code) if err != nil { msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) log.WithFields(f).Errorf(msg) @@ -316,11 +315,6 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, gitV2Servic return NewServerError(reqID, "", errors.New(msg)) } - _, err = gitV2Service.GitLabAddRepositoriesByApp(ctx, updatedGitLabOrgDBModel) - if err != nil { - return NewServerError(reqID, updatedGitLabOrgDBModel.OrganizationName, err) - } - return NewSuccessResponse(reqID, updatedGitLabOrgDBModel.ProjectSFID, updatedGitLabOrgDBModel.OrganizationName) }) } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 387f5a879..be847e2c9 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -17,13 +17,12 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/common" "github.com/communitybridge/easycla/cla-backend-go/config" - gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/go-openapi/strfmt" project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" - "github.com/communitybridge/easycla/cla-backend-go/gitlab" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -41,7 +40,7 @@ type ServiceInterface interface { GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error - UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error + UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error } @@ -50,7 +49,7 @@ type Service struct { repo RepositoryInterface v2GitRepoService repositories.ServiceInterface claGroupRepository projects_cla_groups.Repository - gitLabApp *gitlab.App + gitLabApp *gitlab_api.App } // NewService creates a new gitlab organization service @@ -59,7 +58,7 @@ func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceI repo: repo, v2GitRepoService: v2GitRepoService, claGroupRepository: claGroupRepository, - gitLabApp: gitlab.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), + gitLabApp: gitlab_api.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } @@ -238,7 +237,7 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string rorg.ConnectionStatus = utils.ConnectionFailure } else { // We've been authenticated by the user - great, see if we can determine the list of repos... - glClient, clientErr := gitlab.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) + glClient, clientErr := gitlab_api.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) if clientErr != nil { log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, clientErr) rorg.ConnectionStatus = utils.ConnectionFailure @@ -297,7 +296,7 @@ func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrgani } // UpdateGitlabOrganizationAuth updates the GitLab organization authentication information -func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab.OauthSuccessResponse) error { +func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.UpdateGitlabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -305,7 +304,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani } log.WithFields(f).Debugf("updating gitlab org auth") - authInfoEncrypted, err := gitlab.EncryptAuthInfo(oauthResp, s.gitLabApp) + authInfoEncrypted, err := gitlab_api.EncryptAuthInfo(oauthResp, s.gitLabApp) if err != nil { return fmt.Errorf("encrypt failed : %v", err) } @@ -316,30 +315,37 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani } // Get the client - gitLabClient, err := gitlab2.NewGitlabOauthClientFromAccessToken(oauthResp.AccessToken) + gitLabClient, err := gitlab_api.NewGitlabOauthClientFromAccessToken(oauthResp.AccessToken) if err != nil { return fmt.Errorf("initializing gitlab client : %v", err) } - // Need to look up the GitLab Group/Organization to obtain the ID - //groups, resp, searchErr := gitLabClient.Groups.SearchGroup(gitLabOrgModel.OrganizationName) - // Need to look up the GitLab Group/Organization to obtain the ID - opts := &goGitLab.ListGroupsOptions{ - ListOptions: goGitLab.ListOptions{ - Page: 1, - PerPage: 100, - }, - } - groups, resp, searchErr := gitLabClient.Groups.ListGroups(opts) - if searchErr != nil { - return fmt.Errorf("GitLab search error while locating Group by name: %s, error: %v", gitLabOrgModel.OrganizationName, searchErr) - } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return fmt.Errorf("unable to locate GitLab group by name: %s, status code: %d", gitLabOrgModel.OrganizationName, resp.StatusCode) + // Query the groups list + groups, groupListErr := gitlab_api.GetGroupsListAll(ctx, gitLabClient) + if groupListErr != nil { + return groupListErr } + for _, g := range groups { if g.Name == gitLabOrgModel.OrganizationName { - return s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) + updateGitLabOrgErr := s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) + if updateGitLabOrgErr != nil { + return updateGitLabOrgErr + } + + log.WithFields(f).Debugf("fetching updated GitLab group/organization record") + updatedDBModel, getErr := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + if getErr != nil { + return getErr + } + + log.WithFields(f).Debugf("adding GitLab repositories for this group/organization") + _, err = s.v2GitRepoService.GitLabAddRepositoriesByApp(ctx, updatedDBModel) + if err != nil { + return err + } + + return nil } } @@ -403,7 +409,7 @@ func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI return &installationURL } -func toGitLabProjectResponse(gitLabListRepos *models.GitlabListRepositories) []*models.GitlabProjectRepository { +func toGitLabProjectResponse(gitLabListRepos *models.GitlabRepositoriesList) []*models.GitlabProjectRepository { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.toGitLabProjectResponse", } diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go index 5edc8e2f1..5cd106464 100644 --- a/cla-backend-go/v2/gitlab_sign/service.go +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -16,7 +16,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" - v2Gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" @@ -29,7 +29,7 @@ type service struct { repoService repositories.ServiceInterface gitlabOrgRepo gitlab_organizations.RepositoryInterface userService users.Service - gitlabApp v2Gitlab.App + gitlabApp gitlab_api.App storeRepo store.Repository } @@ -65,7 +65,7 @@ func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organ log.WithFields(f).Debug(msg) return nil } - gitlabClient, err := v2Gitlab.NewGitlabOauthClient(organization.AuthInfo, &s.gitlabApp) + gitlabClient, err := gitlab_api.NewGitlabOauthClient(organization.AuthInfo, &s.gitlabApp) if err != nil { log.WithFields(f).Debugf("initializaing gitlab client for gitlab org: %s failed: %v", organizationID, err) return nil diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 56b810e86..1ba103000 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -5,30 +5,88 @@ package repositories import ( "context" + "errors" "fmt" "strconv" + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/communitybridge/easycla/cla-backend-go/events" + v2GitLabOrg "github.com/communitybridge/easycla/cla-backend-go/v2/common" - gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" - "github.com/xanzy/go-gitlab" - "github.com/sirupsen/logrus" v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" repoModels "github.com/communitybridge/easycla/cla-backend-go/repositories" ) -// GitLabAddRepository service function -func (s *Service) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) { - dbModel, err := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, input) +// GitLabAddRepositories service function +func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesAdd) (*v2Models.GitlabRepositoriesList, error) { + f := logrus.Fields{ + "functionName": "v2.repositories.gitlab_services.GitLabAddRepositories", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "organizationName": utils.StringValue(input.GitlabOrganizationName), + "claGroupID": utils.StringValue(input.ClaGroupID), + } + + gitLabOrgModel, orgErr := s.glOrgRepo.GetGitlabOrganizationByName(ctx, utils.StringValue(input.GitlabOrganizationName)) + if orgErr != nil { + msg := fmt.Sprintf("problem loading gitlab organization by name: %s, error: %v", utils.StringValue(input.GitlabOrganizationName), orgErr) + log.WithFields(f).WithError(orgErr).Warn(msg) + return nil, errors.New(msg) + } + + // Get the client + gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) if err != nil { - return nil, err + return nil, fmt.Errorf("initializing gitlab client : %v", err) } - return dbModelToGitLabRepository(dbModel) + for _, gitLabProjectID := range input.RepositoryGitlabIds { + project, getProjectErr := gitlab_api.GetProjectByID(ctx, gitLabClient, int(gitLabProjectID)) // ok to down-cast as the IDs are not 64 bit + if getProjectErr != nil { + return nil, fmt.Errorf("unable to load project by ID: %d, error: %v", int(gitLabProjectID), getProjectErr) + } + + // Convert int to string + repositoryExternalIDString := strconv.Itoa(project.ID) + + inputDBModel := &repoModels.RepositoryDBModel{ + RepositorySfdcID: projectSFID, + ProjectSFID: projectSFID, + RepositoryExternalID: repositoryExternalIDString, + RepositoryName: project.Name, + RepositoryFullPath: project.PathWithNamespace, + RepositoryURL: project.WebURL, + RepositoryOrganizationName: utils.StringValue(input.GitlabOrganizationName), // gitlab group/organization + RepositoryCLAGroupID: utils.StringValue(input.ClaGroupID), + RepositoryType: utils.GitLabLower, // should always be gitlab + Enabled: true, + } + + _, addErr := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, inputDBModel) + if addErr != nil { + log.WithFields(f).WithError(addErr).Warnf("problem adding GitLab repository with name: %s, error: %+v", project.PathWithNamespace, addErr) + return nil, addErr + } + + // Log the event + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryAdded, + ProjectSFID: projectSFID, + CLAGroupID: utils.StringValue(input.ClaGroupID), + LfUsername: ctx.Value(utils.CtxAuthUser).(*auth.User).UserName, + EventData: &events.RepositoryAddedEventData{ + RepositoryName: project.PathWithNamespace, // give the full path/name + }, + }) + } + + return s.GitLabGetRepositoriesByOrganizationName(ctx, utils.StringValue(input.GitlabOrganizationName)) } // GitLabAddRepositoriesByApp adds the GitLab repositories based on the application credentials @@ -39,8 +97,9 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel "projectSFID": gitLabOrgModel.ProjectSFID, "organizationName": gitLabOrgModel.OrganizationName, } + // Get the client - gitlabClient, err := gitlab2.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) if err != nil { return nil, fmt.Errorf("initializing gitlab client : %v", err) } @@ -51,65 +110,29 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel return nil, fmt.Errorf("unable to locate Project CLAGroup using projectSFID: %s for GitLab repositories group ID: %d, error: %+v", gitLabOrgModel.ProjectSFID, gitLabOrgModel.ExternalGroupID, projectCLAGroupLookupErr) } - user, resp, userErr := gitlabClient.Users.CurrentUser() - if userErr != nil { - return nil, fmt.Errorf("unable to locate current user, error: %+v", userErr) - } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return nil, fmt.Errorf("unable to locate current user, status code: %d", resp.StatusCode) - } - - log.WithFields(f).Debugf("fetched current username: %s with name: %s with email: %s", user.Username, user.Name, user.PublicEmail) - - // Query GitLab for repos - fetch the list of repositories available to the GitLab App - listProjectsOpts := &gitlab.ListProjectsOptions{ - ListOptions: gitlab.ListOptions{ - Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination - PerPage: 100, // max is 100 - }, - Search: utils.StringRef(gitLabOrgModel.OrganizationName), // filter by our organization - SearchNamespaces: utils.Bool(true), - Membership: utils.Bool(true), - MinAccessLevel: gitlab.AccessLevel(gitlab.MaintainerPermissions), - } - - // TODO - DAD - loop until no more repos, could be more than 100 - log.WithFields(f).Debugf("searching for GitLab projects based on the search critera: %s", gitLabOrgModel.OrganizationName) - // Need to use this func to get the list of projects the user has access to, see: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63811 - projects, resp, listProjectsErr := gitlabClient.Projects.ListProjects(listProjectsOpts) - //projects, resp, listProjectsErr := gitlabClient.Projects.ListUserProjects(user.ID, listProjectsOpts) - if listProjectsErr != nil { - return nil, fmt.Errorf("unable to list projects for current user, error: %+v", listProjectsErr) - } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return nil, fmt.Errorf("unable to list projects for current user, status code: %d", resp.StatusCode) - } - - // Add repos to table - for _, proj := range projects { - log.WithFields(f).Debugf("Repository: %s, path: %s, id: %d", proj.Name, proj.Path, proj.ID) - - // TODO - make sure we don't have duplicates? - /* - Name: "easycla-test-repo-demo-1", - NameWithNamespace: "The Linux Foundation / product / EasyCLA / Demo / easycla-test-repo-demo-1", - Path: "easycla-test-repo-demo-1", - PathWithNamespace: "linuxfoundation/product/easycla/demo/easycla-test-repo-demo-1", - */ - _, addRepoErr := s.GitLabAddRepository(ctx, gitLabOrgModel.ProjectSFID, &v2Models.GitlabAddRepository{ - Enabled: false, // default is false - Note: fmt.Sprintf("Added during onboarding of organization: %s", gitLabOrgModel.OrganizationName), - RepositoryExternalID: utils.Int64(int64(proj.ID)), - RepositoryName: utils.StringRef(proj.Name), - RepositoryFullPath: utils.StringRef(proj.PathWithNamespace), - RepositoryOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), - RepositoryProjectSfid: utils.StringRef(gitLabOrgModel.ProjectSFID), - RepositoryURL: utils.StringRef(proj.WebURL), - RepositoryClaGroupID: utils.StringRef(projectCLAGroupModel.ClaGroupID), - }) - if addRepoErr != nil { - return nil, addRepoErr - } + // Query the project list by organization name + projectList, projectListErr := gitlab_api.GetProjectListByOrgName(ctx, gitLabClient, gitLabOrgModel.OrganizationName) + if projectListErr != nil { + return nil, projectListErr + } + + var listProjectIDs []int64 + + // Build a list of project IDs + for _, proj := range projectList { + log.WithFields(f).Debugf("repo: %s, path: %s, id: %d, weburl: %s", proj.Name, proj.Path, proj.ID, proj.WebURL) + listProjectIDs = append(listProjectIDs, int64(proj.ID)) + } + + // Build input to the add function + input := &v2Models.GitlabRepositoriesAdd{ + ClaGroupID: utils.StringRef(projectCLAGroupModel.ClaGroupID), + GitlabOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), + RepositoryGitlabIds: listProjectIDs, + } + _, addRepoErr := s.GitLabAddRepositories(ctx, gitLabOrgModel.ProjectSFID, input) + if addRepoErr != nil { + return nil, addRepoErr } // Return the list of repos to caller @@ -141,7 +164,7 @@ func (s *Service) GitLabGetRepositoryByName(ctx context.Context, repositoryName } // GitLabGetRepositoriesByProjectSFID service function -func (s *Service) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabListRepositories, error) { +func (s *Service) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabRepositoriesList, error) { dbModel, err := s.gitV2Repository.GitHubGetRepositoriesByProjectSFID(ctx, projectSFID) if err != nil { return nil, err @@ -152,13 +175,13 @@ func (s *Service) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projec return nil, err } - return &v2Models.GitlabListRepositories{ + return &v2Models.GitlabRepositoriesList{ List: responses, }, nil } // GitLabGetRepositoriesByCLAGroup service function -func (s *Service) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) { +func (s *Service) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabRepositoriesList, error) { var dbModels []*repoModels.RepositoryDBModel var err error if enabled { @@ -175,13 +198,13 @@ func (s *Service) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupI return nil, err } - return &v2Models.GitlabListRepositories{ + return &v2Models.GitlabRepositoriesList{ List: responses, }, nil } // GitLabGetRepositoriesByOrganizationName returns the list of repositories associated with the Organization/Group name -func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabListRepositories, error) { +func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) { dbModels, err := s.gitV2Repository.GitHubGetRepositoriesByOrganizationName(ctx, orgName) if err != nil { return nil, err @@ -192,7 +215,7 @@ func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, o return nil, err } - return &v2Models.GitlabListRepositories{ + return &v2Models.GitlabRepositoriesList{ List: responses, }, nil } diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index fc1824aae..79760d51b 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -396,7 +396,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_repositories.NewGetProjectGitLabRepositoriesBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - response := &models.GitlabListRepositories{} + response := &models.GitlabRepositoriesList{} err = copier.Copy(response, result) if err != nil { msg := fmt.Sprintf("problem converting response for projectSFID: %s", params.ProjectSFID) @@ -411,19 +411,15 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params gitlab_repositories.AddProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := context.WithValue(context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID), "authUser", authUser) // nolint f := logrus.Fields{ - "functionName": "v2.repositories.handlers.GitlabRepositoriesAddProjectGitLabRepositoryHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "authUser": authUser.UserName, - "authEmail": authUser.Email, - "projectSFID": params.ProjectSFID, - "repositoryExternalID": utils.Int64Value(params.GitlabAddRepository.RepositoryExternalID), - "repositoryName": utils.StringValue(params.GitlabAddRepository.RepositoryName), - "repositoryURL": utils.StringValue(params.GitlabAddRepository.RepositoryURL), - "repositoryOrganizationName": utils.StringValue(params.GitlabAddRepository.RepositoryOrganizationName), - "repositoryCLAGroupID": utils.StringValue(params.GitlabAddRepository.RepositoryClaGroupID), - "repositoryProjectSFID": utils.StringValue(params.GitlabAddRepository.RepositoryProjectSfid), + "functionName": "v2.repositories.handlers.GitlabRepositoriesAddProjectGitLabRepositoryHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "organizationName": utils.StringValue(params.GitlabRepositoriesAdd.GitlabOrganizationName), + "claGroupID": utils.StringValue(params.GitlabRepositoriesAdd.ClaGroupID), } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { @@ -433,19 +429,16 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_repositories.NewAddProjectGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - // If no repository GitLab ID values provided... - // RepositoryGitlabID - provided by the older retool UI which provides only one value - // RepositoryGitlabIds - provided by new PCC which passes multiple values - if params.GitlabAddRepository.RepositoryExternalID == nil { - msg := "missing repository GitLab ID value" + if len(params.GitlabRepositoriesAdd.RepositoryGitlabIds) == 0 { + msg := "missing repository GitLab ID values" return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } log.WithFields(f).Debugf("Adding GitLab repository for project: %s", params.ProjectSFID) - result, err := service.GitLabAddRepository(ctx, params.ProjectSFID, params.GitlabAddRepository) + result, err := service.GitLabAddRepositories(ctx, params.ProjectSFID, params.GitlabRepositoriesAdd) if err != nil { if _, ok := err.(*utils.GitLabRepositoryExists); ok { - msg := fmt.Sprintf("unable to add repository - repository with name: %s already exists for projectSFID: %s", utils.StringValue(params.GitlabAddRepository.RepositoryName), params.ProjectSFID) + msg := fmt.Sprintf("unable to add repository - repository already exists for projectSFID: %s, err: %+v", params.ProjectSFID, err) log.WithFields(f).WithError(err).Warn(msg) return gitlab_repositories.NewAddProjectGitLabRepositoryConflict().WithXRequestID(reqID).WithPayload(utils.ErrorResponseConflictWithError(reqID, msg, err)) } @@ -454,17 +447,6 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - // Log the event - eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryAdded, - ProjectSFID: params.ProjectSFID, - CLAGroupID: utils.StringValue(params.GitlabAddRepository.RepositoryClaGroupID), - LfUsername: authUser.UserName, - EventData: &events.RepositoryAddedEventData{ - RepositoryName: utils.StringValue(params.GitlabAddRepository.RepositoryName), - }, - }) - return gitlab_repositories.NewAddProjectGitLabRepositoryOK().WithPayload(result) }) diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index f21a107d3..c246b9c7d 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -6,7 +6,6 @@ package repositories import ( "context" "fmt" - "strconv" "strings" "github.com/aws/aws-sdk-go/aws" @@ -14,7 +13,6 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/aws/aws-sdk-go/service/dynamodb/expression" - v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" repoModels "github.com/communitybridge/easycla/cla-backend-go/repositories" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -31,7 +29,7 @@ type RepositoryInterface interface { GitHubGetRepositoriesByCLAGroupDisabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) - GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*repoModels.RepositoryDBModel, error) + GitLabAddRepository(ctx context.Context, projectSFID string, input *repoModels.RepositoryDBModel) (*repoModels.RepositoryDBModel, error) GitLabEnableRepositoryByID(ctx context.Context, repositoryID string) error GitLabDisableRepositoryByID(ctx context.Context, repositoryID string) error GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error @@ -225,23 +223,23 @@ func (r *Repository) GitHubGetRepositoriesByOrganizationName(ctx context.Context } // GitLabAddRepository creates a new entry in the repositories table using the specified input parameters -func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*repoModels.RepositoryDBModel, error) { +func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string, input *repoModels.RepositoryDBModel) (*repoModels.RepositoryDBModel, error) { f := logrus.Fields{ "functionName": "v2.repositories.repositories.GitHubAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "repositoryExternalID": utils.Int64Value(input.RepositoryExternalID), - "repositoryURL": utils.StringValue(input.RepositoryURL), - "repositoryName": utils.StringValue(input.RepositoryName), - "repositoryFullPath": utils.StringValue(input.RepositoryFullPath), + "repositoryExternalID": input.RepositoryExternalID, + "repositoryURL": input.RepositoryURL, + "repositoryName": input.RepositoryName, + "repositoryFullPath": input.RepositoryFullPath, "repositoryType": utils.GitLabLower, - "repositoryCLAGroupID": utils.StringValue(input.RepositoryClaGroupID), - "repositoryProjectSFID": utils.StringValue(input.RepositoryProjectSfid), - "repositoryOrganizationName": utils.StringValue(input.RepositoryOrganizationName), + "repositoryCLAGroupID": input.RepositoryCLAGroupID, + "repositoryProjectSFID": input.RepositorySfdcID, + "repositoryOrganizationName": input.RepositoryOrganizationName, } // Check first to see if the repository already exists - _, err := r.GitLabGetRepositoryByName(ctx, utils.StringValue(input.RepositoryName)) + _, err := r.GitLabGetRepositoryByName(ctx, input.RepositoryName) if err != nil { // Expecting Not found - no issue if not found - all other error we throw if _, ok := err.(*utils.GitLabRepositoryNotFound); !ok { @@ -249,7 +247,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string } } else { return nil, &utils.GitLabRepositoryExists{ - Message: fmt.Sprintf("GitLab repository with name: %s has alerady been registered", utils.StringValue(input.RepositoryName)), + Message: fmt.Sprintf("GitLab repository with name: %s has alerady been registered", input.RepositoryName), RepositoryName: "", Err: nil, } @@ -261,27 +259,13 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string return nil, err } - // Convert int64* to string - repositoryExternalIDString := strconv.FormatInt(utils.Int64Value(input.RepositoryExternalID), 10) - - repository := &repoModels.RepositoryDBModel{ - RepositoryID: repoID.String(), // internal ID that we assign - RepositorySfdcID: projectSFID, - ProjectSFID: projectSFID, - DateCreated: currentTime, - DateModified: currentTime, - RepositoryExternalID: repositoryExternalIDString, - RepositoryName: utils.StringValue(input.RepositoryName), - RepositoryFullPath: utils.StringValue(input.RepositoryFullPath), - RepositoryURL: utils.StringValue(input.RepositoryURL), - RepositoryOrganizationName: utils.StringValue(input.RepositoryOrganizationName), // gitlab group/organization - RepositoryCLAGroupID: utils.StringValue(input.RepositoryClaGroupID), - RepositoryType: utils.GitLabLower, // should always be gitlab - Enabled: input.Enabled, // default is enabled - Note: fmt.Sprintf("created on %s", currentTime), - Version: "v1", - } - av, err := dynamodbattribute.MarshalMap(repository) + input.RepositoryID = repoID.String() + input.DateCreated = currentTime + input.DateModified = currentTime + input.Note = fmt.Sprintf("created on %s", currentTime) + input.Version = "v1" + + av, err := dynamodbattribute.MarshalMap(input) if err != nil { log.WithFields(f).Warnf("problem marshalling the input, error: %+v", err) return nil, err @@ -296,7 +280,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string return nil, err } - return repository, nil + return input, nil } // GitLabEnableRepositoryByID enables the specified repository diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index bf4619c2e..34cf4225c 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -9,10 +9,13 @@ import ( "fmt" "strconv" + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/github_organizations" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" "github.com/communitybridge/easycla/cla-backend-go/config" - gitlab2 "github.com/communitybridge/easycla/cla-backend-go/gitlab" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" @@ -52,21 +55,25 @@ type ServiceInterface interface { GitLabGetRepository(ctx context.Context, repositoryID string) (*v2Models.GitlabRepository, error) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*v2Models.GitlabRepository, error) - GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabListRepositories, error) - GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabListRepositories, error) - GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabListRepositories, error) - GitLabAddRepository(ctx context.Context, projectSFID string, input *v2Models.GitlabAddRepository) (*v2Models.GitlabRepository, error) + GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabRepositoriesList, error) + GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabRepositoriesList, error) + GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) + GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesAdd) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitLabOrganization) ([]*v2Models.GitlabRepository, error) GitLabEnableRepository(ctx context.Context, repositoryID string) error GitLabDisableRepository(ctx context.Context, repositoryID string) error GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error } -// GithubOrgRepo provide method to get GitHub organization by name +// GithubOrgRepo redefine the interface here to avoid circular dependency issues type GithubOrgRepo interface { - GetGitHubOrganizationByName(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganizations, error) - GetGitHubOrganization(ctx context.Context, githubOrganizationName string) (*v1Models.GithubOrganization, error) - GetGitHubOrganizations(ctx context.Context, projectSFID string) (*v1Models.GithubOrganizations, error) + AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *v2Models.GitlabCreateOrganization) (*v2Models.GitlabOrganization, error) + GetGitlabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) + GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) + GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) + UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error + UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error + DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } // Service is the service model/structure @@ -74,8 +81,10 @@ type Service struct { gitV1Repository v1Repositories.RepositoryInterface gitV2Repository RepositoryInterface projectsClaGroupsRepo projects_cla_groups.Repository - ghOrgRepo GithubOrgRepo - gitLabApp *gitlab2.App + ghOrgRepo github_organizations.RepositoryInterface + glOrgRepo GithubOrgRepo + gitLabApp *gitlab_api.App + eventService events.Service } var ( @@ -85,13 +94,15 @@ var ( ) // NewService creates a new githubOrganizations service -func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository RepositoryInterface, pcgRepo projects_cla_groups.Repository, ghOrgRepo GithubOrgRepo) ServiceInterface { +func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository RepositoryInterface, pcgRepo projects_cla_groups.Repository, ghOrgRepo github_organizations.RepositoryInterface, glOrgRepo GithubOrgRepo, eventService events.Service) ServiceInterface { return &Service{ gitV1Repository: gitV1Repository, gitV2Repository: gitV2Repository, projectsClaGroupsRepo: pcgRepo, ghOrgRepo: ghOrgRepo, - gitLabApp: gitlab2.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), + glOrgRepo: glOrgRepo, + eventService: eventService, + gitLabApp: gitlab_api.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } @@ -143,9 +154,6 @@ func (s *Service) GitHubAddRepositories(ctx context.Context, projectSFID string, log.WithFields(f).WithError(err).Warn("unable to get organization by name") return nil, err } - if len(org.List) == 0 { - return nil, errors.New("github app not installed on github organization") - } // Updated to process a list of repository IDs - take the list (may be empty) and add the single repository GH ID if it was set repositoryIDList := input.RepositoryGithubIds @@ -551,13 +559,16 @@ func (s *Service) getBranchProtectionRepositoryForOrgName(ctx context.Context, g "githubOrgName": githubOrgName, } - githubOrg, err := s.ghOrgRepo.GetGitHubOrganization(ctx, githubOrgName) + githubOrg, err := s.ghOrgRepo.GetGitHubOrganizationByName(ctx, githubOrgName) if err != nil { log.WithFields(f).Warnf("fetching githubOrg %s failed, error: %v", githubOrgName, err) return nil, err } + if len(githubOrg.List) == 0 { + return nil, errors.New("github app not installed on github organization") + } - branchProtectionRepo, err := branch_protection.NewBranchProtectionRepository(githubOrg.OrganizationInstallationID, branch_protection.EnableNonBlockingLimiter()) + branchProtectionRepo, err := branch_protection.NewBranchProtectionRepository(githubOrg.List[0].OrganizationInstallationID, branch_protection.EnableNonBlockingLimiter()) if err != nil { return nil, err } From 482e3eceae0d8fb19fa0af5b42c7441261115f46 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 18 Aug 2021 18:58:11 -0700 Subject: [PATCH 0434/1276] Added Context Helper Functions (#3170) - Added helper functions for adding authUser to the context and fetching the values Signed-off-by: David Deal --- cla-backend-go/tests/utils_context_test.go | 38 ++++++++++++++ cla-backend-go/utils/context.go | 52 +++++++++++++++++++ .../v2/gitlab_organizations/handlers.go | 9 ++-- .../v2/repositories/gitlab_services.go | 3 +- cla-backend-go/v2/repositories/handlers.go | 17 +++--- 5 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 cla-backend-go/tests/utils_context_test.go diff --git a/cla-backend-go/tests/utils_context_test.go b/cla-backend-go/tests/utils_context_test.go new file mode 100644 index 000000000..70f4c864d --- /dev/null +++ b/cla-backend-go/tests/utils_context_test.go @@ -0,0 +1,38 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "context" + "testing" + + "github.com/LF-Engineering/lfx-kit/auth" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +// TestGetUserNameFromContext is a test for the GetUserNameFromContext +func TestGetUserNameFromContext(t *testing.T) { + reqID := "foo123" + authUser := &auth.User{ + UserName: "ddeal1", + Email: "ddeal1@foo.com", + ACL: auth.ACL{}, + } + ctx := utils.ContextWithRequestAndUser(context.Background(), reqID, authUser) // nolint + assert.Equal(t, "ddeal1", utils.GetUserNameFromContext(ctx)) +} + +// TestGetUserEmailFromContext is a test for the GetUserNameFromContext +func TestGetUserEmailFromContext(t *testing.T) { + reqID := "foo566" + authUser := &auth.User{ + UserName: "ddeal2", + Email: "ddeal2@foo.com", + ACL: auth.ACL{}, + } + ctx := utils.ContextWithRequestAndUser(context.Background(), reqID, authUser) // nolint + assert.Equal(t, "ddeal2@foo.com", utils.GetUserEmailFromContext(ctx)) +} diff --git a/cla-backend-go/utils/context.go b/cla-backend-go/utils/context.go index e2283ffe1..e1be4a955 100644 --- a/cla-backend-go/utils/context.go +++ b/cla-backend-go/utils/context.go @@ -6,6 +6,8 @@ package utils import ( "context" + "github.com/LF-Engineering/lfx-kit/auth" + "github.com/sirupsen/logrus" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -25,3 +27,53 @@ func NewContext() context.Context { return context.WithValue(context.Background(), XREQUESTID, requestID.String()) // nolint } + +// NewContextWithUser returns a new context with a newly generated request ID and the specified user +func NewContextWithUser(authUser *auth.User) context.Context { + f := logrus.Fields{ + "functionName": "utils.NewContextWithUser", + } + requestID, err := uuid.NewV4() + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to generate a UUID for x-request-id") + return context.Background() + } + + return context.WithValue(context.WithValue(context.Background(), XREQUESTID, requestID), "authUser", authUser) // nolint +} + +// ContextWithRequestAndUser returns a new context with the specified request ID and user +func ContextWithRequestAndUser(ctx context.Context, reqID string, authUser *auth.User) context.Context { + return context.WithValue(context.WithValue(ctx, XREQUESTID, reqID), "authUser", authUser) // nolint +} + +// ContextWithUser returns a new context with the specified user +func ContextWithUser(ctx context.Context, authUser *auth.User) context.Context { + return context.WithValue(ctx, "authUser", authUser) // nolint +} + +// GetUserNameFromContext returns the user's name from the context +func GetUserNameFromContext(ctx context.Context) string { + val := ctx.Value(CtxAuthUser) + if val != nil { + authUser := val.(*auth.User) // nolint + if authUser != nil { + return authUser.UserName + } + } + + return "" +} + +// GetUserEmailFromContext returns the user's email from the context +func GetUserEmailFromContext(ctx context.Context) string { + val := ctx.Value(CtxAuthUser) + if val != nil { + authUser := val.(*auth.User) // nolint + if authUser != nil { + return authUser.Email + } + } + + return "" +} diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 1fa4d713b..55e4dbdbd 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -4,7 +4,6 @@ package gitlab_organizations import ( - "context" "errors" "fmt" "net/http" @@ -39,7 +38,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params gitlab_organizations.GetProjectGitlabOrganizationsParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsGetProjectGitlabOrganizationsHandler", @@ -87,7 +86,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params gitlab_organizations.AddProjectGitlabOrganizationParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsAddProjectGitlabOrganizationHandler", @@ -162,7 +161,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic api.GitlabOrganizationsUpdateProjectGitlabOrganizationConfigHandler = gitlab_organizations.UpdateProjectGitlabOrganizationConfigHandlerFunc(func(params gitlab_organizations.UpdateProjectGitlabOrganizationConfigParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint if params.Body.AutoEnabled == nil { return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ Code: "400", @@ -202,7 +201,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic api.GitlabOrganizationsDeleteProjectGitlabOrganizationHandler = gitlab_organizations.DeleteProjectGitlabOrganizationHandlerFunc(func(params gitlab_organizations.DeleteProjectGitlabOrganizationParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsDeleteProjectGitlabOrganizationHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 1ba103000..90d2210cf 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -9,7 +9,6 @@ import ( "fmt" "strconv" - "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/events" v2GitLabOrg "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -79,7 +78,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, EventType: events.RepositoryAdded, ProjectSFID: projectSFID, CLAGroupID: utils.StringValue(input.ClaGroupID), - LfUsername: ctx.Value(utils.CtxAuthUser).(*auth.User).UserName, + LfUsername: utils.GetUserNameFromContext(ctx), EventData: &events.RepositoryAddedEventData{ RepositoryName: project.PathWithNamespace, // give the full path/name }, diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 79760d51b..b919a9c37 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -4,7 +4,6 @@ package repositories import ( - "context" "errors" "fmt" "strings" @@ -36,7 +35,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params github_repositories.GetProjectGithubRepositoriesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitHubRepositoriesGetProjectGithubRepositoriesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -84,7 +83,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params github_repositories.AddProjectGithubRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitHubRepositoriesAddProjectGithubRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -166,7 +165,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params github_repositories.DeleteProjectGithubRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitHubRepositoriesDeleteProjectGithubRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -224,7 +223,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params github_repositories.GetProjectGithubRepositoryBranchProtectionParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitHubRepositoriesGetProjectGithubRepositoryBranchProtectionHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -285,7 +284,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params github_repositories.UpdateProjectGithubRepositoryBranchProtectionParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitHubRepositoriesUpdateProjectGitHubRepositoryBranchProtectionHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -367,7 +366,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params gitlab_repositories.GetProjectGitLabRepositoriesParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitlabRepositoriesGetProjectGitLabRepositoriesHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -411,7 +410,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params gitlab_repositories.AddProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID), "authUser", authUser) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitlabRepositoriesAddProjectGitLabRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -454,7 +453,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic func(params gitlab_repositories.DeleteProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ "functionName": "v2.repositories.handlers.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), From 8739e342d2c87f92e94d9d33b2cd665ef73b0cc6 Mon Sep 17 00:00:00 2001 From: Mike Dolan Date: Thu, 19 Aug 2021 13:45:22 -0400 Subject: [PATCH 0435/1276] Prioritize link visually in email to CLA Managers (#3121) Co-authored-by: David Deal --- cla-backend-go/emails/v2_cla_manager_templates.go | 2 +- cla-backend-go/tests/v2_cla_manager_templates_test.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/emails/v2_cla_manager_templates.go b/cla-backend-go/emails/v2_cla_manager_templates.go index db23d5730..28b91acd0 100644 --- a/cla-backend-go/emails/v2_cla_manager_templates.go +++ b/cla-backend-go/emails/v2_cla_manager_templates.go @@ -139,7 +139,7 @@ const (
  • {{.Project.ExternalProjectName}}
  • Before the contribution can be accepted, your organization must sign a CLA. -Either you or someone whom you designate from your company can login to this portal ({{.CorporateConsole}}) and sign the CLA for this project {{.Project.GetProjectFullURL}}

    +Either you or someone whom you designate from your company can login and sign the CLA for this project {{.Project.GetProjectFullURL}}

    If you are not the CLA Manager, please forward this email to the appropriate person so that they can start the CLA process.

    Please notify the user once CLA setup is complete.

    ` diff --git a/cla-backend-go/tests/v2_cla_manager_templates_test.go b/cla-backend-go/tests/v2_cla_manager_templates_test.go index 21c655640..d22994121 100644 --- a/cla-backend-go/tests/v2_cla_manager_templates_test.go +++ b/cla-backend-go/tests/v2_cla_manager_templates_test.go @@ -138,8 +138,7 @@ func TestV2CLAManagerDesigneeCorporateTemplate(t *testing.T) { assert.Contains(t, result, "SenderNameValue SenderEmailValue has identified you") assert.Contains(t, result, "Corporate CLA for the organization JohnsCompany") assert.Contains(t, result, "
  • JohnsProject
  • ") - assert.Contains(t, result, "can login to this portal (http://CorporateConsole.com)") - assert.Contains(t, result, `sign the CLA for this project JohnsProject`) + assert.Contains(t, result, "can login and sign the CLA for this project JohnsProject") } func TestV2ToCLAManagerDesigneeTemplate(t *testing.T) { From b41dc6cc54ba4b16ea545caf19895fb89b7c686e Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 14:00:42 -0700 Subject: [PATCH 0436/1276] Updated GitLab Onboarding API (#3172) --- cla-backend-go/gitlab_api/client.go | 12 ++ cla-backend-go/gitlab_api/client_groups.go | 85 +++++++- cla-backend-go/gitlab_api/client_projects.go | 2 +- .../common/gitlab-organization-create.yaml | 31 +-- .../common/gitlab-project-organization.yaml | 5 + cla-backend-go/tests/gitlab_client_test.go | 122 +++++++++-- .../v2/gitlab_organizations/handlers.go | 29 +-- .../v2/gitlab_organizations/repository.go | 203 ++++++++++++++---- .../v2/gitlab_organizations/service.go | 27 ++- .../v2/repositories/gitlab_services.go | 6 +- cla-backend-go/v2/repositories/service.go | 6 +- 11 files changed, 426 insertions(+), 102 deletions(-) diff --git a/cla-backend-go/gitlab_api/client.go b/cla-backend-go/gitlab_api/client.go index f976eb857..9566aefeb 100644 --- a/cla-backend-go/gitlab_api/client.go +++ b/cla-backend-go/gitlab_api/client.go @@ -10,6 +10,7 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" + "errors" "fmt" "io" @@ -29,11 +30,22 @@ type OauthSuccessResponse struct { // NewGitlabOauthClient creates a new gitlab client from the given oauth info, authInfo is encrypted func NewGitlabOauthClient(authInfo string, gitLabApp *App) (*goGitLab.Client, error) { + if authInfo == "" { + return nil, errors.New("unable to decrypt auth info - authentication info input is nil") + } + if gitLabApp == nil || gitLabApp.gitLabAppID == "" || gitLabApp.gitLabAppPrivateKey == "" || gitLabApp.gitLabAppSecret == "" { + return nil, errors.New("unable to decrypt auth info - GitLab app structure is nil or empty") + } + oauthResp, err := DecryptAuthInfo(authInfo, gitLabApp) if err != nil { return nil, err } + if oauthResp == nil { + return nil, errors.New("unable to decrypt auth info - value is nil") + } + log.Infof("creating oauth client with access token : %s", oauthResp.AccessToken) return goGitLab.NewOAuthClient(oauthResp.AccessToken) } diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go index 7f2dda5d7..c64a5f2f3 100644 --- a/cla-backend-go/gitlab_api/client_groups.go +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -73,22 +73,96 @@ func GetGroupByName(ctx context.Context, client *goGitLab.Client, name string) ( utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - groups, _, err := client.Groups.ListGroups(&goGitLab.ListGroupsOptions{}) + groups, resp, err := client.Groups.SearchGroup(name) + //groups, _, err := client.Groups.ListGroups(&goGitLab.ListGroupsOptions{}) if err != nil { msg := fmt.Sprintf("problem fetching groups, error: %+v", err) log.WithFields(f).WithError(err).Warn(msg) return nil, errors.New(msg) } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to search groups using query: %s, status code: %d", name, resp.StatusCode) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } for _, group := range groups { + log.WithFields(f).Debugf("testing %s == %s or %s", name, group.Name, group.FullPath) if group.Name == name { return group, nil } + if group.FullPath == name { + return group, nil + } } return nil, nil } +// GetGroupByID gets a gitlab Group by the given name +func GetGroupByID(ctx context.Context, client *goGitLab.Client, groupID int) (*goGitLab.Group, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.GetGroupByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + group, resp, err := client.Groups.GetGroup(groupID) + if err != nil { + msg := fmt.Sprintf("problem fetching group by ID: %d, error: %+v", groupID, err) + log.WithFields(f).WithError(err).Warn(msg) + return nil, errors.New(msg) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to find group by ID: %d, status code: %d", groupID, resp.StatusCode) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + + return group, nil +} + +// GetGroupProjectListByGroupID returns a list of GitLab projects under the specified Organization +func GetGroupProjectListByGroupID(ctx context.Context, client *goGitLab.Client, groupID int) ([]*goGitLab.Project, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.GetGroupProjectListByGroupID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + opts := &goGitLab.ListGroupProjectsOptions{ + ListOptions: goGitLab.ListOptions{ + Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination + PerPage: 100, // max is 100 + }, + IncludeSubgroups: utils.Bool(true), // Include projects in subgroups of this group. Default is false + MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. + } + + var projectList []*goGitLab.Project + for { + // https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects + projects, resp, listProjectsErr := client.Groups.ListGroupProjects(groupID, opts) + if listProjectsErr != nil { + msg := fmt.Sprintf("unable to list projects, error: %+v", listProjectsErr) + log.WithFields(f).WithError(listProjectsErr).Warn(msg) + return nil, errors.New(msg) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to list projects, status code: %d", resp.StatusCode) + log.WithFields(f).WithError(listProjectsErr).Warn(msg) + return nil, errors.New(msg) + } + + // Append to our response + projectList = append(projectList, projects...) + + // Do we have any records to process? + if resp.NextPage == 0 { + break + } + } + + return projectList, nil +} + // ListUserProjectGroups fetches the unique groups of a gitlab users groups, // note: it doesn't list the projects/groups the user is member of ..., it's very limited func ListUserProjectGroups(ctx context.Context, client *goGitLab.Client, userID int) ([]*UserGroup, error) { @@ -107,7 +181,14 @@ func ListUserProjectGroups(ctx context.Context, client *goGitLab.Client, userID log.WithFields(f).Debugf("fetching projects for user id : %d with options : %v", userID, listOptions.ListOptions) projects, resp, err := client.Projects.ListUserProjects(userID, listOptions) if err != nil { - return nil, fmt.Errorf("listing user : %d projects failed : %v", userID, err) + msg := fmt.Sprintf("listing user : %d projects failed : %v", userID, err) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + msg := fmt.Sprintf("unable to list user projects using userID: %d, status code: %d", userID, resp.StatusCode) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) } log.Debugf("fetched %d projects for the user ", len(projects)) diff --git a/cla-backend-go/gitlab_api/client_projects.go b/cla-backend-go/gitlab_api/client_projects.go index 9e7c6d654..5cb7cbf49 100644 --- a/cla-backend-go/gitlab_api/client_projects.go +++ b/cla-backend-go/gitlab_api/client_projects.go @@ -53,7 +53,7 @@ func GetProjectListByOrgName(ctx context.Context, client *goGitLab.Client, organ // getProjectListWithOptions returns a list of GitLab projects using the specified filter func getProjectListWithOptions(ctx context.Context, client *goGitLab.Client, opts *goGitLab.ListProjectsOptions) ([]*goGitLab.Project, error) { f := logrus.Fields{ - "functionName": "gitlab.client.getProjectListWithOptions", + "functionName": "gitlab_api.client_projects.getProjectListWithOptions", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } diff --git a/cla-backend-go/swagger/common/gitlab-organization-create.yaml b/cla-backend-go/swagger/common/gitlab-organization-create.yaml index 0a9ea81c4..48384010b 100644 --- a/cla-backend-go/swagger/common/gitlab-organization-create.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization-create.yaml @@ -3,25 +3,30 @@ type: object required: - - organizationName + - group_id properties: - organizationName: - type: string - description: The GitLab Group/Organization name - example: "kubernetes" - # Pattern aligns with UI and other platform services including Org Service - # \w Any word character (alphanumeric & underscore), dashes, periods - pattern: '^([\w\-\.]+){2,255}$' - minLength: 2 - maxLength: 255 - autoEnabled: +# organization_name: +# type: string +# description: The GitLab Group/Organization name +# example: "kubernetes" +# # Pattern aligns with UI and other platform services including Org Service +# # \w Any word character (alphanumeric & underscore), dashes, periods +# pattern: '^([\w\-\.]+){2,255}$' +# minLength: 2 +# maxLength: 255 + group_id: + type: integer + description: The GitLab Group ID + example: 13050017 + minimum: 1 + auto_enabled: type: boolean description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. default: false - autoEnabledClaGroupID: + auto_enabled_cla_group_id: type: string description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. - branchProtectionEnabled: + branch_protection_enabled: type: boolean description: Flag to indicate if this GitLab Group/Organization is configured to automatically setup branch protection on CLA enabled repositories. default: false diff --git a/cla-backend-go/swagger/common/gitlab-project-organization.yaml b/cla-backend-go/swagger/common/gitlab-project-organization.yaml index badacff1b..f33b7fcf5 100644 --- a/cla-backend-go/swagger/common/gitlab-project-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-project-organization.yaml @@ -39,6 +39,11 @@ properties: type: string description: The Gitlab Group/Organization full path example: "linuxfoundation/product/easycla" + organization_external_id: + type: integer + description: The Gitlab Group/Organization external ID used by GitLab + example: 13050017 + minimum: 1 connection_status: type: string enum: diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index 46eda1e9e..8303f9cbd 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -4,10 +4,8 @@ package tests import ( - "encoding/json" "fmt" "io" - "net/url" "os" "testing" @@ -21,14 +19,13 @@ import ( "github.com/xanzy/go-gitlab" ) -const enabled = false // nolint -const group = "The Linux Foundation/product/EasyCLA" +const enabled = false // nolint +const group = "The Linux Foundation/product/EasyCLA" // nolint const accessInfo = "" -const easyCLAGroupName = "linuxfoundation/product/easycla" - -func TestGitLabGetGroup(t *testing.T) { // no lint +const easyCLAGroupName = "linuxfoundation/product/easycla" // nolint +func TestGetGroupByName(t *testing.T) { // no lint if enabled { // nolint // Need to initialize the system to load the configuration which contains a number of SSM parameters stage := os.Getenv("STAGE") @@ -58,14 +55,93 @@ func TestGitLabGetGroup(t *testing.T) { // no lint assert.Nil(t, err, "GitLab OAuth Client Error is Nil") assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") + ctx := utils.NewContext() // Need to look up the GitLab Group/Organization to obtain the ID - groupModel, resp, getError := gitLabClient.Groups.GetGroup(url.QueryEscape(easyCLAGroupName)) - assert.Nil(t, getError, "GitLab GetGroup Error is Nil") - if resp.StatusCode < 200 || resp.StatusCode > 299 { - assert.Fail(t, fmt.Sprintf("unable to locate GitLab group by value: %s, status code: %d", easyCLAGroupName, resp.StatusCode)) + //groupModel, getError := gitlab_api.GetGroupByName(ctx, gitLabClient, easyCLAGroupName) + //groupModel, getError := gitlab_api.GetGroupByName(ctx, gitLabClient, "EasyCLA") + groupModel, getError := gitlab_api.GetGroupByName(ctx, gitLabClient, "linuxfoundation/product/asitha") + assert.Nil(t, getError, "GitLab GetGroup Error should be nil", getError) + assert.NotNil(t, groupModel, "Group Model should not be nil") + t.Logf("group ID: %d, name: %s, path: %s, full path: %s", groupModel.ID, groupModel.Name, groupModel.Path, groupModel.FullPath) + } +} + +func TestGetGroupByID(t *testing.T) { // no lint + if enabled { // nolint + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + + // Create a new GitLab App client instance + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + // Create a new client + gitLabClient, err := gitlab_api.NewGitlabOauthClient(accessInfo, gitLabApp) + assert.Nil(t, err, "GitLab OAuth Client Error is Nil") + assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") + + ctx := utils.NewContext() + groupModel, getError := gitlab_api.GetGroupByID(ctx, gitLabClient, 13050017) + assert.Nil(t, getError, "GitLab GetGroup Error should be nil", getError) + assert.NotNil(t, groupModel, "Group Model should not be nil") + t.Logf("group ID: %d, name: %s, path: %s, full path: %s", groupModel.ID, groupModel.Name, groupModel.Path, groupModel.FullPath) + } +} + +func TestGetGroupProjectListByGroupID(t *testing.T) { // no lint + if enabled { // nolint + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + + // Create a new GitLab App client instance + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + // Create a new client + gitLabClient, err := gitlab_api.NewGitlabOauthClient(accessInfo, gitLabApp) + assert.Nil(t, err, "GitLab OAuth Client Error is Nil") + assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") + + ctx := utils.NewContext() + gitLabProjects, getError := gitlab_api.GetGroupProjectListByGroupID(ctx, gitLabClient, 13050017) + assert.Nil(t, getError, "Get Group Projects List by Group ID error should be nil", getError) + assert.NotNil(t, gitLabProjects, "Get Group Projects Array should not be nil") + assert.Greaterf(t, len(gitLabProjects), 0, "Get Group Projects Array greater than zero: %d", len(gitLabProjects)) + for _, p := range gitLabProjects { + t.Logf("id: %d, name: %s, web url: %s, path: %s, full path: %s", p.ID, p.Name, p.WebURL, p.Path, p.PathWithNamespace) } - assert.NotNil(t, groupModel, "Group Model is not nil") - t.Logf("group name: %s, ID: %d, path: %s", groupModel.Name, groupModel.ID, groupModel.Path) } } @@ -193,18 +269,18 @@ func TestGitLabListProjects(t *testing.T) { // no lint } // DEBUG - t.Logf("Recevied %d projects", len(projects)) - for _, p := range projects { - t.Logf("project name: %s, ID: %d, path: %s", p.Name, p.ID, p.PathWithNamespace) - } + //t.Logf("Recevied %d projects", len(projects)) + //for _, p := range projects { + // t.Logf("project name: %s, ID: %d, path: %s", p.Name, p.ID, p.PathWithNamespace) + //} // DEBUG - t.Log("projects:") - for _, p := range projects { - byteArr, err := json.Marshal(p) - assert.Nil(t, err) - t.Logf("project: %s", byteArr) - } + //t.Log("projects:") + //for _, p := range projects { + //byteArr, err := json.Marshal(p) + //assert.Nil(t, err) + //t.Logf("project: %s", byteArr) + //} if len(projects) > 1 { assert.Fail(t, fmt.Sprintf("expecting > 1 result for GitLab list projects, found: %d - %+v", len(projects), projects)) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 55e4dbdbd..643824cdb 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -113,13 +113,13 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Quick check of the parameters - if params.Body == nil || params.Body.OrganizationName == nil { - msg := fmt.Sprintf("missing organization name in body: %+v", params.Body) + if params.Body == nil || params.Body.GroupID == nil { + msg := fmt.Sprintf("missing group ID in body: %+v", params.Body) log.WithFields(f).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( utils.ErrorResponseBadRequest(reqID, msg)) } - f["organizationName"] = utils.StringValue(params.Body.OrganizationName) + f["groupID"] = utils.Int64Value(params.Body.GroupID) if params.Body.AutoEnabled == nil { msg := fmt.Sprintf("missing autoEnabled name in body: %+v", params.Body) @@ -146,15 +146,20 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - // Log the event - eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - LfUsername: authUser.UserName, - EventType: events.GitlabOrganizationAdded, - ProjectSFID: params.ProjectSFID, - EventData: &events.GitlabOrganizationAddedEventData{ - GitlabOrganizationName: *params.Body.OrganizationName, - }, - }) + // Get the current group name for the event + for _, group := range result.List { + if group.OrganizationExternalID == *params.Body.GroupID { + // Log the event + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + LfUsername: authUser.UserName, + EventType: events.GitlabOrganizationAdded, + ProjectSFID: params.ProjectSFID, + EventData: &events.GitlabOrganizationAddedEventData{ + GitlabOrganizationName: group.OrganizationName, + }, + }) + } + } return gitlab_organizations.NewAddProjectGitlabOrganizationOK().WithPayload(result) }) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 09dc1ac1b..14e119f7a 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -35,12 +35,14 @@ const ( // RepositoryInterface is interface for gitlab org data model type RepositoryInterface interface { - AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) + AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) + GetGitlabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error - UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error + UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitlabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } @@ -53,45 +55,46 @@ type Repository struct { // NewRepository creates a new instance of the gitlabOrganizations repository func NewRepository(awsSession *session.Session, stage string) RepositoryInterface { - return Repository{ + return &Repository{ stage: stage, dynamoDBClient: dynamodb.New(awsSession), gitlabOrgTableName: fmt.Sprintf("cla-%s-gitlab-orgs", stage), } } -func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *models2.GitlabCreateOrganization) (*models2.GitlabOrganization, error) { - gitLabOrganizationName := utils.StringValue(input.OrganizationName) +func (repo *Repository) AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.AddGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "projectSFID": projectSFID, - "organizationName": gitLabOrganizationName, - "autoEnabled": utils.BoolValue(input.AutoEnabled), - "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), + "groupID": groupID, + "organizationName": organizationName, + "autoEnabled": autoEnabled, + "autoEnabledClaGroupID": autoEnabledClaGroupID, + "branchProtectionEnabled": branchProtectionEnabled, + "enabled": enabled, } // First, let's check to see if we have an existing gitlab organization with the same name - existingRecord, getErr := repo.GetGitlabOrganizationByName(ctx, utils.StringValue(input.OrganizationName)) + existingRecord, getErr := repo.GetGitlabOrganizationByExternalID(ctx, groupID) if getErr != nil { - log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab organization by name %s - ok to create a new record", gitLabOrganizationName) + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab organization by name %d - ok to create a new record", groupID) } if existingRecord != nil { - log.WithFields(f).Debugf("An existing GitLab organization with name %s exists in our database", gitLabOrganizationName) + log.WithFields(f).Debugf("An existing GitLab organization with name %d exists in our database", groupID) // If everything matches... if projectSFID == existingRecord.ProjectSFID { log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") - enabledFlag := true - updateErr := repo.UpdateGitlabOrganization(ctx, projectSFID, gitLabOrganizationName, - utils.BoolValue(input.AutoEnabled), input.AutoEnabledClaGroupID, utils.BoolValue(input.BranchProtectionEnabled), &enabledFlag) + updateErr := repo.UpdateGitlabOrganizationByExternalID(ctx, projectSFID, groupID, organizationName, + autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, enabled) if updateErr != nil { return nil, updateErr } // Return the updated record - if gitlabOrg, err := repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName); err != nil { + if gitlabOrg, err := repo.GetGitlabOrganizationByExternalID(ctx, groupID); err != nil { return nil, err } else { return common.ToModel(gitlabOrg), nil @@ -116,19 +119,19 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS return nil, err } - enabled := true gitlabOrg := &common.GitLabOrganization{ OrganizationID: organizationID.String(), DateCreated: currentTime, DateModified: currentTime, - OrganizationName: *input.OrganizationName, - OrganizationNameLower: strings.ToLower(*input.OrganizationName), + OrganizationName: organizationName, + OrganizationNameLower: strings.ToLower(organizationName), + ExternalGroupID: int(groupID), OrganizationSFID: parentProjectSFID, ProjectSFID: projectSFID, - Enabled: aws.BoolValue(&enabled), - AutoEnabled: aws.BoolValue(input.AutoEnabled), - AutoEnabledClaGroupID: input.AutoEnabledClaGroupID, - BranchProtectionEnabled: aws.BoolValue(input.BranchProtectionEnabled), + Enabled: enabled, + AutoEnabled: autoEnabled, + AutoEnabledClaGroupID: autoEnabledClaGroupID, + BranchProtectionEnabled: branchProtectionEnabled, AuthState: authStateNonce.String(), Version: "v1", // OrganizationURL: set later when we can authenticate to the API @@ -163,7 +166,7 @@ func (repo Repository) AddGitlabOrganization(ctx context.Context, parentProjectS } // GetGitlabOrganizations get GitLab organizations based on the project SFID -func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { +func (repo *Repository) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.GetGitlabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -219,7 +222,7 @@ func (repo Repository) GetGitlabOrganizations(ctx context.Context, projectSFID s } // GetGitlabOrganizationByName get GitLab organization by name -func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) { +func (repo *Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v1.gitlab_organizations.repository.GetGitlabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -267,8 +270,55 @@ func (repo Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOr return resultOutput[0], nil } +func (repo *Repository) GetGitlabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) { + f := logrus.Fields{ + "functionName": "v1.gitlab_organizations.repository.GetGitlabOrganizationByExternalID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabGroupID": gitLabGroupID, + } + + condition := expression.Key(GitLabOrganizationsExternalGitLabGroupIDColumn).Equal(expression.Value(gitLabGroupID)) + builder := expression.NewBuilder().WithKeyCondition(condition) + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.gitlabOrgTableName), + IndexName: aws.String(GitlabOrgLowerNameIndex), + } + + log.WithFields(f).Debugf("querying for GitLab organization by external group ID: %d...", gitLabGroupID) + results, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving gitlab_organizations using external ID = %d", gitLabGroupID) + return nil, err + } + if len(results.Items) == 0 { + log.WithFields(f).Debugf("Unable to find GitLab organization by group ID: %d - no results", gitLabGroupID) + return nil, nil + } + + var resultOutput []*common.GitLabOrganization + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) + if err != nil { + log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) + return nil, err + } + + return resultOutput[0], nil +} + // GetGitlabOrganization by organization name -func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) { +func (repo *Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.GetGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -302,7 +352,7 @@ func (repo Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiza } // UpdateGitlabOrganizationAuth updates the specified Gitlab organization oauth info -func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error { +func (repo *Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -368,7 +418,8 @@ func (repo Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organiz return nil } -func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error { +// UpdateGitlabOrganization updates the GitLab group based on the specified values +func (repo *Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.UpdateGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -396,6 +447,7 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID "#C": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), "#B": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), "#M": aws.String(GitLabOrganizationsDateModifiedColumn), + "#E": aws.String(GitLabOrganizationsEnabledColumn), } expressionAttributeValues := map[string]*dynamodb.AttributeValue{ ":a": { @@ -410,16 +462,11 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID ":m": { S: aws.String(currentTime), }, + ":e": { + BOOL: aws.Bool(enabled), + }, } - updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m" - - if enabled != nil { - expressionAttributeNames["#E"] = aws.String("enabled") - expressionAttributeValues[":e"] = &dynamodb.AttributeValue{ - BOOL: aws.Bool(*enabled), - } - updateExpression = updateExpression + ", #E = :e " - } + updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m, #E = :e" input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ @@ -443,7 +490,89 @@ func (repo Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID return nil } -func (repo Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { +// UpdateGitlabOrganizationByExternalID updates the GitLab group based on the specified values +func (repo *Repository) UpdateGitlabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { + f := logrus.Fields{ + "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationByExternalID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "groupID": groupID, + "organizationName": organizationName, + "autoEnabled": autoEnabled, + "autoEnabledClaGroupID": autoEnabledClaGroupID, + "branchProtectionEnabled": branchProtectionEnabled, + "tableName": repo.gitlabOrgTableName, + } + + _, currentTime := utils.CurrentTime() + gitlabOrg, lookupErr := repo.GetGitlabOrganizationByExternalID(ctx, groupID) + if lookupErr != nil { + log.WithFields(f).Warnf("error looking up GitLab group by ID: %d, error: %+v", groupID, lookupErr) + return lookupErr + } + if gitlabOrg == nil { + log.WithFields(f).Warn("error looking up GitLab group - no results") + return errors.New("unable to lookup GitLab group by ID") + } + + expressionAttributeNames := map[string]*string{ + "#A": aws.String(GitLabOrganizationsAutoEnabledColumn), + "#C": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), + "#B": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), + "#N": aws.String(GitLabOrganizationsOrganizationNameColumn), + "#NL": aws.String(GitLabOrganizationsOrganizationNameLowerColumn), + "#M": aws.String(GitLabOrganizationsDateModifiedColumn), + "#E": aws.String(GitLabOrganizationsEnabledColumn), + } + expressionAttributeValues := map[string]*dynamodb.AttributeValue{ + ":a": { + BOOL: aws.Bool(autoEnabled), + }, + ":c": { + S: aws.String(autoEnabledClaGroupID), + }, + ":b": { + BOOL: aws.Bool(branchProtectionEnabled), + }, + ":n": { + S: aws.String(organizationName), + }, + ":nl": { + S: aws.String(strings.ToLower(organizationName)), + }, + ":m": { + S: aws.String(currentTime), + }, + ":e": { + BOOL: aws.Bool(enabled), + }, + } + updateExpression := "SET #A = :a, #C = :c, #B = :b, #N = :n, #NL = :nl, #M = :m, #E = :e " + + input := &dynamodb.UpdateItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + GitLabOrganizationsOrganizationIDColumn: { + S: aws.String(gitlabOrg.OrganizationID), + }, + }, + ExpressionAttributeNames: expressionAttributeNames, + ExpressionAttributeValues: expressionAttributeValues, + UpdateExpression: &updateExpression, + TableName: aws.String(repo.gitlabOrgTableName), + } + + log.WithFields(f).Debugf("updating GitLab organization record: %+v", input) + _, updateErr := repo.dynamoDBClient.UpdateItem(input) + if updateErr != nil { + log.WithFields(f).Warnf("unable to update GitLab organization record, error: %+v", updateErr) + return updateErr + } + + return nil +} + +// DeleteGitlabOrganization deletes the specified GitLab organization +func (repo *Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { f := logrus.Fields{ "functionName": "v1.gitlab_organizations.repository.DeleteGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index be847e2c9..adac34e2b 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -70,7 +70,7 @@ func (s *Service) AddGitlabOrganization(ctx context.Context, projectSFID string, "projectSFID": projectSFID, "autoEnabled": utils.BoolValue(input.AutoEnabled), "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), - "organizationName": utils.StringValue(input.OrganizationName), + "groupID": utils.Int64Value(input.GroupID), } psc := v2ProjectService.GetClient() @@ -90,8 +90,17 @@ func (s *Service) AddGitlabOrganization(ctx context.Context, projectSFID string, f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") - log.WithFields(f).Debug("adding gitlab organization...") - resp, err := s.repo.AddGitlabOrganization(ctx, parentProjectSFID, projectSFID, input) + log.WithFields(f).Debug("adding GitLab organization...") + autoEnabled := false + if input.AutoEnabled != nil { + autoEnabled = utils.BoolValue(input.AutoEnabled) + } + branchProtectionEnabled := false + if input.BranchProtectionEnabled != nil { + branchProtectionEnabled = utils.BoolValue(input.BranchProtectionEnabled) + } + + resp, err := s.repo.AddGitlabOrganization(ctx, parentProjectSFID, projectSFID, *input.GroupID, "", autoEnabled, input.AutoEnabledClaGroupID, branchProtectionEnabled, true) if err != nil { log.WithFields(f).WithError(err).Warn("problem adding gitlab organization for project") return nil, err @@ -314,7 +323,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani return fmt.Errorf("gitlab organization lookup error: %+v", err) } - // Get the client + // Get a reference to the GItLab client gitLabClient, err := gitlab_api.NewGitlabOauthClientFromAccessToken(oauthResp.AccessToken) if err != nil { return fmt.Errorf("initializing gitlab client : %v", err) @@ -327,20 +336,20 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani } for _, g := range groups { - if g.Name == gitLabOrgModel.OrganizationName { + if g.FullPath == gitLabOrgModel.OrganizationFullPath { updateGitLabOrgErr := s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) if updateGitLabOrgErr != nil { return updateGitLabOrgErr } - log.WithFields(f).Debugf("fetching updated GitLab group/organization record") - updatedDBModel, getErr := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + log.WithFields(f).Debugf("fetching updated GitLab group/organization record which should now have all the details") + updatedOrgDBModel, getErr := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) if getErr != nil { return getErr } log.WithFields(f).Debugf("adding GitLab repositories for this group/organization") - _, err = s.v2GitRepoService.GitLabAddRepositoriesByApp(ctx, updatedDBModel) + _, err = s.v2GitRepoService.GitLabAddRepositoriesByApp(ctx, updatedOrgDBModel) if err != nil { return err } @@ -361,7 +370,7 @@ func (s *Service) UpdateGitlabOrganization(ctx context.Context, projectSFID stri } } - return s.repo.UpdateGitlabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, nil) + return s.repo.UpdateGitlabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) } // DeleteGitlabOrganization deletes the specified GitLab organization diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 90d2210cf..7229a5fd0 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -64,7 +64,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, RepositoryOrganizationName: utils.StringValue(input.GitlabOrganizationName), // gitlab group/organization RepositoryCLAGroupID: utils.StringValue(input.ClaGroupID), RepositoryType: utils.GitLabLower, // should always be gitlab - Enabled: true, + Enabled: false, // we don't enable by default } _, addErr := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, inputDBModel) @@ -110,7 +110,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel } // Query the project list by organization name - projectList, projectListErr := gitlab_api.GetProjectListByOrgName(ctx, gitLabClient, gitLabOrgModel.OrganizationName) + projectList, projectListErr := gitlab_api.GetGroupProjectListByGroupID(ctx, gitLabClient, gitLabOrgModel.ExternalGroupID) if projectListErr != nil { return nil, projectListErr } @@ -119,7 +119,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel // Build a list of project IDs for _, proj := range projectList { - log.WithFields(f).Debugf("repo: %s, path: %s, id: %d, weburl: %s", proj.Name, proj.Path, proj.ID, proj.WebURL) + log.WithFields(f).Debugf("id: %d, repo: %s, path: %s, full path: %s, weburl: %s", proj.ID, proj.Name, proj.Path, proj.PathWithNamespace, proj.WebURL) listProjectIDs = append(listProjectIDs, int64(proj.ID)) } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 34cf4225c..c01224d08 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -67,12 +67,14 @@ type ServiceInterface interface { // GithubOrgRepo redefine the interface here to avoid circular dependency issues type GithubOrgRepo interface { - AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, input *v2Models.GitlabCreateOrganization) (*v2Models.GitlabOrganization, error) + AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) + GetGitlabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error - UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled *bool) error + UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitlabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } From 032707bc9fec24f10d3531118b4cb8adacf2cbfe Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 14:59:41 -0700 Subject: [PATCH 0437/1276] Addeed GitLab External ID to Response & Index Update (#3173) --- .../swagger/common/gitlab-organization.yaml | 5 ++ cla-backend-go/v2/common/models.go | 26 ++++----- .../v2/gitlab_organizations/handlers.go | 18 +++---- .../v2/gitlab_organizations/repository.go | 14 ++--- .../v2/gitlab_organizations/service.go | 53 ++++++++++--------- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/cla-backend-go/swagger/common/gitlab-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization.yaml index faecadbee..1f577200f 100644 --- a/cla-backend-go/swagger/common/gitlab-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization.yaml @@ -6,6 +6,11 @@ properties: organization_id: type: string description: internal id of the gitlab organization + organization_external_id: + type: integer + description: The Gitlab Group/Organization external ID used by GitLab + example: 13050017 + minimum: 1 date_created: type: string example: "2020-02-06T09:31:49.245630+0000" diff --git a/cla-backend-go/v2/common/models.go b/cla-backend-go/v2/common/models.go index 2e284076c..6fbd36291 100644 --- a/cla-backend-go/v2/common/models.go +++ b/cla-backend-go/v2/common/models.go @@ -31,19 +31,19 @@ type GitLabOrganization struct { // ToModel converts to models.GitlabOrganization func ToModel(in *GitLabOrganization) *models2.GitlabOrganization { return &models2.GitlabOrganization{ - OrganizationID: in.OrganizationID, - DateCreated: in.DateCreated, - DateModified: in.DateModified, - OrganizationName: in.OrganizationName, - OrganizationFullPath: in.OrganizationFullPath, - OrganizationURL: in.OrganizationURL, - OrganizationSfid: in.OrganizationSFID, - Version: in.Version, - Enabled: in.Enabled, - AutoEnabled: in.AutoEnabled, - AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, - ProjectSfid: in.ProjectSFID, - // Not exposing ExternalGroupID + OrganizationID: in.OrganizationID, + DateCreated: in.DateCreated, + DateModified: in.DateModified, + OrganizationName: in.OrganizationName, + OrganizationFullPath: in.OrganizationFullPath, + OrganizationURL: in.OrganizationURL, + OrganizationSfid: in.OrganizationSFID, + Version: in.Version, + Enabled: in.Enabled, + AutoEnabled: in.AutoEnabled, + AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, + ProjectSfid: in.ProjectSFID, + OrganizationExternalID: int64(in.ExternalGroupID), } } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 643824cdb..66d8e1ff9 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -268,20 +268,20 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic reqID := requestID.String() if params.Code == "" { msg := "missing code parameter" - log.WithFields(f).Errorf(msg) + log.WithFields(f).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } if params.State == "" { msg := "missing state parameter" - log.WithFields(f).Errorf(msg) + log.WithFields(f).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } codeParts := strings.Split(params.State, ":") if len(codeParts) != 2 { msg := fmt.Sprintf("invalid state variable passed : %s", params.State) - log.WithFields(f).Errorf(msg) + log.WithFields(f).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } @@ -291,7 +291,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic gitLabOrg, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) if err != nil { msg := fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err) - log.WithFields(f).Errorf(msg) + log.WithFields(f).WithError(err).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } @@ -299,15 +299,15 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic oauthResp, err := gitlab_api.FetchOauthCredentials(params.Code) if err != nil { msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) - log.WithFields(f).Errorf(msg) + log.WithFields(f).WithError(err).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } log.WithFields(f).Debugf("oauth resp is like : %+v", oauthResp) - err = service.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) - if err != nil { - msg := fmt.Sprintf("updating gitlab credentials failed : %s : %v", gitlabOrganizationID, err) - log.WithFields(f).Errorf(msg) + updateErr := service.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) + if updateErr != nil { + msg := fmt.Sprintf("installation of GitLab Group and Repositories, error: %v", updateErr) + log.WithFields(f).WithError(updateErr).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 14e119f7a..d6929d98a 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -31,6 +31,8 @@ const ( GitlabOrgSFIDIndex = "gitlab-org-sfid-index" GitlabOrgLowerNameIndex = "gitlab-organization-name-lower-search-index" GitlabProjectSFIDOrganizationNameIndex = "gitlab-project-sfid-organization-name-index" + // GitLabExternalIDIndex the index for the external ID + GitLabExternalIDIndex = "github-user-external-id-index" ) // RepositoryInterface is interface for gitlab org data model @@ -293,7 +295,7 @@ func (repo *Repository) GetGitlabOrganizationByExternalID(ctx context.Context, g ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(repo.gitlabOrgTableName), - IndexName: aws.String(GitlabOrgLowerNameIndex), + IndexName: aws.String(GitLabExternalIDIndex), } log.WithFields(f).Debugf("querying for GitLab organization by external group ID: %d...", gitLabGroupID) @@ -318,18 +320,18 @@ func (repo *Repository) GetGitlabOrganizationByExternalID(ctx context.Context, g } // GetGitlabOrganization by organization name -func (repo *Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) { +func (repo *Repository) GetGitlabOrganization(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.GetGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, + "gitLabOrganizationID": gitLabOrganizationID, } - log.WithFields(f).Debug("Querying for GitLab organization by name...") + log.WithFields(f).Debugf("Querying for GitLab organization by ID: %s", gitLabOrganizationID) result, err := repo.dynamoDBClient.GetItem(&dynamodb.GetItemInput{ Key: map[string]*dynamodb.AttributeValue{ GitLabOrganizationsOrganizationIDColumn: { - S: aws.String(gitlabOrganizationID), + S: aws.String(gitLabOrganizationID), }, }, TableName: aws.String(repo.gitlabOrgTableName), @@ -338,7 +340,7 @@ func (repo *Repository) GetGitlabOrganization(ctx context.Context, gitlabOrganiz return nil, err } if len(result.Item) == 0 { - log.WithFields(f).Debug("Unable to find GitLab organization by name - no results") + log.WithFields(f).Debugf("Unable to find GitLab organization by ID: %s - no results", gitLabOrganizationID) return nil, nil } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index adac34e2b..0d7544019 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -34,13 +34,13 @@ import ( // ServiceInterface contains functions of GitlabOrganizations service type ServiceInterface interface { AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) - GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) - GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, gitlabOrganizationName string) (*models.GitlabOrganization, error) + GetGitlabOrganization(ctx context.Context, gitLabOrganizationID string) (*models.GitlabOrganization, error) + GetGitlabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) + GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) - GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) + GetGitlabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error - UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error + UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error } @@ -125,15 +125,15 @@ func (s *Service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationI } // GetGitlabOrganizationByID returns the record associated with the GitLab Organization ID -func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) { +func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, + "gitLabOrganizationID": gitLabOrganizationID, } - log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) - dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id: %s", gitLabOrganizationID) + dbModel, err := s.repo.GetGitlabOrganization(ctx, gitLabOrganizationID) if err != nil { return nil, err } @@ -141,15 +141,15 @@ func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitlabOrganizat return dbModel, nil } -func (s *Service) GetGitlabOrganizationByName(ctx context.Context, gitlabOrganizationName string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationName, + "gitlabOrganizationID": gitLabOrganizationName, } - log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationName) - dbModel, err := s.repo.GetGitlabOrganizationByName(ctx, gitlabOrganizationName) + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id: %s", gitLabOrganizationName) + dbModel, err := s.repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName) if err != nil { return nil, err } @@ -232,6 +232,7 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string OrganizationName: org.OrganizationName, OrganizationURL: org.OrganizationURL, OrganizationFullPath: org.OrganizationFullPath, + OrganizationExternalID: org.OrganizationExternalID, InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), BranchProtectionEnabled: false, ConnectionStatus: "", // updated below @@ -283,16 +284,16 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string } // GetGitlabOrganizationByState returns the GitLab organization by the auth state -func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrganizationID, authState string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, + "gitLabOrganizationID": gitLabOrganizationID, "authState": authState, } - log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitlabOrganizationID) - dbModel, err := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitLabOrganizationID) + dbModel, err := s.repo.GetGitlabOrganization(ctx, gitLabOrganizationID) if err != nil { return nil, err } @@ -305,11 +306,11 @@ func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitlabOrgani } // UpdateGitlabOrganizationAuth updates the GitLab organization authentication information -func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { +func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.UpdateGitlabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "gitlabOrganizationID": gitlabOrganizationID, + "gitLabOrganizationID": gitLabOrganizationID, } log.WithFields(f).Debugf("updating gitlab org auth") @@ -318,7 +319,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani return fmt.Errorf("encrypt failed : %v", err) } - gitLabOrgModel, err := s.GetGitlabOrganizationByID(ctx, gitlabOrganizationID) + gitLabOrgModel, err := s.GetGitlabOrganizationByID(ctx, gitLabOrganizationID) if err != nil { return fmt.Errorf("gitlab organization lookup error: %+v", err) } @@ -337,13 +338,13 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitlabOrgani for _, g := range groups { if g.FullPath == gitLabOrgModel.OrganizationFullPath { - updateGitLabOrgErr := s.repo.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) + updateGitLabOrgErr := s.repo.UpdateGitlabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) if updateGitLabOrgErr != nil { return updateGitLabOrgErr } log.WithFields(f).Debugf("fetching updated GitLab group/organization record which should now have all the details") - updatedOrgDBModel, getErr := s.repo.GetGitlabOrganization(ctx, gitlabOrganizationID) + updatedOrgDBModel, getErr := s.repo.GetGitlabOrganization(ctx, gitLabOrganizationID) if getErr != nil { return getErr } @@ -374,12 +375,12 @@ func (s *Service) UpdateGitlabOrganization(ctx context.Context, projectSFID stri } // DeleteGitlabOrganization deletes the specified GitLab organization -func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error { +func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitLabOrgName string) error { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.DeleteGitlabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "gitlabOrgName": gitlabOrgName, + "gitLabOrgName": gitLabOrgName, } // Lookup the parent @@ -392,13 +393,13 @@ func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID stri log.WithFields(f).Debugf("retrieved parent of project sfid : %s -> %s", projectSFID, parentProjectSFID) // Todo: Enable this when the repositories are implemented - //err := s.ghRepository.GitHubDisableRepositoriesOfOrganization(ctx, parentProjectSFID, gitlabOrgName) + //err := s.ghRepository.GitHubDisableRepositoriesOfOrganization(ctx, parentProjectSFID, gitLabOrgName) //if err != nil { // log.WithFields(f).Warnf("problem disabling repositories for github organizations, error: %+v", projErr) // return err //} - return s.repo.DeleteGitlabOrganization(ctx, projectSFID, gitlabOrgName) + return s.repo.DeleteGitlabOrganization(ctx, projectSFID, gitLabOrgName) } func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI { From 4514fb364e999fc3248be0c2a81c2bdad235fa80 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 15:22:56 -0700 Subject: [PATCH 0438/1276] Resolved GitLab Query Issue (#3174) Signed-off-by: David Deal --- cla-backend-go/v2/gitlab_organizations/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 0d7544019..398b57ab1 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -337,7 +337,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrgani } for _, g := range groups { - if g.FullPath == gitLabOrgModel.OrganizationFullPath { + if g.ID == gitLabOrgModel.ExternalGroupID { updateGitLabOrgErr := s.repo.UpdateGitlabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) if updateGitLabOrgErr != nil { return updateGitLabOrgErr @@ -359,7 +359,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrgani } } - return fmt.Errorf("unable to locate GitLab group name '%s' using search, found: %d", gitLabOrgModel.OrganizationName, len(groups)) + return fmt.Errorf("unable to locate GitLab group by using external ID: %d, found: %d", gitLabOrgModel.ExternalGroupID, len(groups)) } // UpdateGitlabOrganization updates the GitLab organization From 017faf692cbaba04deb004bf91efd5d5eadd61f3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 16:18:33 -0700 Subject: [PATCH 0439/1276] Added GitLab Full Path Support (#3175) --- cla-backend-go/gitlab_api/client_groups.go | 25 +++ cla-backend-go/serverless.yml | 2 + .../common/gitlab-organization-create.yaml | 25 +-- cla-backend-go/tests/gitlab_client_test.go | 38 +++++ .../v2/dynamo_events/gitlab_webhooks.go | 6 +- cla-backend-go/v2/gitlab-activity/service.go | 6 +- .../v2/gitlab_organizations/handlers.go | 23 +-- .../v2/gitlab_organizations/repository.go | 157 ++++++++++++------ .../v2/gitlab_organizations/service.go | 79 ++++----- cla-backend-go/v2/gitlab_sign/service.go | 2 +- .../v2/repositories/gitlab_services.go | 2 +- cla-backend-go/v2/repositories/service.go | 19 ++- cla-backend/serverless.yml | 7 + 13 files changed, 266 insertions(+), 125 deletions(-) diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go index c64a5f2f3..ba7a30b60 100644 --- a/cla-backend-go/gitlab_api/client_groups.go +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -121,6 +121,31 @@ func GetGroupByID(ctx context.Context, client *goGitLab.Client, groupID int) (*g return group, nil } +// GetGroupByFullPath gets a gitlab Group by the given full path +func GetGroupByFullPath(ctx context.Context, client *goGitLab.Client, fullPath string) (*goGitLab.Group, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.GetGroupByName", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + groups, err := GetGroupsListAll(ctx, client) + //groups, _, err := client.Groups.ListGroups(&goGitLab.ListGroupsOptions{}) + if err != nil { + msg := fmt.Sprintf("problem fetching groups, error: %+v", err) + log.WithFields(f).WithError(err).Warn(msg) + return nil, errors.New(msg) + } + + for _, group := range groups { + log.WithFields(f).Debugf("testing %s == %s", fullPath, group.FullPath) + if group.FullPath == fullPath { + return group, nil + } + } + + return nil, nil +} + // GetGroupProjectListByGroupID returns a list of GitLab projects under the specified Organization func GetGroupProjectListByGroupID(ctx context.Context, client *goGitLab.Client, groupID int) ([]*goGitLab.Project, error) { f := logrus.Fields{ diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index efbae4f06..699623260 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -199,6 +199,8 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-id-index" environment: STAGE: ${self:provider.stage} diff --git a/cla-backend-go/swagger/common/gitlab-organization-create.yaml b/cla-backend-go/swagger/common/gitlab-organization-create.yaml index 48384010b..983fe0268 100644 --- a/cla-backend-go/swagger/common/gitlab-organization-create.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization-create.yaml @@ -2,23 +2,26 @@ # SPDX-License-Identifier: MIT type: object -required: - - group_id properties: -# organization_name: -# type: string -# description: The GitLab Group/Organization name -# example: "kubernetes" -# # Pattern aligns with UI and other platform services including Org Service -# # \w Any word character (alphanumeric & underscore), dashes, periods -# pattern: '^([\w\-\.]+){2,255}$' -# minLength: 2 -# maxLength: 255 + # organization_name: + # type: string + # description: The GitLab Group/Organization name + # example: "kubernetes" + # # Pattern aligns with UI and other platform services including Org Service + # # \w Any word character (alphanumeric & underscore), dashes, periods + # pattern: '^([\w\-\.]+){2,255}$' + # minLength: 2 + # maxLength: 255 group_id: type: integer description: The GitLab Group ID example: 13050017 minimum: 1 + group_full_path: + type: string + description: The GitLab Group full path + example: 'linuxfoundation/product/easycla' + minLength: 3 auto_enabled: type: boolean description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index 8303f9cbd..46a4924c2 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -104,6 +104,44 @@ func TestGetGroupByID(t *testing.T) { // no lint } } +func TestGetGroupByFullPath(t *testing.T) { // no lint + if enabled { // nolint + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + + // Create a new GitLab App client instance + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + // Create a new client + gitLabClient, err := gitlab_api.NewGitlabOauthClient(accessInfo, gitLabApp) + assert.Nil(t, err, "GitLab OAuth Client Error is Nil") + assert.NotNil(t, gitLabClient, "GitLab OAuth Client is Not Nil") + + ctx := utils.NewContext() + groupModel, getError := gitlab_api.GetGroupByFullPath(ctx, gitLabClient, "linuxfoundation/product/asitha") + assert.Nil(t, getError, "GitLab GetGroup Error should be nil", getError) + assert.NotNil(t, groupModel, "Group Model should not be nil") + t.Logf("group ID: %d, name: %s, path: %s, full path: %s", groupModel.ID, groupModel.Name, groupModel.Path, groupModel.FullPath) + } +} + func TestGetGroupProjectListByGroupID(t *testing.T) { // no lint if enabled { // nolint // Need to initialize the system to load the configuration which contains a number of SSM parameters diff --git a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go index 49c3160ba..cc1d07696 100644 --- a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go +++ b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go @@ -50,7 +50,7 @@ func (s *service) GitLabRepoAddedWebhookEventHandler(event events.DynamoDBEventR log.WithFields(f).Debugf("adding webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) - gitlabOrg, err := s.gitLabOrgRepo.GetGitlabOrganizationByName(ctx, newRepoModel.RepositoryOrganizationName) + gitlabOrg, err := s.gitLabOrgRepo.GetGitLabOrganizationByName(ctx, newRepoModel.RepositoryOrganizationName) if err != nil { return fmt.Errorf("fetching gitlab org : %s failed : %v", newRepoModel.RepositoryOrganizationName, err) } @@ -119,7 +119,7 @@ func (s *service) GitlabRepoModifiedWebhookEventHandler(event events.DynamoDBEve log.WithFields(f).Debugf("removing webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) } - gitlabOrg, err := s.gitLabOrgRepo.GetGitlabOrganizationByName(ctx, oldRepoModel.RepositoryOrganizationName) + gitlabOrg, err := s.gitLabOrgRepo.GetGitLabOrganizationByName(ctx, oldRepoModel.RepositoryOrganizationName) if err != nil { return fmt.Errorf("fetching gitlab org : %s failed : %v", oldRepoModel.RepositoryOrganizationName, err) } @@ -179,7 +179,7 @@ func (s *service) GitLabRepoRemovedWebhookEventHandler(event events.DynamoDBEven log.WithFields(f).Debugf("removing webhook for repository : %s:%s with external id : %s", repositoryID, repositoryName, repositoryExternalID) - gitlabOrg, err := s.gitLabOrgRepo.GetGitlabOrganizationByName(ctx, oldRepoModel.RepositoryOrganizationName) + gitlabOrg, err := s.gitLabOrgRepo.GetGitLabOrganizationByName(ctx, oldRepoModel.RepositoryOrganizationName) if err != nil { return fmt.Errorf("fetching gitlab org : %s failed : %v", oldRepoModel.RepositoryOrganizationName, err) } diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 6d4d61340..6ca2a9f43 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -253,16 +253,16 @@ func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeE parts := strings.Split(repositoryPath, "/") organizationName := parts[0] - gitlabOrg, err := s.gitlabRepository.GetGitlabOrganizationByName(ctx, organizationName) + gitlabOrg, err := s.gitlabRepository.GetGitLabOrganizationByName(ctx, organizationName) if err != nil || gitlabOrg == nil { // try getting it with project name as well - gitlabOrg, err = s.gitlabRepository.GetGitlabOrganizationByName(ctx, mergeEvent.Project.Namespace) + gitlabOrg, err = s.gitlabRepository.GetGitLabOrganizationByName(ctx, mergeEvent.Project.Namespace) if err != nil || gitlabOrg == nil { return nil, fmt.Errorf("gitlab org : %s doesn't exist : %v", organizationName, err) } } - gitlabOrg, err = s.gitlabRepository.GetGitlabOrganization(ctx, gitlabOrg.OrganizationID) + gitlabOrg, err = s.gitlabRepository.GetGitLabOrganization(ctx, gitlabOrg.OrganizationID) if err != nil { return nil, fmt.Errorf("fetching gitlab org : %s failed : %v", gitlabOrg.OrganizationID, err) } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 66d8e1ff9..837b6ddf0 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -64,7 +64,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.GetGitlabOrganizations(ctx, params.ProjectSFID) + result, err := service.GetGitLabOrganizations(ctx, params.ProjectSFID) if err != nil { if strings.ContainsAny(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("Gitlab organization with project SFID not found: %s", params.ProjectSFID) @@ -94,6 +94,8 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "authUser": authUser.UserName, "authEmail": authUser.Email, "projectSFID": params.ProjectSFID, + "groupID": params.Body.GroupID, + "groupFullPath": params.Body.GroupFullPath, } // Load the project @@ -113,13 +115,12 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Quick check of the parameters - if params.Body == nil || params.Body.GroupID == nil { - msg := fmt.Sprintf("missing group ID in body: %+v", params.Body) + if params.Body == nil || (params.Body.GroupID == 0 && params.Body.GroupFullPath == "") { + msg := fmt.Sprintf("missing group ID or group full path in the body: %+v", params.Body) log.WithFields(f).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( utils.ErrorResponseBadRequest(reqID, msg)) } - f["groupID"] = utils.Int64Value(params.Body.GroupID) if params.Body.AutoEnabled == nil { msg := fmt.Sprintf("missing autoEnabled name in body: %+v", params.Body) @@ -138,7 +139,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - result, err := service.AddGitlabOrganization(ctx, params.ProjectSFID, params.Body) + result, err := service.AddGitLabOrganization(ctx, params.ProjectSFID, params.Body) if err != nil { msg := fmt.Sprintf("unable to add GitLab organization, error: %+v", err) log.WithFields(f).WithError(err).Warn(msg) @@ -148,7 +149,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic // Get the current group name for the event for _, group := range result.List { - if group.OrganizationExternalID == *params.Body.GroupID { + if group.OrganizationExternalID == params.Body.GroupID || group.OrganizationFullPath == params.Body.GroupFullPath { // Log the event eventService.LogEventWithContext(ctx, &events.LogEventArgs{ LfUsername: authUser.UserName, @@ -181,7 +182,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic }) } - err := service.UpdateGitlabOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + err := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) if err != nil { if errors.Is(err, projects_cla_groups.ErrCLAGroupDoesNotExist) { return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, err.Error())) @@ -231,7 +232,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_organizations.NewDeleteProjectGitlabOrganizationForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - err = service.DeleteGitlabOrganization(ctx, params.ProjectSFID, params.OrgName) + err = service.DeleteGitLabOrganization(ctx, params.ProjectSFID, params.OrgName) if err != nil { if strings.Contains(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("project not found with given SFID: %s", params.ProjectSFID) @@ -288,7 +289,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic gitlabOrganizationID := codeParts[0] stateVar := codeParts[1] - gitLabOrg, err := service.GetGitlabOrganizationByState(ctx, gitlabOrganizationID, stateVar) + gitLabOrg, err := service.GetGitLabOrganizationByState(ctx, gitlabOrganizationID, stateVar) if err != nil { msg := fmt.Sprintf("fetching gitlab model failed : %s : %v", gitlabOrganizationID, err) log.WithFields(f).WithError(err).Warn(msg) @@ -304,7 +305,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } log.WithFields(f).Debugf("oauth resp is like : %+v", oauthResp) - updateErr := service.UpdateGitlabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) + updateErr := service.UpdateGitLabOrganizationAuth(ctx, gitlabOrganizationID, oauthResp) if updateErr != nil { msg := fmt.Sprintf("installation of GitLab Group and Repositories, error: %v", updateErr) log.WithFields(f).WithError(updateErr).Warn(msg) @@ -312,7 +313,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Reload the GitLab organization - will have additional details now... - updatedGitLabOrgDBModel, err := service.GetGitlabOrganizationByID(ctx, gitLabOrg.OrganizationID) + updatedGitLabOrgDBModel, err := service.GetGitLabOrganizationByID(ctx, gitLabOrg.OrganizationID) if err != nil { msg := fmt.Sprintf("problem loading updated gitlab organization by ID: %s : %v", gitlabOrganizationID, err) log.WithFields(f).Errorf(msg) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index d6929d98a..a435b6723 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -28,24 +28,28 @@ import ( // indexes const ( - GitlabOrgSFIDIndex = "gitlab-org-sfid-index" - GitlabOrgLowerNameIndex = "gitlab-organization-name-lower-search-index" - GitlabProjectSFIDOrganizationNameIndex = "gitlab-project-sfid-organization-name-index" + // GitlabOrgSFIDIndex the index for the SFID + GitlabOrgSFIDIndex = "gitlab-org-sfid-index" + // GitlabOrgLowerNameIndex the index for the group/org naem in lower case + GitlabOrgLowerNameIndex = "gitlab-organization-name-lower-search-index" // GitLabExternalIDIndex the index for the external ID - GitLabExternalIDIndex = "github-user-external-id-index" + GitLabExternalIDIndex = "gitlab-external-group-id-index" + // GitLabFullPathIndex the index for the full path + GitLabFullPathIndex = "gitlab-full-path-index" ) // RepositoryInterface is interface for gitlab org data model type RepositoryInterface interface { - AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) - GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) - GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) - UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error - UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - UpdateGitlabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error + AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) + GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) + GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) + GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) + GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) + GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) + UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error + UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitLabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } // Repository object/struct @@ -64,24 +68,35 @@ func NewRepository(awsSession *session.Session, stage string) RepositoryInterfac } } -func (repo *Repository) AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) { +func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.repository.AddGitlabOrganization", + "functionName": "v2.gitlab_organizations.repository.AddGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "parentProjectSFID": parentProjectSFID, "projectSFID": projectSFID, "groupID": groupID, "organizationName": organizationName, + "groupFullPath": groupFullPath, "autoEnabled": autoEnabled, "autoEnabledClaGroupID": autoEnabledClaGroupID, "branchProtectionEnabled": branchProtectionEnabled, "enabled": enabled, } - // First, let's check to see if we have an existing gitlab organization with the same name - existingRecord, getErr := repo.GetGitlabOrganizationByExternalID(ctx, groupID) - if getErr != nil { - log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab organization by name %d - ok to create a new record", groupID) + var existingRecord *common.GitLabOrganization + var getErr error + if groupID != 0 { + // First, let's check to see if we have an existing gitlab organization with the same name + existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) + if getErr != nil { + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by name %d - ok to create a new record", groupID) + } + } else if groupFullPath != "" { + // First, let's check to see if we have an existing gitlab organization with the same name + existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) + if getErr != nil { + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by full path: %s - ok to create a new record", groupFullPath) + } } if existingRecord != nil { @@ -89,14 +104,14 @@ func (repo *Repository) AddGitlabOrganization(ctx context.Context, parentProject // If everything matches... if projectSFID == existingRecord.ProjectSFID { log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") - updateErr := repo.UpdateGitlabOrganizationByExternalID(ctx, projectSFID, groupID, organizationName, + updateErr := repo.UpdateGitLabOrganizationByExternalID(ctx, projectSFID, groupID, organizationName, groupFullPath, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, enabled) if updateErr != nil { return nil, updateErr } // Return the updated record - if gitlabOrg, err := repo.GetGitlabOrganizationByExternalID(ctx, groupID); err != nil { + if gitlabOrg, err := repo.GetGitLabOrganizationByExternalID(ctx, groupID); err != nil { return nil, err } else { return common.ToModel(gitlabOrg), nil @@ -167,10 +182,10 @@ func (repo *Repository) AddGitlabOrganization(ctx context.Context, parentProject return common.ToModel(gitlabOrg), nil } -// GetGitlabOrganizations get GitLab organizations based on the project SFID -func (repo *Repository) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { +// GetGitLabOrganizations get GitLab organizations based on the project SFID +func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.repository.GetGitlabOrganizations", + "functionName": "v2.gitlab_organizations.repository.GetGitLabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -223,10 +238,10 @@ func (repo *Repository) GetGitlabOrganizations(ctx context.Context, projectSFID return &models2.GitlabOrganizations{List: gitlabOrgList}, nil } -// GetGitlabOrganizationByName get GitLab organization by name -func (repo *Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) { +// GetGitLabOrganizationByName get GitLab organization by name +func (repo *Repository) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) { f := logrus.Fields{ - "functionName": "v1.gitlab_organizations.repository.GetGitlabOrganizationByName", + "functionName": "v1.gitlab_organizations.repository.GetGitLabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitLabOrganizationName": gitLabOrganizationName, } @@ -272,9 +287,9 @@ func (repo *Repository) GetGitlabOrganizationByName(ctx context.Context, gitLabO return resultOutput[0], nil } -func (repo *Repository) GetGitlabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) { +func (repo *Repository) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) { f := logrus.Fields{ - "functionName": "v1.gitlab_organizations.repository.GetGitlabOrganizationByExternalID", + "functionName": "v1.gitlab_organizations.repository.GetGitLabOrganizationByExternalID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitLabGroupID": gitLabGroupID, } @@ -319,10 +334,58 @@ func (repo *Repository) GetGitlabOrganizationByExternalID(ctx context.Context, g return resultOutput[0], nil } -// GetGitlabOrganization by organization name -func (repo *Repository) GetGitlabOrganization(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { +// GetGitlabOrganizationByFullPath loads the organization based on the full path value +func (repo *Repository) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) { + f := logrus.Fields{ + "functionName": "v1.gitlab_organizations.repository.GetGitLabOrganizationByFullPath", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "groupFullPath": groupFullPath, + } + + condition := expression.Key(GitLabOrganizationsOrganizationFullPathColumn).Equal(expression.Value(groupFullPath)) + builder := expression.NewBuilder().WithKeyCondition(condition) + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.gitlabOrgTableName), + IndexName: aws.String(GitLabFullPathIndex), + } + + log.WithFields(f).Debugf("querying for GitLab group by full path: %s...", groupFullPath) + results, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving GitLab group by full path: %s", groupFullPath) + return nil, err + } + if len(results.Items) == 0 { + log.WithFields(f).Debugf("Unable to find GitLab group by full path: %s - no results", groupFullPath) + return nil, nil + } + + var resultOutput []*common.GitLabOrganization + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) + if err != nil { + log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) + return nil, err + } + + return resultOutput[0], nil +} + +// GetGitLabOrganization by organization name +func (repo *Repository) GetGitLabOrganization(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.GetGitlabOrganization", + "functionName": "gitlab_organizations.repository.GetGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitLabOrganizationID": gitLabOrganizationID, } @@ -353,10 +416,10 @@ func (repo *Repository) GetGitlabOrganization(ctx context.Context, gitLabOrganiz return &org, nil } -// UpdateGitlabOrganizationAuth updates the specified Gitlab organization oauth info -func (repo *Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error { +// UpdateGitLabOrganizationAuth updates the specified Gitlab organization oauth info +func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationAuth", + "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "organizationID": organizationID, "organizationFullPath": organizationFullPath, @@ -365,7 +428,7 @@ func (repo *Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organi } _, currentTime := utils.CurrentTime() - gitlabOrg, lookupErr := repo.GetGitlabOrganization(ctx, organizationID) + gitlabOrg, lookupErr := repo.GetGitLabOrganization(ctx, organizationID) if lookupErr != nil || gitlabOrg == nil { log.WithFields(f).Warnf("error looking up Gitlab organization by id: %s, error: %+v", organizationID, lookupErr) return lookupErr @@ -420,10 +483,10 @@ func (repo *Repository) UpdateGitlabOrganizationAuth(ctx context.Context, organi return nil } -// UpdateGitlabOrganization updates the GitLab group based on the specified values -func (repo *Repository) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { +// UpdateGitLabOrganization updates the GitLab group based on the specified values +func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitlabOrganization", + "functionName": "gitlab_organizations.repository.UpdateGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "organizationName": organizationName, @@ -434,7 +497,7 @@ func (repo *Repository) UpdateGitlabOrganization(ctx context.Context, projectSFI } _, currentTime := utils.CurrentTime() - gitlabOrg, lookupErr := repo.GetGitlabOrganizationByName(ctx, organizationName) + gitlabOrg, lookupErr := repo.GetGitLabOrganizationByName(ctx, organizationName) if lookupErr != nil { log.WithFields(f).Warnf("error looking up Gitlab organization by name, error: %+v", lookupErr) return lookupErr @@ -493,9 +556,9 @@ func (repo *Repository) UpdateGitlabOrganization(ctx context.Context, projectSFI } // UpdateGitlabOrganizationByExternalID updates the GitLab group based on the specified values -func (repo *Repository) UpdateGitlabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { +func (repo *Repository) UpdateGitLabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName, fullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitlabOrganizationByExternalID", + "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationByExternalID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "groupID": groupID, @@ -507,7 +570,7 @@ func (repo *Repository) UpdateGitlabOrganizationByExternalID(ctx context.Context } _, currentTime := utils.CurrentTime() - gitlabOrg, lookupErr := repo.GetGitlabOrganizationByExternalID(ctx, groupID) + gitlabOrg, lookupErr := repo.GetGitLabOrganizationByExternalID(ctx, groupID) if lookupErr != nil { log.WithFields(f).Warnf("error looking up GitLab group by ID: %d, error: %+v", groupID, lookupErr) return lookupErr @@ -573,17 +636,17 @@ func (repo *Repository) UpdateGitlabOrganizationByExternalID(ctx context.Context return nil } -// DeleteGitlabOrganization deletes the specified GitLab organization -func (repo *Repository) DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { +// DeleteGitLabOrganization deletes the specified GitLab organization +func (repo *Repository) DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { f := logrus.Fields{ - "functionName": "v1.gitlab_organizations.repository.DeleteGitlabOrganization", + "functionName": "v1.gitlab_organizations.repository.DeleteGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "gitlabOrgName": gitlabOrgName, } var gitlabOrganizationID string - orgs, orgErr := repo.GetGitlabOrganizations(ctx, projectSFID) + orgs, orgErr := repo.GetGitLabOrganizations(ctx, projectSFID) if orgErr != nil { errMsg := fmt.Sprintf("gitlab organization is not found using projectSFID: %s, error: %+v", projectSFID, orgErr) log.WithFields(f).Warn(errMsg) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 398b57ab1..3926659fb 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -33,15 +33,15 @@ import ( // ServiceInterface contains functions of GitlabOrganizations service type ServiceInterface interface { - AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) - GetGitlabOrganization(ctx context.Context, gitLabOrganizationID string) (*models.GitlabOrganization, error) - GetGitlabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) - GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) - GetGitlabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) - UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error - UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error - DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error + AddGitLabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) + GetGitLabOrganization(ctx context.Context, gitLabOrganizationID string) (*models.GitlabOrganization, error) + GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) + GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) + GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) + GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) + UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error + UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error + DeleteGitLabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error } // Service data model @@ -63,14 +63,15 @@ func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceI } // AddGitlabOrganization adds the specified GitLab organization -func (s *Service) AddGitlabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { +func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.AddGitlabOrganization", + "functionName": "v2.gitlab_organizations.service.AddGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "autoEnabled": utils.BoolValue(input.AutoEnabled), "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), - "groupID": utils.Int64Value(input.GroupID), + "groupID": input.GroupID, + "groupFullPath": input.GroupFullPath, } psc := v2ProjectService.GetClient() @@ -100,19 +101,19 @@ func (s *Service) AddGitlabOrganization(ctx context.Context, projectSFID string, branchProtectionEnabled = utils.BoolValue(input.BranchProtectionEnabled) } - resp, err := s.repo.AddGitlabOrganization(ctx, parentProjectSFID, projectSFID, *input.GroupID, "", autoEnabled, input.AutoEnabledClaGroupID, branchProtectionEnabled, true) + resp, err := s.repo.AddGitLabOrganization(ctx, parentProjectSFID, projectSFID, input.GroupID, "", input.GroupFullPath, autoEnabled, input.AutoEnabledClaGroupID, branchProtectionEnabled, true) if err != nil { log.WithFields(f).WithError(err).Warn("problem adding gitlab organization for project") return nil, err } log.WithFields(f).Debugf("created GitLab organization with ID: %s", resp.OrganizationID) - return s.GetGitlabOrganizations(ctx, projectSFID) + return s.GetGitLabOrganizations(ctx, projectSFID) } // GetGitlabOrganization returns the GitLab organization based on the specified GitLab Organization ID -func (s *Service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { - dbModel, err := s.GetGitlabOrganizationByID(ctx, gitlabOrganizationID) +func (s *Service) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { + dbModel, err := s.GetGitLabOrganizationByID(ctx, gitlabOrganizationID) if err != nil { return nil, err } @@ -125,15 +126,15 @@ func (s *Service) GetGitlabOrganization(ctx context.Context, gitlabOrganizationI } // GetGitlabOrganizationByID returns the record associated with the GitLab Organization ID -func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { +func (s *Service) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByID", + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitLabOrganizationID": gitLabOrganizationID, } log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id: %s", gitLabOrganizationID) - dbModel, err := s.repo.GetGitlabOrganization(ctx, gitLabOrganizationID) + dbModel, err := s.repo.GetGitLabOrganization(ctx, gitLabOrganizationID) if err != nil { return nil, err } @@ -141,15 +142,15 @@ func (s *Service) GetGitlabOrganizationByID(ctx context.Context, gitLabOrganizat return dbModel, nil } -func (s *Service) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizationByName", + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitlabOrganizationID": gitLabOrganizationName, } log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id: %s", gitLabOrganizationName) - dbModel, err := s.repo.GetGitlabOrganizationByName(ctx, gitLabOrganizationName) + dbModel, err := s.repo.GetGitLabOrganizationByName(ctx, gitLabOrganizationName) if err != nil { return nil, err } @@ -159,16 +160,16 @@ func (s *Service) GetGitlabOrganizationByName(ctx context.Context, gitLabOrganiz } // GetGitlabOrganizations returns a collection of GitLab organizations based on the specified project SFID value -func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { +func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitlabOrganizations", + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } // Load the GitLab Organization and Repository details - result will be missing CLA Group info and ProjectSFID details log.WithFields(f).Debugf("loading Gitlab organizations for projectSFID: %s", projectSFID) - orgs, err := s.repo.GetGitlabOrganizations(ctx, projectSFID) + orgs, err := s.repo.GetGitLabOrganizations(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("problem loading gitlab organizations from the project service") return nil, err @@ -210,7 +211,7 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string } } - orgDetailed, orgErr := s.repo.GetGitlabOrganization(ctx, org.OrganizationID) + orgDetailed, orgErr := s.repo.GetGitLabOrganization(ctx, org.OrganizationID) if orgErr != nil { log.WithFields(f).Errorf("fetching gitlab org failed : %s : %v", org.OrganizationID, orgErr) continue @@ -284,16 +285,16 @@ func (s *Service) GetGitlabOrganizations(ctx context.Context, projectSFID string } // GetGitlabOrganizationByState returns the GitLab organization by the auth state -func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitlabOrganization", + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitLabOrganizationID": gitLabOrganizationID, "authState": authState, } log.WithFields(f).Debugf("fetching gitlab organization for gitlab org id : %s", gitLabOrganizationID) - dbModel, err := s.repo.GetGitlabOrganization(ctx, gitLabOrganizationID) + dbModel, err := s.repo.GetGitLabOrganization(ctx, gitLabOrganizationID) if err != nil { return nil, err } @@ -306,9 +307,9 @@ func (s *Service) GetGitlabOrganizationByState(ctx context.Context, gitLabOrgani } // UpdateGitlabOrganizationAuth updates the GitLab organization authentication information -func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { +func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.UpdateGitlabOrganizationAuth", + "functionName": "v2.gitlab_organizations.service.UpdateGitLabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitLabOrganizationID": gitLabOrganizationID, } @@ -319,7 +320,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrgani return fmt.Errorf("encrypt failed : %v", err) } - gitLabOrgModel, err := s.GetGitlabOrganizationByID(ctx, gitLabOrganizationID) + gitLabOrgModel, err := s.GetGitLabOrganizationByID(ctx, gitLabOrganizationID) if err != nil { return fmt.Errorf("gitlab organization lookup error: %+v", err) } @@ -338,13 +339,13 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrgani for _, g := range groups { if g.ID == gitLabOrgModel.ExternalGroupID { - updateGitLabOrgErr := s.repo.UpdateGitlabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) + updateGitLabOrgErr := s.repo.UpdateGitLabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) if updateGitLabOrgErr != nil { return updateGitLabOrgErr } log.WithFields(f).Debugf("fetching updated GitLab group/organization record which should now have all the details") - updatedOrgDBModel, getErr := s.repo.GetGitlabOrganization(ctx, gitLabOrganizationID) + updatedOrgDBModel, getErr := s.repo.GetGitLabOrganization(ctx, gitLabOrganizationID) if getErr != nil { return getErr } @@ -363,7 +364,7 @@ func (s *Service) UpdateGitlabOrganizationAuth(ctx context.Context, gitLabOrgani } // UpdateGitlabOrganization updates the GitLab organization -func (s *Service) UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { +func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { // check if valid cla group id is passed if autoEnabledClaGroupID != "" { if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { @@ -371,13 +372,13 @@ func (s *Service) UpdateGitlabOrganization(ctx context.Context, projectSFID stri } } - return s.repo.UpdateGitlabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) + return s.repo.UpdateGitLabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) } // DeleteGitlabOrganization deletes the specified GitLab organization -func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID string, gitLabOrgName string) error { +func (s *Service) DeleteGitLabOrganization(ctx context.Context, projectSFID string, gitLabOrgName string) error { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.DeleteGitlabOrganization", + "functionName": "v2.gitlab_organizations.service.DeleteGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "gitLabOrgName": gitLabOrgName, @@ -399,7 +400,7 @@ func (s *Service) DeleteGitlabOrganization(ctx context.Context, projectSFID stri // return err //} - return s.repo.DeleteGitlabOrganization(ctx, projectSFID, gitLabOrgName) + return s.repo.DeleteGitLabOrganization(ctx, projectSFID, gitLabOrgName) } func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI { diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go index 5cd106464..ee4083736 100644 --- a/cla-backend-go/v2/gitlab_sign/service.go +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -54,7 +54,7 @@ func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organ "mergeRequestID": mergeRequestID, } - organization, err := s.gitlabOrgRepo.GetGitlabOrganization(ctx, organizationID) + organization, err := s.gitlabOrgRepo.GetGitLabOrganization(ctx, organizationID) if err != nil { log.WithFields(f).Debugf("unable to get gitlab organiztion by ID: %s, error: %+v ", organizationID, err) return nil diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 7229a5fd0..30027ca15 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -32,7 +32,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, "claGroupID": utils.StringValue(input.ClaGroupID), } - gitLabOrgModel, orgErr := s.glOrgRepo.GetGitlabOrganizationByName(ctx, utils.StringValue(input.GitlabOrganizationName)) + gitLabOrgModel, orgErr := s.glOrgRepo.GetGitLabOrganizationByName(ctx, utils.StringValue(input.GitlabOrganizationName)) if orgErr != nil { msg := fmt.Sprintf("problem loading gitlab organization by name: %s, error: %v", utils.StringValue(input.GitlabOrganizationName), orgErr) log.WithFields(f).WithError(orgErr).Warn(msg) diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index c01224d08..3cd339608 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -67,15 +67,16 @@ type ServiceInterface interface { // GithubOrgRepo redefine the interface here to avoid circular dependency issues type GithubOrgRepo interface { - AddGitlabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) - GetGitlabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) - GetGitlabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) - GetGitlabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) - UpdateGitlabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error - UpdateGitlabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - UpdateGitlabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - DeleteGitlabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error + AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) + GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) + GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) + GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) + GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) + GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) + UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error + UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitLabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } // Service is the service model/structure diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index d35d1d310..188ed452b 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -224,6 +224,8 @@ provider: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" + - Effect: Allow Action: - dynamodb:Query @@ -285,6 +287,11 @@ provider: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-id-index" environment: STAGE: ${self:provider.stage} From 1ba113bd045dabbf77c95a6ff88717fa94023105 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 16:36:53 -0700 Subject: [PATCH 0440/1276] Updated Docs and Added Full Path to Response (#3176) --- .../v2/gitlab_organizations/repository.go | 9 ++++++--- .../v2/gitlab_organizations/service.go | 17 +++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index a435b6723..a01feb8f3 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -68,6 +68,7 @@ func NewRepository(awsSession *session.Session, stage string) RepositoryInterfac } } +// AddGitLabOrganization adds the specified values to the GitLab Group/Org table func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.AddGitLabOrganization", @@ -89,7 +90,7 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject // First, let's check to see if we have an existing gitlab organization with the same name existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) if getErr != nil { - log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by name %d - ok to create a new record", groupID) + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by ID: %d - ok to create a new record", groupID) } } else if groupFullPath != "" { // First, let's check to see if we have an existing gitlab organization with the same name @@ -100,7 +101,7 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject } if existingRecord != nil { - log.WithFields(f).Debugf("An existing GitLab organization with name %d exists in our database", groupID) + log.WithFields(f).Debugf("An existing GitLab organization with ID %d exists in our database", groupID) // If everything matches... if projectSFID == existingRecord.ProjectSFID { log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") @@ -142,6 +143,7 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject DateModified: currentTime, OrganizationName: organizationName, OrganizationNameLower: strings.ToLower(organizationName), + OrganizationFullPath: groupFullPath, ExternalGroupID: int(groupID), OrganizationSFID: parentProjectSFID, ProjectSFID: projectSFID, @@ -287,6 +289,7 @@ func (repo *Repository) GetGitLabOrganizationByName(ctx context.Context, gitLabO return resultOutput[0], nil } +// GetGitLabOrganizationByExternalID returns the GitLab Group/Org based on the external GitLab Group ID value func (repo *Repository) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v1.gitlab_organizations.repository.GetGitLabOrganizationByExternalID", @@ -334,7 +337,7 @@ func (repo *Repository) GetGitLabOrganizationByExternalID(ctx context.Context, g return resultOutput[0], nil } -// GetGitlabOrganizationByFullPath loads the organization based on the full path value +// GetGitLabOrganizationByFullPath loads the organization based on the full path value func (repo *Repository) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v1.gitlab_organizations.repository.GetGitLabOrganizationByFullPath", diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 3926659fb..4b278a2b1 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -62,7 +62,7 @@ func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceI } } -// AddGitlabOrganization adds the specified GitLab organization +// AddGitLabOrganization adds the specified GitLab organization func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.AddGitLabOrganization", @@ -111,7 +111,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, return s.GetGitLabOrganizations(ctx, projectSFID) } -// GetGitlabOrganization returns the GitLab organization based on the specified GitLab Organization ID +// GetGitLabOrganization returns the GitLab organization based on the specified GitLab Organization ID func (s *Service) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { dbModel, err := s.GetGitLabOrganizationByID(ctx, gitlabOrganizationID) if err != nil { @@ -125,7 +125,7 @@ func (s *Service) GetGitLabOrganization(ctx context.Context, gitlabOrganizationI return common.ToModel(dbModel), err } -// GetGitlabOrganizationByID returns the record associated with the GitLab Organization ID +// GetGitLabOrganizationByID returns the record associated with the GitLab Organization ID func (s *Service) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByID", @@ -142,6 +142,7 @@ func (s *Service) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizat return dbModel, nil } +// GetGitLabOrganizationByName returns the gitlab organization based on the Group/Org name func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByName", @@ -159,7 +160,7 @@ func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganiz } -// GetGitlabOrganizations returns a collection of GitLab organizations based on the specified project SFID value +// GetGitLabOrganizations returns a collection of GitLab organizations based on the specified project SFID value func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizations", @@ -284,7 +285,7 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string return out, nil } -// GetGitlabOrganizationByState returns the GitLab organization by the auth state +// GetGitLabOrganizationByState returns the GitLab organization by the auth state func (s *Service) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganization", @@ -306,7 +307,7 @@ func (s *Service) GetGitLabOrganizationByState(ctx context.Context, gitLabOrgani return common.ToModel(dbModel), nil } -// UpdateGitlabOrganizationAuth updates the GitLab organization authentication information +// UpdateGitLabOrganizationAuth updates the GitLab organization authentication information func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.UpdateGitLabOrganizationAuth", @@ -363,7 +364,7 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani return fmt.Errorf("unable to locate GitLab group by using external ID: %d, found: %d", gitLabOrgModel.ExternalGroupID, len(groups)) } -// UpdateGitlabOrganization updates the GitLab organization +// UpdateGitLabOrganization updates the GitLab organization func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { // check if valid cla group id is passed if autoEnabledClaGroupID != "" { @@ -375,7 +376,7 @@ func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID stri return s.repo.UpdateGitLabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) } -// DeleteGitlabOrganization deletes the specified GitLab organization +// DeleteGitLabOrganization deletes the specified GitLab organization func (s *Service) DeleteGitLabOrganization(ctx context.Context, projectSFID string, gitLabOrgName string) error { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.DeleteGitLabOrganization", From 0b6251378fbc6d51aa2dc569658d90facd384b90 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 17:00:15 -0700 Subject: [PATCH 0441/1276] Added Check for GL Full Path (#3177) --- cla-backend-go/v2/gitlab_organizations/service.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 4b278a2b1..13e25d74b 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -339,7 +339,10 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } for _, g := range groups { - if g.ID == gitLabOrgModel.ExternalGroupID { + // If we have an external group ID or a full path... + if (gitLabOrgModel.ExternalGroupID > 0 && g.ID == gitLabOrgModel.ExternalGroupID) || + (gitLabOrgModel.OrganizationFullPath != "" && g.FullPath == gitLabOrgModel.OrganizationFullPath) { + updateGitLabOrgErr := s.repo.UpdateGitLabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) if updateGitLabOrgErr != nil { return updateGitLabOrgErr @@ -361,7 +364,7 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } } - return fmt.Errorf("unable to locate GitLab group by using external ID: %d, found: %d", gitLabOrgModel.ExternalGroupID, len(groups)) + return fmt.Errorf("unable to locate GitLab group by using external ID: %d or full path: %s, found: %d", gitLabOrgModel.ExternalGroupID, gitLabOrgModel.OrganizationFullPath, len(groups)) } // UpdateGitLabOrganization updates the GitLab organization From 6fc2aed1576a321f15a2a9a2b2b30ff754e025b6 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 19:03:38 -0700 Subject: [PATCH 0442/1276] Updated Lambda Index Permissions (#3178) --- cla-backend-go/serverless.yml | 2 +- cla-backend/serverless.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index 699623260..cb5c744ee 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -200,7 +200,7 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: STAGE: ${self:provider.stage} diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 188ed452b..74873fe0b 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -291,7 +291,7 @@ provider: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: STAGE: ${self:provider.stage} From d2341da5df7b7cd9613c473153bfcfbd2520fe3b Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 19 Aug 2021 19:43:08 -0700 Subject: [PATCH 0443/1276] Resolved GitLab Query Bug (#3179) --- .../v2/gitlab_organizations/handlers.go | 2 +- .../v2/gitlab_organizations/repository.go | 157 +++++++----------- .../v2/gitlab_organizations/service.go | 6 +- cla-backend-go/v2/repositories/service.go | 3 +- 4 files changed, 62 insertions(+), 106 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 837b6ddf0..994f49f51 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -182,7 +182,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic }) } - err := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, params.OrgName, *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + err := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, 0, params.OrgName, "", *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) if err != nil { if errors.Is(err, projects_cla_groups.ErrCLAGroupDoesNotExist) { return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, err.Error())) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index a01feb8f3..e00cbaa58 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -47,8 +47,7 @@ type RepositoryInterface interface { GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error - UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - UpdateGitLabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } @@ -87,12 +86,14 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject var existingRecord *common.GitLabOrganization var getErr error if groupID != 0 { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", groupID) // First, let's check to see if we have an existing gitlab organization with the same name existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) if getErr != nil { log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by ID: %d - ok to create a new record", groupID) } } else if groupFullPath != "" { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", groupFullPath) // First, let's check to see if we have an existing gitlab organization with the same name existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) if getErr != nil { @@ -101,21 +102,30 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject } if existingRecord != nil { - log.WithFields(f).Debugf("An existing GitLab organization with ID %d exists in our database", groupID) + log.WithFields(f).Debugf("An existing GitLab organization with ID %d or full path: %s exists in our database", groupID, groupFullPath) // If everything matches... if projectSFID == existingRecord.ProjectSFID { - log.WithFields(f).Debug("Existing GitLab organization with same SFID - should be able to update it") - updateErr := repo.UpdateGitLabOrganizationByExternalID(ctx, projectSFID, groupID, organizationName, groupFullPath, + log.WithFields(f).Debug("existing GitLab organization with same SFID - should be able to update it") + updateErr := repo.UpdateGitLabOrganization(ctx, projectSFID, groupID, organizationName, groupFullPath, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, enabled) if updateErr != nil { return nil, updateErr } - // Return the updated record - if gitlabOrg, err := repo.GetGitLabOrganizationByExternalID(ctx, groupID); err != nil { - return nil, err - } else { - return common.ToModel(gitlabOrg), nil + if groupID > 0 { + // Return the updated record + if gitlabOrg, err := repo.GetGitLabOrganizationByExternalID(ctx, groupID); err != nil { + return nil, err + } else { + return common.ToModel(gitlabOrg), nil + } + } else if groupFullPath != "" { + // Return the updated record + if gitlabOrg, err := repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath); err != nil { + return nil, err + } else { + return common.ToModel(gitlabOrg), nil + } } } @@ -487,11 +497,13 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi } // UpdateGitLabOrganization updates the GitLab group based on the specified values -func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { +func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitLabOrganization", + "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationByExternalID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, + "groupID": groupID, + "groupFullPath": groupFullPath, "organizationName": organizationName, "autoEnabled": autoEnabled, "autoEnabledClaGroupID": autoEnabledClaGroupID, @@ -499,17 +511,33 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI "tableName": repo.gitlabOrgTableName, } - _, currentTime := utils.CurrentTime() - gitlabOrg, lookupErr := repo.GetGitLabOrganizationByName(ctx, organizationName) - if lookupErr != nil { - log.WithFields(f).Warnf("error looking up Gitlab organization by name, error: %+v", lookupErr) - return lookupErr + var existingRecord *common.GitLabOrganization + var getErr error + if groupID > 0 { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", groupID) + existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) + if getErr != nil { + msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v", groupID, groupFullPath) + log.WithFields(f).WithError(getErr).Warn(msg) + return errors.New(msg) + } + } else if groupFullPath != "" { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", groupFullPath) + existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) + if getErr != nil { + msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v", groupFullPath, getErr) + log.WithFields(f).WithError(getErr).Warn(msg) + return errors.New(msg) + } } - if gitlabOrg == nil { - log.WithFields(f).Warn("error looking up Gitlab organization - no results") - return errors.New("unable to lookup Gitlab organization by name") + + if existingRecord == nil { + msg := fmt.Sprintf("error looking up GitLab group using group ID: %d or full path: %s - no results", groupID, groupFullPath) + log.WithFields(f).Warn(msg) + return errors.New(msg) } + _, currentTime := utils.CurrentTime() expressionAttributeNames := map[string]*string{ "#A": aws.String(GitLabOrganizationsAutoEnabledColumn), "#C": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), @@ -534,93 +562,22 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI BOOL: aws.Bool(enabled), }, } - updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m, #E = :e" + updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m, #E = :e " - input := &dynamodb.UpdateItemInput{ - Key: map[string]*dynamodb.AttributeValue{ - GitLabOrganizationsOrganizationIDColumn: { - S: aws.String(gitlabOrg.OrganizationID), - }, - }, - ExpressionAttributeNames: expressionAttributeNames, - ExpressionAttributeValues: expressionAttributeValues, - UpdateExpression: &updateExpression, - TableName: aws.String(repo.gitlabOrgTableName), - } + if organizationName != "" { + expressionAttributeNames["#N"] = aws.String(GitLabOrganizationsOrganizationNameColumn) + expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(organizationName)} + updateExpression = fmt.Sprintf("%s, #N = :n ", updateExpression) - log.WithFields(f).Debugf("updating gitlab organization record: %+v", input) - _, updateErr := repo.dynamoDBClient.UpdateItem(input) - if updateErr != nil { - log.WithFields(f).Warnf("unable to update Gitlab organization record, error: %+v", updateErr) - return updateErr + expressionAttributeNames["#NL"] = aws.String(GitLabOrganizationsOrganizationNameColumn) + expressionAttributeValues[":nl"] = &dynamodb.AttributeValue{S: aws.String(strings.ToLower(organizationName))} + updateExpression = fmt.Sprintf("%s, #NL = :nl ", updateExpression) } - return nil -} - -// UpdateGitlabOrganizationByExternalID updates the GitLab group based on the specified values -func (repo *Repository) UpdateGitLabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName, fullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { - f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationByExternalID", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "groupID": groupID, - "organizationName": organizationName, - "autoEnabled": autoEnabled, - "autoEnabledClaGroupID": autoEnabledClaGroupID, - "branchProtectionEnabled": branchProtectionEnabled, - "tableName": repo.gitlabOrgTableName, - } - - _, currentTime := utils.CurrentTime() - gitlabOrg, lookupErr := repo.GetGitLabOrganizationByExternalID(ctx, groupID) - if lookupErr != nil { - log.WithFields(f).Warnf("error looking up GitLab group by ID: %d, error: %+v", groupID, lookupErr) - return lookupErr - } - if gitlabOrg == nil { - log.WithFields(f).Warn("error looking up GitLab group - no results") - return errors.New("unable to lookup GitLab group by ID") - } - - expressionAttributeNames := map[string]*string{ - "#A": aws.String(GitLabOrganizationsAutoEnabledColumn), - "#C": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), - "#B": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), - "#N": aws.String(GitLabOrganizationsOrganizationNameColumn), - "#NL": aws.String(GitLabOrganizationsOrganizationNameLowerColumn), - "#M": aws.String(GitLabOrganizationsDateModifiedColumn), - "#E": aws.String(GitLabOrganizationsEnabledColumn), - } - expressionAttributeValues := map[string]*dynamodb.AttributeValue{ - ":a": { - BOOL: aws.Bool(autoEnabled), - }, - ":c": { - S: aws.String(autoEnabledClaGroupID), - }, - ":b": { - BOOL: aws.Bool(branchProtectionEnabled), - }, - ":n": { - S: aws.String(organizationName), - }, - ":nl": { - S: aws.String(strings.ToLower(organizationName)), - }, - ":m": { - S: aws.String(currentTime), - }, - ":e": { - BOOL: aws.Bool(enabled), - }, - } - updateExpression := "SET #A = :a, #C = :c, #B = :b, #N = :n, #NL = :nl, #M = :m, #E = :e " - input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ GitLabOrganizationsOrganizationIDColumn: { - S: aws.String(gitlabOrg.OrganizationID), + S: aws.String(existingRecord.OrganizationID), }, }, ExpressionAttributeNames: expressionAttributeNames, diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 13e25d74b..22da09b06 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -39,7 +39,7 @@ type ServiceInterface interface { GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) - UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error + UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error DeleteGitLabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error } @@ -368,7 +368,7 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } // UpdateGitLabOrganization updates the GitLab organization -func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { +func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { // check if valid cla group id is passed if autoEnabledClaGroupID != "" { if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { @@ -376,7 +376,7 @@ func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID stri } } - return s.repo.UpdateGitLabOrganization(ctx, projectSFID, organizationName, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) + return s.repo.UpdateGitLabOrganization(ctx, projectSFID, groupID, organizationName, groupFullPath, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) } // DeleteGitLabOrganization deletes the specified GitLab organization diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 3cd339608..118218149 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -74,8 +74,7 @@ type GithubOrgRepo interface { GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error - UpdateGitLabOrganization(ctx context.Context, projectSFID string, organizationName string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - UpdateGitLabOrganizationByExternalID(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } From fdc9aef7af639e5feb9b012577a4487fd82c29b6 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 20 Aug 2021 08:28:28 -0700 Subject: [PATCH 0444/1276] Resolved GitLab Lookup Error With No OrgName Value (#3180) --- .../common/gitlab-repositories-add.yaml | 13 +++++++--- .../v2/repositories/gitlab_services.go | 26 ++++++++++++------- cla-backend-go/v2/repositories/handlers.go | 6 +++-- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/cla-backend-go/swagger/common/gitlab-repositories-add.yaml b/cla-backend-go/swagger/common/gitlab-repositories-add.yaml index fa1e32043..33da7ac31 100644 --- a/cla-backend-go/swagger/common/gitlab-repositories-add.yaml +++ b/cla-backend-go/swagger/common/gitlab-repositories-add.yaml @@ -2,9 +2,7 @@ # SPDX-License-Identifier: MIT type: object -required: - - gitlab_organization_name - - cla_group_id +description: 'GitLab repositories add model' properties: repository_gitlab_ids: type: array @@ -17,6 +15,15 @@ properties: type: string description: The organization name associated with this repository example: 'The Linux Foundation/product/EasyCLA' + organization_external_id: + type: integer + description: The Gitlab Group/Organization external ID used by GitLab + example: 13050017 + minimum: 1 + organization_full_path: + type: string + description: The Gitlab Group/Organization full path + example: "linuxfoundation/product/easycla" cla_group_id: description: CLA Group ID $ref: './common/properties/internal-id.yaml' diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 30027ca15..00450c7d8 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -28,13 +28,15 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, "functionName": "v2.repositories.gitlab_services.GitLabAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, - "organizationName": utils.StringValue(input.GitlabOrganizationName), - "claGroupID": utils.StringValue(input.ClaGroupID), + "organizationName": input.GitlabOrganizationName, + "claGroupID": input.ClaGroupID, + "groupFullPath": input.OrganizationFullPath, + "groupID": input.OrganizationExternalID, } - gitLabOrgModel, orgErr := s.glOrgRepo.GetGitLabOrganizationByName(ctx, utils.StringValue(input.GitlabOrganizationName)) + gitLabOrgModel, orgErr := s.glOrgRepo.GetGitLabOrganizationByName(ctx, input.GitlabOrganizationName) if orgErr != nil { - msg := fmt.Sprintf("problem loading gitlab organization by name: %s, error: %v", utils.StringValue(input.GitlabOrganizationName), orgErr) + msg := fmt.Sprintf("problem loading gitlab organization by name: %s, error: %v", input.GitlabOrganizationName, orgErr) log.WithFields(f).WithError(orgErr).Warn(msg) return nil, errors.New(msg) } @@ -61,8 +63,8 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, RepositoryName: project.Name, RepositoryFullPath: project.PathWithNamespace, RepositoryURL: project.WebURL, - RepositoryOrganizationName: utils.StringValue(input.GitlabOrganizationName), // gitlab group/organization - RepositoryCLAGroupID: utils.StringValue(input.ClaGroupID), + RepositoryOrganizationName: input.GitlabOrganizationName, + RepositoryCLAGroupID: input.ClaGroupID, RepositoryType: utils.GitLabLower, // should always be gitlab Enabled: false, // we don't enable by default } @@ -77,7 +79,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryAdded, ProjectSFID: projectSFID, - CLAGroupID: utils.StringValue(input.ClaGroupID), + CLAGroupID: input.ClaGroupID, LfUsername: utils.GetUserNameFromContext(ctx), EventData: &events.RepositoryAddedEventData{ RepositoryName: project.PathWithNamespace, // give the full path/name @@ -85,7 +87,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, }) } - return s.GitLabGetRepositoriesByOrganizationName(ctx, utils.StringValue(input.GitlabOrganizationName)) + return s.GitLabGetRepositoriesByProjectSFID(ctx, projectSFID) } // GitLabAddRepositoriesByApp adds the GitLab repositories based on the application credentials @@ -95,6 +97,8 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": gitLabOrgModel.ProjectSFID, "organizationName": gitLabOrgModel.OrganizationName, + "groupFullPath": gitLabOrgModel.OrganizationFullPath, + "groupID": gitLabOrgModel.ExternalGroupID, } // Get the client @@ -125,8 +129,10 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel // Build input to the add function input := &v2Models.GitlabRepositoriesAdd{ - ClaGroupID: utils.StringRef(projectCLAGroupModel.ClaGroupID), - GitlabOrganizationName: utils.StringRef(gitLabOrgModel.OrganizationName), + ClaGroupID: projectCLAGroupModel.ClaGroupID, + GitlabOrganizationName: gitLabOrgModel.OrganizationName, + OrganizationExternalID: int64(gitLabOrgModel.ExternalGroupID), + OrganizationFullPath: gitLabOrgModel.OrganizationFullPath, RepositoryGitlabIds: listProjectIDs, } _, addRepoErr := s.GitLabAddRepositories(ctx, gitLabOrgModel.ProjectSFID, input) diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index b919a9c37..8f57c8c80 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -417,8 +417,10 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "authUser": authUser.UserName, "authEmail": authUser.Email, "projectSFID": params.ProjectSFID, - "organizationName": utils.StringValue(params.GitlabRepositoriesAdd.GitlabOrganizationName), - "claGroupID": utils.StringValue(params.GitlabRepositoriesAdd.ClaGroupID), + "organizationName": params.GitlabRepositoriesAdd.GitlabOrganizationName, + "claGroupID": params.GitlabRepositoriesAdd.ClaGroupID, + "groupFullPath": params.GitlabRepositoriesAdd.OrganizationFullPath, + "groupID": params.GitlabRepositoriesAdd.OrganizationExternalID, } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { From f585b3bfc5061f7b83d3108d3b7c665a14341b98 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 20 Aug 2021 10:20:54 -0700 Subject: [PATCH 0445/1276] GitLab Debug/Cleanup (#3182) Signed-off-by: David Deal --- .../v2/gitlab_organizations/handlers.go | 29 +++++++++++++ .../v2/gitlab_organizations/repository.go | 5 ++- .../v2/gitlab_organizations/service.go | 42 ++++++++++--------- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 994f49f51..846f51fe1 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -7,6 +7,8 @@ import ( "errors" "fmt" "net/http" + "net/url" + "regexp" "strings" "github.com/go-openapi/runtime" @@ -122,6 +124,33 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequest(reqID, msg)) } + // Clean up/filter the Group Full Path, if needed + if params.Body.GroupFullPath != "" { + r, regexErr := regexp.Compile(`^http(s)?://`) + if regexErr != nil { + msg := fmt.Sprintf("invalid regex for group full path, error: %+v", regexErr) + log.WithFields(f).WithError(regexErr).Warn(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationInternalServerError().WithPayload( + utils.ErrorResponseInternalServerErrorWithError(reqID, msg, regexErr)) + } + if r.MatchString(params.Body.GroupFullPath) { + groupWithUrl, urlParseErr := url.Parse(params.Body.GroupFullPath) + if urlParseErr != nil { + msg := fmt.Sprintf("invalid group full path provided, error: %+v", urlParseErr) + log.WithFields(f).WithError(urlParseErr).Warn(msg) + return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( + utils.ErrorResponseBadRequestWithError(reqID, msg, urlParseErr)) + } + // Update the group full path value - just include the path and not the https://... part + params.Body.GroupFullPath = groupWithUrl.Path + } + + // Remove leading slash + if strings.HasPrefix(params.Body.GroupFullPath, "/") { + params.Body.GroupFullPath = params.Body.GroupFullPath[1:] + } + } + if params.Body.AutoEnabled == nil { msg := fmt.Sprintf("missing autoEnabled name in body: %+v", params.Body) log.WithFields(f).Warn(msg) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index e00cbaa58..4c9872aef 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -129,8 +129,9 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject } } - log.WithFields(f).Debug("Existing GitLab organization with different project SFID - won't be able to update it - will return conflict") - return nil, fmt.Errorf("record already exists") + msg := fmt.Sprintf("record already exists - existing GitLab group with a different project SFID - won't be able to update it") + log.WithFields(f).Debug(msg) + return nil, errors.New(msg) } // No existing records - create one diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 22da09b06..654bcde82 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -168,14 +168,6 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string "projectSFID": projectSFID, } - // Load the GitLab Organization and Repository details - result will be missing CLA Group info and ProjectSFID details - log.WithFields(f).Debugf("loading Gitlab organizations for projectSFID: %s", projectSFID) - orgs, err := s.repo.GetGitLabOrganizations(ctx, projectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warn("problem loading gitlab organizations from the project service") - return nil, err - } - psc := v2ProjectService.GetClient() log.WithFields(f).Debug("loading project details from the project service...") projectServiceRecord, err := psc.GetProject(projectSFID) @@ -193,16 +185,25 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string f["parentProjectSFID"] = parentProjectSFID log.WithFields(f).Debug("located parentProjectID...") + // Load the GitLab Organization and Repository details - result will be missing CLA Group info and ProjectSFID details + log.WithFields(f).Debugf("loading Gitlab organizations for projectSFID: %s", projectSFID) + orgList, err := s.repo.GetGitLabOrganizations(ctx, projectSFID) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem loading gitlab organizations from the project service") + return nil, err + } + log.WithFields(f).Debugf("loaded %d Gitlab organizations for projectSFID: %s", len(orgList.List), projectSFID) + // Our response model out := &models.GitlabProjectOrganizations{ List: make([]*models.GitlabProjectOrganization, 0), } - orgmap := make(map[string]*models.GitlabProjectOrganization) - for _, org := range orgs.List { + orgMap := make(map[string]*models.GitlabProjectOrganization) + for _, org := range orgList.List { autoEnabledCLAGroupName := "" if org.AutoEnabledClaGroupID != "" { - log.WithFields(f).Debugf("Loading CLA Group by ID: %s to obtain the name for GitLab auth enabled CLA Group response", org.AutoEnabledClaGroupID) + log.WithFields(f).Debugf("loading CLA Group by ID: %s to obtain the name for GitLab auth enabled CLA Group response", org.AutoEnabledClaGroupID) claGroupMode, claGroupLookupErr := s.claGroupRepository.GetCLAGroup(ctx, org.AutoEnabledClaGroupID) if claGroupLookupErr != nil { log.WithFields(f).WithError(claGroupLookupErr).Warnf("Unable to lookup CLA Group by ID: %s", org.AutoEnabledClaGroupID) @@ -212,18 +213,19 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string } } + log.WithFields(f).Debugf("loading GitLab organization by organization ID: %s", org.OrganizationID) orgDetailed, orgErr := s.repo.GetGitLabOrganization(ctx, org.OrganizationID) if orgErr != nil { log.WithFields(f).Errorf("fetching gitlab org failed : %s : %v", org.OrganizationID, orgErr) continue } - reposFromOrg, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByOrganizationName(ctx, org.OrganizationName) + repoList, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByProjectSFID(ctx, projectSFID) if repoErr != nil { - if errors.Is(repoErr, &utils.GitLabRepositoryNotFound{}) { - log.WithFields(f).Debugf("no repositories onboarded for GitLab Org: %s", org.OrganizationName) + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + log.WithFields(f).WithError(repoErr).Debugf("no GitLab repositories onboarded for project : %s", projectSFID) } else { - log.WithFields(f).Debugf("unexpected error while fetching gitlab org repositories for GitLab Org: %s : %v", org.OrganizationName, repoErr) + log.WithFields(f).WithError(repoErr).Debugf("unexpected error while fetching GitLab group repositories for project: %s, error: %v", projectSFID, repoErr) } } @@ -236,9 +238,9 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string OrganizationFullPath: org.OrganizationFullPath, OrganizationExternalID: org.OrganizationExternalID, InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), - BranchProtectionEnabled: false, - ConnectionStatus: "", // updated below - Repositories: []*models.GitlabProjectRepository{}, + BranchProtectionEnabled: false, // TODO review this - why not modeled? + ConnectionStatus: "", // updated below + Repositories: []*models.GitlabProjectRepository{}, // updated below } if orgDetailed.AuthInfo == "" { @@ -254,7 +256,7 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, clientErr) rorg.ConnectionStatus = utils.ConnectionFailure } else { - rorg.Repositories = s.updateRepositoryStatus(glClient, toGitLabProjectResponse(reposFromOrg)) + rorg.Repositories = s.updateRepositoryStatus(glClient, toGitLabProjectResponse(repoList)) user, _, userErr := glClient.Users.CurrentUser() if userErr != nil { @@ -268,7 +270,7 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string } } - orgmap[org.OrganizationName] = rorg + orgMap[org.OrganizationName] = rorg out.List = append(out.List, rorg) } From 88ccf5e358c7dc2cd3b1d5cffb59ebb91394527c Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 20 Aug 2021 11:22:32 -0700 Subject: [PATCH 0446/1276] Resolved GitLab Query Issue + Added Go Routine (#3183) --- .../v2/repositories/gitlab_services.go | 147 +++++++++++++----- 1 file changed, 108 insertions(+), 39 deletions(-) diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 00450c7d8..f5d5a7c77 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -9,6 +9,8 @@ import ( "fmt" "strconv" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + "github.com/communitybridge/easycla/cla-backend-go/events" v2GitLabOrg "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -34,57 +36,119 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, "groupID": input.OrganizationExternalID, } - gitLabOrgModel, orgErr := s.glOrgRepo.GetGitLabOrganizationByName(ctx, input.GitlabOrganizationName) - if orgErr != nil { - msg := fmt.Sprintf("problem loading gitlab organization by name: %s, error: %v", input.GitlabOrganizationName, orgErr) - log.WithFields(f).WithError(orgErr).Warn(msg) + var gitLabOrgModel *common.GitLabOrganization + var getOrgErr error + if input.GitlabOrganizationName != "" { + gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByName(ctx, input.GitlabOrganizationName) + if getOrgErr != nil { + msg := fmt.Sprintf("problem loading GitLab organization by name: %s, error: %v", input.GitlabOrganizationName, getOrgErr) + log.WithFields(f).WithError(getOrgErr).Warn(msg) + return nil, errors.New(msg) + } + } else if input.OrganizationFullPath != "" { + gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) + if getOrgErr != nil { + msg := fmt.Sprintf("problem loading GitLab organization by full path: %s, error: %v", input.OrganizationFullPath, getOrgErr) + log.WithFields(f).WithError(getOrgErr).Warn(msg) + return nil, errors.New(msg) + } + } + if gitLabOrgModel == nil { + msg := fmt.Sprintf("problem loading GitLab organization by name '%s' or full path '%s'", input.GitlabOrganizationName, input.OrganizationFullPath) + log.WithFields(f).Warn(msg) return nil, errors.New(msg) } + log.WithFields(f).Debugf("successfully loading GitLab group/organization") // Get the client gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) if err != nil { - return nil, fmt.Errorf("initializing gitlab client : %v", err) + return nil, fmt.Errorf("initializing GitLab client : %v", err) } - for _, gitLabProjectID := range input.RepositoryGitlabIds { - project, getProjectErr := gitlab_api.GetProjectByID(ctx, gitLabClient, int(gitLabProjectID)) // ok to down-cast as the IDs are not 64 bit - if getProjectErr != nil { - return nil, fmt.Errorf("unable to load project by ID: %d, error: %v", int(gitLabProjectID), getProjectErr) - } + type GitLabAddRepositoryResponse struct { + RepositoryName string + RepositoryFullPath string + Error error + } + addRepoRespChan := make(chan *GitLabAddRepositoryResponse, len(input.RepositoryGitlabIds)) - // Convert int to string - repositoryExternalIDString := strconv.Itoa(project.ID) - - inputDBModel := &repoModels.RepositoryDBModel{ - RepositorySfdcID: projectSFID, - ProjectSFID: projectSFID, - RepositoryExternalID: repositoryExternalIDString, - RepositoryName: project.Name, - RepositoryFullPath: project.PathWithNamespace, - RepositoryURL: project.WebURL, - RepositoryOrganizationName: input.GitlabOrganizationName, - RepositoryCLAGroupID: input.ClaGroupID, - RepositoryType: utils.GitLabLower, // should always be gitlab - Enabled: false, // we don't enable by default - } + // Add each repo - could be a lot of repos, so we run this in a go routine + for _, gitLabProjectID := range input.RepositoryGitlabIds { + go func(gitLabProjectID int) { + project, getProjectErr := gitlab_api.GetProjectByID(ctx, gitLabClient, gitLabProjectID) + if getProjectErr != nil { + newErr := fmt.Errorf("unable to load GitLab project using ID: %d, error: %v", gitLabProjectID, getProjectErr) + log.WithFields(f).WithError(newErr) + addRepoRespChan <- &GitLabAddRepositoryResponse{ + Error: newErr, + } + return + } + + // Convert int to string + repositoryExternalIDString := strconv.Itoa(project.ID) + + inputDBModel := &repoModels.RepositoryDBModel{ + RepositorySfdcID: projectSFID, + ProjectSFID: projectSFID, + RepositoryExternalID: repositoryExternalIDString, + RepositoryName: project.Name, + RepositoryFullPath: project.PathWithNamespace, + RepositoryURL: project.WebURL, + RepositoryOrganizationName: input.GitlabOrganizationName, + RepositoryCLAGroupID: input.ClaGroupID, + RepositoryType: utils.GitLabLower, // should always be gitlab + Enabled: false, // we don't enable by default + } + + repoModel, addErr := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, inputDBModel) + if addErr != nil || repoModel == nil { + log.WithFields(f).WithError(addErr).Warnf("problem adding GitLab repository with name: %s, error: %+v", project.PathWithNamespace, addErr) + addRepoRespChan <- &GitLabAddRepositoryResponse{ + Error: addErr, + } + return + } + + // Log the event + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryAdded, + ProjectSFID: projectSFID, + CLAGroupID: input.ClaGroupID, + LfUsername: utils.GetUserNameFromContext(ctx), + EventData: &events.RepositoryAddedEventData{ + RepositoryName: project.PathWithNamespace, // give the full path/name + }, + }) + addRepoRespChan <- &GitLabAddRepositoryResponse{ + RepositoryName: repoModel.RepositoryName, + RepositoryFullPath: repoModel.RepositoryFullPath, + Error: nil, + } + }(int(gitLabProjectID)) // ok to down cast + } - _, addErr := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, inputDBModel) - if addErr != nil { - log.WithFields(f).WithError(addErr).Warnf("problem adding GitLab repository with name: %s, error: %+v", project.PathWithNamespace, addErr) - return nil, addErr + // Wait for the go routines to finish and load up the results + log.WithFields(f).Debug("waiting for add repos to finish...") + var lastErr error + for range input.RepositoryGitlabIds { + select { + case response := <-addRepoRespChan: + if response.Error != nil { + log.WithFields(f).WithError(response.Error).Warn(response.Error.Error()) + lastErr = response.Error + } else { + log.WithFields(f).Debugf("added repo: %s with full path: %s", response.RepositoryName, response.RepositoryFullPath) + } + case <-ctx.Done(): + log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for CLA Groups to load timeouted") + lastErr = fmt.Errorf("cla group laoding failed : %v", ctx.Err()) } + } - // Log the event - s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryAdded, - ProjectSFID: projectSFID, - CLAGroupID: input.ClaGroupID, - LfUsername: utils.GetUserNameFromContext(ctx), - EventData: &events.RepositoryAddedEventData{ - RepositoryName: project.PathWithNamespace, // give the full path/name - }, - }) + if lastErr != nil { + return nil, lastErr } return s.GitLabGetRepositoriesByProjectSFID(ctx, projectSFID) @@ -135,16 +199,21 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel OrganizationFullPath: gitLabOrgModel.OrganizationFullPath, RepositoryGitlabIds: listProjectIDs, } + log.WithFields(f).Debugf("adding %d GitLab repositories", len(listProjectIDs)) _, addRepoErr := s.GitLabAddRepositories(ctx, gitLabOrgModel.ProjectSFID, input) if addRepoErr != nil { + log.WithFields(f).WithError(addRepoErr).Warnf("problem adding %d GitLab repositories", len(listProjectIDs)) return nil, addRepoErr } // Return the list of repos to caller + log.WithFields(f).Debugf("fetching complete repository list by project SFID: %s", gitLabOrgModel.ProjectSFID) dbModels, getRepoErr := s.gitV2Repository.GitHubGetRepositoriesByProjectSFID(ctx, gitLabOrgModel.ProjectSFID) if getRepoErr != nil { + log.WithFields(f).WithError(getRepoErr).Warnf("problem fetching repositories by project SFID: %s", gitLabOrgModel.ProjectSFID) return nil, getRepoErr } + return dbModelsToGitLabRepositories(dbModels) } From d75ceb1b83df776ccbdd6390551f3cf561de5f4c Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 20 Aug 2021 12:42:30 -0700 Subject: [PATCH 0447/1276] Added Org Name in Callback Workflow (#3184) Signed-off-by: David Deal --- .../v2/gitlab_organizations/repository.go | 78 +++++++++++-------- .../v2/gitlab_organizations/service.go | 2 +- .../v2/repositories/gitlab_services.go | 6 +- cla-backend-go/v2/repositories/service.go | 4 +- 4 files changed, 52 insertions(+), 38 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 4c9872aef..7a077cf25 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -20,7 +20,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/aws/aws-sdk-go/service/dynamodb/expression" - models2 "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" @@ -40,13 +40,13 @@ const ( // RepositoryInterface is interface for gitlab org data model type RepositoryInterface interface { - AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) - GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) + AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, groupName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) + GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) - UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error + UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } @@ -68,7 +68,7 @@ func NewRepository(awsSession *session.Session, stage string) RepositoryInterfac } // AddGitLabOrganization adds the specified values to the GitLab Group/Org table -func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*models2.GitlabOrganization, error) { +func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.AddGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -138,13 +138,13 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject _, currentTime := utils.CurrentTime() organizationID, err := uuid.NewV4() if err != nil { - log.WithFields(f).WithError(err).Warnf("Unable to generate a UUID for gitlab org, error: %v", err) + log.WithFields(f).WithError(err).Warnf("Unable to generate a UUID for gitlab org, error: %v2Models", err) return nil, err } authStateNonce, err := uuid.NewV4() if err != nil { - log.WithFields(f).WithError(err).Warnf("Unable to generate a auth nonce UUID for gitlab org, error: %v", err) + log.WithFields(f).WithError(err).Warnf("Unable to generate a auth nonce UUID for gitlab org, error: %v2Models", err) return nil, err } @@ -196,7 +196,7 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject } // GetGitLabOrganizations get GitLab organizations based on the project SFID -func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models2.GitlabOrganizations, error) { +func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.GetGitLabOrganizations", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -212,7 +212,7 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { - log.WithFields(f).Warnf("problem building query expression, error: %+v", err) + log.WithFields(f).Warnf("problem building query expression, error: %+v2Models", err) return nil, err } @@ -235,8 +235,8 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID if len(results.Items) == 0 { log.WithFields(f).Debug("no results from query") - return &models2.GitlabOrganizations{ - List: []*models2.GitlabOrganization{}, + return &v2Models.GitlabOrganizations{ + List: []*v2Models.GitlabOrganization{}, }, nil } @@ -248,7 +248,7 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID log.WithFields(f).Debug("building response model...") gitlabOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) - return &models2.GitlabOrganizations{List: gitlabOrgList}, nil + return &v2Models.GitlabOrganizations{List: gitlabOrgList}, nil } // GetGitLabOrganizationByName get GitLab organization by name @@ -293,7 +293,7 @@ func (repo *Repository) GetGitLabOrganizationByName(ctx context.Context, gitLabO var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { - log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) + log.WithFields(f).Warnf("problem decoding database results, error: %+v2Models", err) return nil, err } @@ -341,7 +341,7 @@ func (repo *Repository) GetGitLabOrganizationByExternalID(ctx context.Context, g var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { - log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) + log.WithFields(f).Warnf("problem decoding database results, error: %+v2Models", err) return nil, err } @@ -389,7 +389,7 @@ func (repo *Repository) GetGitLabOrganizationByFullPath(ctx context.Context, gro var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { - log.WithFields(f).Warnf("problem decoding database results, error: %+v", err) + log.WithFields(f).Warnf("problem decoding database results, error: %+v2Models", err) return nil, err } @@ -424,27 +424,28 @@ func (repo *Repository) GetGitLabOrganization(ctx context.Context, gitLabOrganiz var org common.GitLabOrganization err = dynamodbattribute.UnmarshalMap(result.Item, &org) if err != nil { - log.WithFields(f).Warnf("error unmarshalling organization table data, error: %v", err) + log.WithFields(f).Warnf("error unmarshalling organization table data, error: %v2Models", err) return nil, err } return &org, nil } // UpdateGitLabOrganizationAuth updates the specified Gitlab organization oauth info -func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error { +func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationAuth", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "organizationID": organizationID, - "organizationFullPath": organizationFullPath, - "organizationURL": organizationURL, - "tableName": repo.gitlabOrgTableName, + "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationAuth", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "organizationID": organizationID, + "groupName": groupName, + "groupFullPath": groupFullPath, + "organizationURL": organizationURL, + "tableName": repo.gitlabOrgTableName, } _, currentTime := utils.CurrentTime() gitlabOrg, lookupErr := repo.GetGitLabOrganization(ctx, organizationID) if lookupErr != nil || gitlabOrg == nil { - log.WithFields(f).Warnf("error looking up Gitlab organization by id: %s, error: %+v", organizationID, lookupErr) + log.WithFields(f).Warnf("error looking up Gitlab organization by id: %s, error: %+v2Models", organizationID, lookupErr) return lookupErr } @@ -463,7 +464,7 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi S: aws.String(organizationURL), }, ":fp": { - S: aws.String(organizationFullPath), + S: aws.String(groupFullPath), }, ":m": { S: aws.String(currentTime), @@ -472,9 +473,18 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi N: aws.String(strconv.Itoa(gitLabGroupID)), }, } - updateExpression := "SET #A = :a, #U = :u, #FP = :fp, #M = :m, #P = :p" + if groupName != "" { + expressionAttributeNames["#N"] = aws.String(GitLabOrganizationsOrganizationNameColumn) + expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(groupName)} + updateExpression = fmt.Sprintf("%s, #N = :n ", updateExpression) + + expressionAttributeNames["#NL"] = aws.String(GitLabOrganizationsOrganizationNameColumn) + expressionAttributeValues[":nl"] = &dynamodb.AttributeValue{S: aws.String(strings.ToLower(groupName))} + updateExpression = fmt.Sprintf("%s, #NL = :nl ", updateExpression) + } + input := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ GitLabOrganizationsOrganizationIDColumn: { @@ -490,7 +500,7 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi log.WithFields(f).Debug("updating gitlab organization record...") _, updateErr := repo.dynamoDBClient.UpdateItem(input) if updateErr != nil { - log.WithFields(f).Warnf("unable to update Gitlab organization record, error: %+v", updateErr) + log.WithFields(f).Warnf("unable to update Gitlab organization record, error: %+v2Models", updateErr) return updateErr } @@ -518,7 +528,7 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", groupID) existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) if getErr != nil { - msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v", groupID, groupFullPath) + msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v2Models", groupID, groupFullPath) log.WithFields(f).WithError(getErr).Warn(msg) return errors.New(msg) } @@ -526,7 +536,7 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", groupFullPath) existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) if getErr != nil { - msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v", groupFullPath, getErr) + msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v2Models", groupFullPath, getErr) log.WithFields(f).WithError(getErr).Warn(msg) return errors.New(msg) } @@ -587,10 +597,10 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI TableName: aws.String(repo.gitlabOrgTableName), } - log.WithFields(f).Debugf("updating GitLab organization record: %+v", input) + log.WithFields(f).Debugf("updating GitLab organization record: %+v2Models", input) _, updateErr := repo.dynamoDBClient.UpdateItem(input) if updateErr != nil { - log.WithFields(f).Warnf("unable to update GitLab organization record, error: %+v", updateErr) + log.WithFields(f).Warnf("unable to update GitLab organization record, error: %+v2Models", updateErr) return updateErr } @@ -609,7 +619,7 @@ func (repo *Repository) DeleteGitLabOrganization(ctx context.Context, projectSFI var gitlabOrganizationID string orgs, orgErr := repo.GetGitLabOrganizations(ctx, projectSFID) if orgErr != nil { - errMsg := fmt.Sprintf("gitlab organization is not found using projectSFID: %s, error: %+v", projectSFID, orgErr) + errMsg := fmt.Sprintf("gitlab organization is not found using projectSFID: %s, error: %+v2Models", projectSFID, orgErr) log.WithFields(f).Warn(errMsg) return errors.New(errMsg) } @@ -653,7 +663,7 @@ func (repo *Repository) DeleteGitLabOrganization(ctx context.Context, projectSFI }, ) if err != nil { - errMsg := fmt.Sprintf("error deleting gitlab organization: %s - %+v", gitlabOrgName, err) + errMsg := fmt.Sprintf("error deleting gitlab organization: %s - %+v2Models", gitlabOrgName, err) log.WithFields(f).Warnf(errMsg) return errors.New(errMsg) } @@ -661,7 +671,7 @@ func (repo *Repository) DeleteGitLabOrganization(ctx context.Context, projectSFI return nil } -func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*common.GitLabOrganization) []*models2.GitlabOrganization { +func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*common.GitLabOrganization) []*v2Models.GitlabOrganization { f := logrus.Fields{ "functionName": "buildGitlabOrganizationListModels", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 654bcde82..b23e824f2 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -345,7 +345,7 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani if (gitLabOrgModel.ExternalGroupID > 0 && g.ID == gitLabOrgModel.ExternalGroupID) || (gitLabOrgModel.OrganizationFullPath != "" && g.FullPath == gitLabOrgModel.OrganizationFullPath) { - updateGitLabOrgErr := s.repo.UpdateGitLabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.FullPath, g.WebURL) + updateGitLabOrgErr := s.repo.UpdateGitLabOrganizationAuth(ctx, gitLabOrganizationID, g.ID, authInfoEncrypted, g.Name, g.FullPath, g.WebURL) if updateGitLabOrgErr != nil { return updateGitLabOrgErr } diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index f5d5a7c77..e5713c6e8 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -39,6 +39,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, var gitLabOrgModel *common.GitLabOrganization var getOrgErr error if input.GitlabOrganizationName != "" { + log.WithFields(f).Debugf("fetching GitLab organization by name: %s", input.GitlabOrganizationName) gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByName(ctx, input.GitlabOrganizationName) if getOrgErr != nil { msg := fmt.Sprintf("problem loading GitLab organization by name: %s, error: %v", input.GitlabOrganizationName, getOrgErr) @@ -46,6 +47,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, return nil, errors.New(msg) } } else if input.OrganizationFullPath != "" { + log.WithFields(f).Debugf("fetching GitLab organization by full path: %s", input.OrganizationFullPath) gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) if getOrgErr != nil { msg := fmt.Sprintf("problem loading GitLab organization by full path: %s, error: %v", input.OrganizationFullPath, getOrgErr) @@ -58,7 +60,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, log.WithFields(f).Warn(msg) return nil, errors.New(msg) } - log.WithFields(f).Debugf("successfully loading GitLab group/organization") + log.WithFields(f).Debugf("successfully loaded GitLab group/organization") // Get the client gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) @@ -76,6 +78,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, // Add each repo - could be a lot of repos, so we run this in a go routine for _, gitLabProjectID := range input.RepositoryGitlabIds { go func(gitLabProjectID int) { + log.WithFields(f).Debugf("loading GitLab project from GitLab using projectID: %d...", gitLabProjectID) project, getProjectErr := gitlab_api.GetProjectByID(ctx, gitLabClient, gitLabProjectID) if getProjectErr != nil { newErr := fmt.Errorf("unable to load GitLab project using ID: %d, error: %v", gitLabProjectID, getProjectErr) @@ -85,6 +88,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, } return } + log.WithFields(f).Debugf("loaded GitLab project from GitLab using projectID: %d", gitLabProjectID) // Convert int to string repositoryExternalIDString := strconv.Itoa(project.ID) diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 118218149..877ee9829 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -67,13 +67,13 @@ type ServiceInterface interface { // GithubOrgRepo redefine the interface here to avoid circular dependency issues type GithubOrgRepo interface { - AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) + AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, groupName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) - UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, organizationFullPath, organizationURL string) error + UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error } From 2ff4f665470021953ba4e155048ad43c597041be Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 20 Aug 2021 13:08:50 -0700 Subject: [PATCH 0448/1276] Resolved Duplicate Key Issue in GL Repo (#3185) Signed-off-by: David Deal --- cla-backend-go/v2/gitlab_organizations/repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 7a077cf25..54d206003 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -480,7 +480,7 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(groupName)} updateExpression = fmt.Sprintf("%s, #N = :n ", updateExpression) - expressionAttributeNames["#NL"] = aws.String(GitLabOrganizationsOrganizationNameColumn) + expressionAttributeNames["#NL"] = aws.String(GitLabOrganizationsOrganizationNameLowerColumn) expressionAttributeValues[":nl"] = &dynamodb.AttributeValue{S: aws.String(strings.ToLower(groupName))} updateExpression = fmt.Sprintf("%s, #NL = :nl ", updateExpression) } From 585abee79ab5a7738fbb99d67e23928cdb90d250 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 20 Aug 2021 17:07:01 -0700 Subject: [PATCH 0449/1276] Updated GitLab Repo Enrol Unenroll (#3186) --- cla-backend-go/events/event_data.go | 31 +- cla-backend-go/go.mod | 25 -- cla-backend-go/go.sum | 300 ------------------ cla-backend-go/repositories/constants.go | 6 + cla-backend-go/swagger/cla.v2.yaml | 30 +- ...d.yaml => gitlab-repositories-enable.yaml} | 16 +- cla-backend-go/utils/errors.go | 26 +- .../v2/repositories/gitlab_services.go | 70 +++- cla-backend-go/v2/repositories/handlers.go | 99 +++--- cla-backend-go/v2/repositories/repository.go | 97 ++++-- cla-backend-go/v2/repositories/service.go | 8 +- 11 files changed, 272 insertions(+), 436 deletions(-) rename cla-backend-go/swagger/common/{gitlab-repositories-add.yaml => gitlab-repositories-enable.yaml} (94%) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 130fc63fa..2175748a0 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -44,7 +44,8 @@ type RepositoryAddedEventData struct { // RepositoryDisabledEventData event data model type RepositoryDisabledEventData struct { - RepositoryName string + RepositoryName string + RepositoryExternalID int64 } // RepositoryRenamedEventData event data model @@ -482,7 +483,20 @@ func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (s // GetEventDetailsString returns the details string for this event func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was deleted for the project %s", ed.RepositoryName, args.ProjectName) + data := "The GitHub repository " // nolint + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if ed.RepositoryName != "" { + data = data + fmt.Sprintf(" with repository name %s", ed.RepositoryName) + } + if ed.RepositoryExternalID > 0 { + data = data + fmt.Sprintf(" with repository external ID %d", ed.RepositoryExternalID) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectSFID) + } + data = data + " was disabled" if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -1452,13 +1466,20 @@ func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString returns the summary string for this event func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was deleted", ed.RepositoryName) + data := "The GitHub repository " // nolint if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } - if args.ProjectName != "" { - data = data + fmt.Sprintf(" for the project %s", args.ProjectName) + if ed.RepositoryName != "" { + data = data + fmt.Sprintf(" with repository name %s", ed.RepositoryName) + } + if ed.RepositoryExternalID > 0 { + data = data + fmt.Sprintf(" with repository external ID %d", ed.RepositoryExternalID) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectSFID) } + data = data + " was disabled" if args.CompanyName != "" { data = data + fmt.Sprintf(" for the company %s", args.CompanyName) } diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 1c4bfd89f..c737d1c57 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -16,16 +16,10 @@ require ( github.com/bitly/go-simplejson v0.5.0 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 github.com/bradleyfalzon/ghinstallation v1.1.1 - github.com/coreos/bbolt v1.3.2 // indirect - github.com/coreos/etcd v3.3.13+incompatible // indirect - github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fnproject/fdk-go v0.0.2 github.com/gin-gonic/gin v1.7.2 - github.com/go-delve/delve v1.7.0 // indirect github.com/go-openapi/errors v0.19.6 github.com/go-openapi/loads v0.19.5 github.com/go-openapi/runtime v0.19.19 @@ -40,50 +34,32 @@ require ( github.com/google/go-github/v37 v37.0.0 github.com/google/uuid v1.1.4 github.com/gorilla/sessions v1.2.1 // indirect - github.com/gorilla/websocket v1.4.2 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/imroc/req v0.3.0 github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 - github.com/jonboulle/clockwork v0.1.0 // indirect github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f // indirect github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 github.com/kr/pretty v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/mozillazg/request v0.8.0 // indirect - github.com/myitcv/gobin v0.0.14 // indirect github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc - github.com/peterh/liner v1.2.1 // indirect - github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0 // indirect - github.com/prometheus/client_golang v0.9.3 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/rs/cors v1.7.0 github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect github.com/sirupsen/logrus v1.8.1 - github.com/soheilhy/cmux v0.1.4 // indirect github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 - github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/ugorji/go v1.2.6 // indirect github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a github.com/xanzy/go-gitlab v0.50.1 - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.3.2 // indirect - go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a // indirect go.uber.org/ratelimit v0.1.0 - golang.org/x/arch v0.0.0-20210727222714-28578f966459 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 @@ -92,5 +68,4 @@ require ( golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/resty.v1 v1.12.0 // indirect ) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 690e61600..7a0dc864a 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -17,38 +17,28 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -56,29 +46,17 @@ github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJ github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -92,71 +70,35 @@ github.com/aws/aws-sdk-go v1.36.27 h1:wc3xLJJHog2SwiqlLnrLUuct/n+dBjB45QhuZw2psV github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= -github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -165,18 +107,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -185,22 +122,11 @@ github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2 github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-delve/delve v1.7.0 h1:MaWAD3LtvjE/LL98urSHPjaMT+OubpQ2sqF3R2Uj1rc= -github.com/go-delve/delve v1.7.0/go.mod h1:2DpgGoHOW7r7MXyykmT7axp9IEEIc8EV/swa5m8rkbo= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -280,58 +206,38 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -361,10 +267,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -379,20 +283,15 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-dap v0.5.0 h1:RMHAVn5xeunBakYk65ggHXttk6qjZVdbmi+xhAoL2wY= -github.com/google/go-dap v0.5.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3bOQ/tM= github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -404,11 +303,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -417,70 +313,41 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -488,7 +355,6 @@ github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= @@ -498,7 +364,6 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -506,17 +371,12 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -524,49 +384,31 @@ github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2h github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= -github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -575,40 +417,22 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -621,141 +445,75 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/myitcv/gobin v0.0.14 h1:YkTUz0IeRspEJlly/+AXRBMA3GN7ArRVbsLJ1uYFwRk= -github.com/myitcv/gobin v0.0.14/go.mod h1:GvHEiYCWroKI2KrMT+xQkHC3FC551wigVWeR4Sgg5P4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= -github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0 h1:wBza4Dlm/NCQF572oSGNZ69flNFxlwIHjtwS6oy3Rvw= -github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= -github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -770,8 +528,6 @@ github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 h1:xp/21gmSP github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8/go.mod h1:K3DbqPpP2WE/9MWokWWzgFZcbgtMb9Wd5CYk9AAbEN8= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v0.0.0-20180129160544-d2b24cf3d3b4/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= @@ -779,37 +535,23 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -822,25 +564,13 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= -go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a h1:wDtSCWGrX9tusypq2Qq9xzaA3Tf/+4D2KaWO+HQvGZE= -go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= -golang.org/x/arch v0.0.0-20210727222714-28578f966459 h1:ECTRghTMeoUryGydSc+nr1o4M2i73DwlP4LFEDJb3II= -golang.org/x/arch v0.0.0-20210727222714-28578f966459/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -865,7 +595,6 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -884,10 +613,8 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -897,15 +624,12 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -976,8 +700,6 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1012,7 +734,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1031,7 +752,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1047,7 +767,6 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1076,7 +795,6 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1108,7 +826,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1136,7 +853,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1187,7 +903,6 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1208,7 +923,6 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1224,22 +938,14 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1259,13 +965,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cla-backend-go/repositories/constants.go b/cla-backend-go/repositories/constants.go index 40ab63317..69e937a88 100644 --- a/cla-backend-go/repositories/constants.go +++ b/cla-backend-go/repositories/constants.go @@ -3,12 +3,18 @@ package repositories +// RepositoryIDColumn constant +const RepositoryIDColumn = "repository_id" + // RepositoryNameColumn constant const RepositoryNameColumn = "repository_name" // RepositoryTypeColumn constant const RepositoryTypeColumn = "repository_type" +// RepositoryExternalIDColumn constant +const RepositoryExternalIDColumn = "repository_external_id" + // RepositoryProjectIDColumn constant const RepositoryProjectIDColumn = "project_sfid" diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 05ee68c21..24bd81f6d 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1763,10 +1763,10 @@ paths: - gitlab-organizations /project/{projectSFID}/gitlab/repositories: - post: - summary: Add a GitLab repository to the project - description: Endpoint to add a GitLab repository for the project - operationId: addProjectGitLabRepository + put: + summary: Enables GitLab repositories for the CLA Group + description: Endpoint to enable one or more GitLab repositories for the CLA Group + operationId: enableGitLabRepository parameters: - $ref: "#/parameters/x-request-id" - $ref: "#/parameters/x-acl" @@ -1777,9 +1777,9 @@ paths: type: string required: true - in: body - name: gitlab-repositories-add + name: gitlab-repositories-enable schema: - $ref: '#/definitions/gitlab-repositories-add' + $ref: '#/definitions/gitlab-repositories-enable' required: true responses: '200': @@ -1798,8 +1798,6 @@ paths: $ref: '#/responses/forbidden' '404': $ref: '#/responses/not-found' - '409': - $ref: '#/responses/conflict' '500': $ref: '#/responses/internal-server-error' tags: @@ -1839,11 +1837,11 @@ paths: tags: - gitlab-repositories - /project/{projectSFID}/gitlab/repositories/{repositoryID}: + /project/{projectSFID}/gitlab/repositories/{repositoryExternalID}: delete: - summary: Remove the GitLab repository from the project - description: Endpoint to remove a GitLab repository from a project - operationId: deleteProjectGitLabRepository + summary: Unenrolls the GitLab repository from the CLA Group + description: Endpoint to unenroll a GitLab repository from a CLA Group + operationId: unenrollGitLabRepository parameters: - $ref: "#/parameters/x-request-id" - $ref: "#/parameters/x-acl" @@ -1853,9 +1851,9 @@ paths: in: path type: string required: true - - name: repositoryID + - name: repositoryExternalID in: path - type: string + type: integer required: true responses: '204': @@ -4504,8 +4502,8 @@ definitions: gitlab-repositories-list: $ref: './common/gitlab-repositories-list.yaml' - gitlab-repositories-add: - $ref: './common/gitlab-repositories-add.yaml' + gitlab-repositories-enable: + $ref: './common/gitlab-repositories-enable.yaml' # --------------------------------------------------------------------------- # CLA Group Definitions diff --git a/cla-backend-go/swagger/common/gitlab-repositories-add.yaml b/cla-backend-go/swagger/common/gitlab-repositories-enable.yaml similarity index 94% rename from cla-backend-go/swagger/common/gitlab-repositories-add.yaml rename to cla-backend-go/swagger/common/gitlab-repositories-enable.yaml index 33da7ac31..e28d91c14 100644 --- a/cla-backend-go/swagger/common/gitlab-repositories-add.yaml +++ b/cla-backend-go/swagger/common/gitlab-repositories-enable.yaml @@ -2,15 +2,8 @@ # SPDX-License-Identifier: MIT type: object -description: 'GitLab repositories add model' +description: 'GitLab repositories enable model' properties: - repository_gitlab_ids: - type: array - items: - description: the repository external identifier, such as the GitLab ID of the repository - type: integer - minimum: 1 - example: 7 gitlab_organization_name: type: string description: The organization name associated with this repository @@ -27,3 +20,10 @@ properties: cla_group_id: description: CLA Group ID $ref: './common/properties/internal-id.yaml' + repository_gitlab_ids: + type: array + items: + description: the repository external identifier, such as the GitLab ID of the repository + type: integer + minimum: 1 + example: 7 diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index 3c80e7418..67193de67 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -354,12 +354,13 @@ func (e *GitHubRepositoryExists) Unwrap() error { // GitLabRepositoryNotFound is an error model for a GitLab repository not found type GitLabRepositoryNotFound struct { - Message string - OrganizationName string - RepositoryName string - ProjectSFID string - CLAGroupID string - Err error + Message string + OrganizationName string + RepositoryName string + RepositoryExternalID int64 + ProjectSFID string + CLAGroupID string + Err error } // Error is an error string function for the GitHubRepositoryNotFound model @@ -374,6 +375,9 @@ func (e *GitLabRepositoryNotFound) Error() string { if e.RepositoryName != "" { msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) } + if e.RepositoryExternalID > 0 { + msg = fmt.Sprintf("%s - repository external ID: %d ", msg, e.RepositoryExternalID) + } if e.ProjectSFID != "" { msg = fmt.Sprintf("%s - project SFID: %s ", msg, e.ProjectSFID) } @@ -394,9 +398,10 @@ func (e *GitLabRepositoryNotFound) Unwrap() error { // GitLabDuplicateRepositoriesFound is an error model for a GitLab duplicate repositories found type GitLabDuplicateRepositoriesFound struct { - Message string - RepositoryName string - Err error + Message string + RepositoryName string + RepositoryExternalID int64 + Err error } // Error is an error string function for the GitLabDuplicateRepositoriesFound model @@ -408,6 +413,9 @@ func (e *GitLabDuplicateRepositoriesFound) Error() string { if e.RepositoryName != "" { msg = fmt.Sprintf("%s - repository: %s ", msg, e.RepositoryName) } + if e.RepositoryExternalID > 0 { + msg = fmt.Sprintf("%s - repository external ID: %d ", msg, e.RepositoryExternalID) + } if e.Err != nil { msg = fmt.Sprintf("%s - error: %+v ", msg, e.Err.Error()) } diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index e5713c6e8..77bc099b0 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -25,7 +25,7 @@ import ( ) // GitLabAddRepositories service function -func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesAdd) (*v2Models.GitlabRepositoriesList, error) { +func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesEnable) (*v2Models.GitlabRepositoriesList, error) { f := logrus.Fields{ "functionName": "v2.repositories.gitlab_services.GitLabAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -146,8 +146,8 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, log.WithFields(f).Debugf("added repo: %s with full path: %s", response.RepositoryName, response.RepositoryFullPath) } case <-ctx.Done(): - log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for CLA Groups to load timeouted") - lastErr = fmt.Errorf("cla group laoding failed : %v", ctx.Err()) + log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for add repositories timed out") + lastErr = fmt.Errorf("add repositories failed with timeout, error: %v", ctx.Err()) } } @@ -196,7 +196,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel } // Build input to the add function - input := &v2Models.GitlabRepositoriesAdd{ + input := &v2Models.GitlabRepositoriesEnable{ ClaGroupID: projectCLAGroupModel.ClaGroupID, GitlabOrganizationName: gitLabOrgModel.OrganizationName, OrganizationExternalID: int64(gitLabOrgModel.ExternalGroupID), @@ -241,6 +241,16 @@ func (s *Service) GitLabGetRepositoryByName(ctx context.Context, repositoryName return dbModelToGitLabRepository(dbModel) } +// GitLabGetRepositoryByExternalID service function +func (s *Service) GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*v2Models.GitlabRepository, error) { + dbModel, err := s.gitV2Repository.GitLabGetRepositoryByExternalID(ctx, repositoryExternalID) + if err != nil { + return nil, err + } + + return dbModelToGitLabRepository(dbModel) +} + // GitLabGetRepositoriesByProjectSFID service function func (s *Service) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabRepositoriesList, error) { dbModel, err := s.gitV2Repository.GitHubGetRepositoriesByProjectSFID(ctx, projectSFID) @@ -298,14 +308,58 @@ func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, o }, nil } +// GitLabEnableRepositories assigns repos to +func (s *Service) GitLabEnableRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64) error { + f := logrus.Fields{ + "functionName": "v2.repositories.gitlab_services.GitLabEnableRepositories", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + type GitLabUpdateRepositoryResponse struct { + RepositoryID int64 + Error error + } + updateRepoChan := make(chan *GitLabUpdateRepositoryResponse, len(repositoryIDList)) + + for _, repoID := range repositoryIDList { + go func(claGroupID string, repoID int64) { + updateErr := s.GitLabEnableRepository(ctx, claGroupID, repoID) + updateRepoChan <- &GitLabUpdateRepositoryResponse{ + RepositoryID: repoID, + Error: updateErr, + } + }(claGroupID, repoID) + } + + // Wait for the go routines to finish and load up the results + log.WithFields(f).Debug("waiting for update repos to finish...") + var lastErr error + for range repositoryIDList { + select { + case response := <-updateRepoChan: + if response.Error != nil { + log.WithFields(f).WithError(response.Error).Warn(response.Error.Error()) + lastErr = response.Error + } else { + log.WithFields(f).Debugf("updated repo with ID: %d", response.RepositoryID) + } + case <-ctx.Done(): + log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for update GitLab repos timed out") + lastErr = fmt.Errorf("waiting for update GitLab repositories timed out, error: %v", ctx.Err()) + } + } + + return lastErr +} + // GitLabEnableRepository service function -func (s *Service) GitLabEnableRepository(ctx context.Context, repositoryID string) error { - return s.gitV2Repository.GitLabEnableRepositoryByID(ctx, repositoryID) +func (s *Service) GitLabEnableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error { + return s.gitV2Repository.GitLabEnableRepositoryByID(ctx, claGroupID, repositoryExternalID) } // GitLabDisableRepository service function -func (s *Service) GitLabDisableRepository(ctx context.Context, repositoryID string) error { - return s.gitV2Repository.GitLabDisableRepositoryByID(ctx, repositoryID) +func (s *Service) GitLabDisableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error { + return s.gitV2Repository.GitLabDisableRepositoryByID(ctx, claGroupID, repositoryExternalID) } // GitLabDisableCLAGroupRepositories service function diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 8f57c8c80..afa3223d9 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -8,6 +8,9 @@ import ( "fmt" "strings" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_organizations" + project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_repositories" "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" @@ -406,102 +409,112 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_repositories.NewGetProjectGitLabRepositoriesOK().WithPayload(response) }) - api.GitlabRepositoriesAddProjectGitLabRepositoryHandler = gitlab_repositories.AddProjectGitLabRepositoryHandlerFunc( - func(params gitlab_repositories.AddProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { + api.GitlabRepositoriesEnableGitLabRepositoryHandler = gitlab_repositories.EnableGitLabRepositoryHandlerFunc( + func(params gitlab_repositories.EnableGitLabRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ - "functionName": "v2.repositories.handlers.GitlabRepositoriesAddProjectGitLabRepositoryHandler", + "functionName": "v2.repositories.handlers.GitlabRepositoriesEnableGitLabRepositoryHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "authUser": authUser.UserName, "authEmail": authUser.Email, "projectSFID": params.ProjectSFID, - "organizationName": params.GitlabRepositoriesAdd.GitlabOrganizationName, - "claGroupID": params.GitlabRepositoriesAdd.ClaGroupID, - "groupFullPath": params.GitlabRepositoriesAdd.OrganizationFullPath, - "groupID": params.GitlabRepositoriesAdd.OrganizationExternalID, + "organizationName": params.GitlabRepositoriesEnable.GitlabOrganizationName, + "claGroupID": params.GitlabRepositoriesEnable.ClaGroupID, + "groupFullPath": params.GitlabRepositoriesEnable.OrganizationFullPath, + "groupID": params.GitlabRepositoriesEnable.OrganizationExternalID, + } + + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Add GitLab Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Add GitLab Repositories for Project '%s' with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) - return gitlab_repositories.NewAddProjectGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + return gitlab_repositories.NewEnableGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - if len(params.GitlabRepositoriesAdd.RepositoryGitlabIds) == 0 { + if len(params.GitlabRepositoriesEnable.RepositoryGitlabIds) == 0 { msg := "missing repository GitLab ID values" - return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + return gitlab_repositories.NewEnableGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - log.WithFields(f).Debugf("Adding GitLab repository for project: %s", params.ProjectSFID) - result, err := service.GitLabAddRepositories(ctx, params.ProjectSFID, params.GitlabRepositoriesAdd) - if err != nil { - if _, ok := err.(*utils.GitLabRepositoryExists); ok { - msg := fmt.Sprintf("unable to add repository - repository already exists for projectSFID: %s, err: %+v", params.ProjectSFID, err) - log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewAddProjectGitLabRepositoryConflict().WithXRequestID(reqID).WithPayload(utils.ErrorResponseConflictWithError(reqID, msg, err)) - } + log.WithFields(f).Debugf("assigning GitLab repository for project: %s and CLA Group: %s", params.ProjectSFID, params.GitlabRepositoriesEnable.ClaGroupID) + enableErr := service.GitLabEnableRepositories(ctx, params.GitlabRepositoriesEnable.ClaGroupID, params.GitlabRepositoriesEnable.RepositoryGitlabIds) + if enableErr != nil { msg := fmt.Sprintf("problem adding GitLab repositories for projectSFID: %s", params.ProjectSFID) - log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewAddProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + log.WithFields(f).WithError(enableErr).Warn(msg) + return gitlab_repositories.NewEnableGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, enableErr)) + } + + repoList, getErr := service.GitLabGetRepositoriesByProjectSFID(ctx, params.ProjectSFID) + if getErr != nil { + msg := fmt.Sprintf("problem fetching GitLab repositories for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(getErr).Warn(msg) + return gitlab_repositories.NewEnableGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, getErr)) } - return gitlab_repositories.NewAddProjectGitLabRepositoryOK().WithPayload(result) + return gitlab_repositories.NewEnableGitLabRepositoryOK().WithPayload(repoList) }) - api.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler = gitlab_repositories.DeleteProjectGitLabRepositoryHandlerFunc( - func(params gitlab_repositories.DeleteProjectGitLabRepositoryParams, authUser *auth.User) middleware.Responder { + api.GitlabRepositoriesUnenrollGitLabRepositoryHandler = gitlab_repositories.UnenrollGitLabRepositoryHandlerFunc( + func(params gitlab_repositories.UnenrollGitLabRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ - "functionName": "v2.repositories.handlers.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "authUser": authUser.UserName, - "authEmail": authUser.Email, - "projectSFID": params.ProjectSFID, - "repositoryID": params.RepositoryID, + "functionName": "v2.repositories.handlers.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "repositoryExternalID": params.RepositoryExternalID, } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Delete Gitlab Repositories with Project scope of %s", + msg := fmt.Sprintf("user %s does not have access to Unenroll Gitlab Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) log.WithFields(f).Debug(msg) - return gitlab_repositories.NewDeleteProjectGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + return gitlab_repositories.NewUnenrollGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - ghRepo, err := service.GitLabGetRepository(ctx, params.RepositoryID) + ghRepo, err := service.GitLabGetRepositoryByExternalID(ctx, params.RepositoryExternalID) if err != nil { if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { - msg := fmt.Sprintf("repository not found for projectSFID: %s", params.ProjectSFID) + msg := fmt.Sprintf("repository not found for repository external ID: %d", params.RepositoryExternalID) log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewDeleteProjectGitLabRepositoryNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + return gitlab_repositories.NewUnenrollGitLabRepositoryNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) } - msg := fmt.Sprintf("problem looking up repository for projectSFID: %s", params.ProjectSFID) + msg := fmt.Sprintf("problem looking up repository using the repostiory external ID: %d", params.RepositoryExternalID) log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewDeleteProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + return gitlab_repositories.NewUnenrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - err = service.GitLabDisableRepository(ctx, params.RepositoryID) + err = service.GitLabDisableRepository(ctx, "", params.RepositoryExternalID) if err != nil { msg := fmt.Sprintf("problem disabling repository for projectSFID: %s, error: %+v", params.ProjectSFID, err) log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewDeleteProjectGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + return gitlab_repositories.NewUnenrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.RepositoryDisabled, ProjectSFID: params.ProjectSFID, - CLAGroupID: ghRepo.RepositoryClaGroupID, LfUsername: authUser.UserName, EventData: &events.RepositoryDisabledEventData{ - RepositoryName: ghRepo.RepositoryName, + RepositoryName: ghRepo.RepositoryName, + RepositoryExternalID: ghRepo.RepositoryExternalID, }, }) - return gitlab_repositories.NewDeleteProjectGitLabRepositoryNoContent().WithXRequestID(reqID) + return gitlab_repositories.NewUnenrollGitLabRepositoryNoContent().WithXRequestID(reqID) }) } diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index c246b9c7d..c5afe0ca5 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -6,6 +6,7 @@ package repositories import ( "context" "fmt" + "strconv" "strings" "github.com/aws/aws-sdk-go/aws" @@ -22,16 +23,18 @@ import ( // RepositoryInterface interface defines the functions for the GitLab repository data model type RepositoryInterface interface { - GitLabGetRepository(ctx context.Context, repositoryID string) (*repoModels.RepositoryDBModel, error) - GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByCLAGroupEnabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByCLAGroupDisabled(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) ([]*repoModels.RepositoryDBModel, error) GitHubGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) + + GitLabGetRepository(ctx context.Context, repositoryID string) (*repoModels.RepositoryDBModel, error) + GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*repoModels.RepositoryDBModel, error) + GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*repoModels.RepositoryDBModel, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *repoModels.RepositoryDBModel) (*repoModels.RepositoryDBModel, error) - GitLabEnableRepositoryByID(ctx context.Context, repositoryID string) error - GitLabDisableRepositoryByID(ctx context.Context, repositoryID string) error + GitLabEnableRepositoryByID(ctx context.Context, claGroupID string, repositoryID int64) error + GitLabDisableRepositoryByID(ctx context.Context, claGroupID string, repositoryID int64) error GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error } @@ -118,6 +121,32 @@ func (r *Repository) GitLabGetRepositoryByName(ctx context.Context, repositoryNa return record, nil } +// GitLabGetRepositoryByExternalID returns the database model for the specified repository by external ID +func (r *Repository) GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*repoModels.RepositoryDBModel, error) { + str := strconv.FormatInt(repositoryExternalID, 10) + condition := expression.Key(repoModels.RepositoryExternalIDColumn).Equal(expression.Value(str)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) + record, err := r.getRepositoryWithConditionFilter(ctx, condition, filter, repoModels.RepositoryExternalIDIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + RepositoryExternalID: repositoryExternalID, + } + } + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabDuplicateRepositoriesFound); ok { + return nil, &utils.GitLabDuplicateRepositoriesFound{ + RepositoryExternalID: repositoryExternalID, + } + } + // Some other error + return nil, err + } + + return record, nil +} + // GitHubGetRepositoriesByCLAGroup returns the database models for the specified CLA Group ID func (r *Repository) GitHubGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string) ([]*repoModels.RepositoryDBModel, error) { condition := expression.Key(repoModels.RepositoryCLAGroupIDColumn).Equal(expression.Value(claGroupID)) @@ -284,13 +313,13 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string } // GitLabEnableRepositoryByID enables the specified repository -func (r *Repository) GitLabEnableRepositoryByID(ctx context.Context, repositoryID string) error { - return r.setRepositoryEnabledValue(ctx, repositoryID, true) +func (r *Repository) GitLabEnableRepositoryByID(ctx context.Context, claGroupID string, repositoryExternalID int64) error { + return r.setRepositoryEnabledValue(ctx, claGroupID, repositoryExternalID, true) } // GitLabDisableRepositoryByID disables the specified repository -func (r *Repository) GitLabDisableRepositoryByID(ctx context.Context, repositoryID string) error { - return r.setRepositoryEnabledValue(ctx, repositoryID, false) +func (r *Repository) GitLabDisableRepositoryByID(ctx context.Context, claGroupID string, repositoryExternalID int64) error { + return r.setRepositoryEnabledValue(ctx, claGroupID, repositoryExternalID, false) } // GitLabEnableCLAGroupRepositories enables the specified CLA Group repositories @@ -301,7 +330,12 @@ func (r *Repository) GitLabEnableCLAGroupRepositories(ctx context.Context, claGr } for _, repo := range repositories { - enableErr := r.GitLabEnableRepositoryByID(ctx, repo.RepositoryID) + int64I, parseErr := strconv.ParseInt(repo.RepositoryExternalID, 10, 64) + if parseErr != nil { + return parseErr + } + + enableErr := r.GitLabEnableRepositoryByID(ctx, claGroupID, int64I) if enableErr != nil { return enableErr } @@ -318,7 +352,12 @@ func (r *Repository) GitLabDisableCLAGroupRepositories(ctx context.Context, claG } for _, repo := range repositories { - enableErr := r.GitLabDisableRepositoryByID(ctx, repo.RepositoryID) + int64I, parseErr := strconv.ParseInt(repo.RepositoryExternalID, 10, 64) + if parseErr != nil { + return parseErr + } + + enableErr := r.GitLabDisableRepositoryByID(ctx, claGroupID, int64I) if enableErr != nil { return enableErr } @@ -427,14 +466,22 @@ func (r *Repository) getRepositoriesWithConditionFilter(ctx context.Context, con } // setRepositoryEnabledValue sets the specified repository to the specified enabled value -func (r *Repository) setRepositoryEnabledValue(ctx context.Context, repositoryID string, enabledValue bool) error { +func (r *Repository) setRepositoryEnabledValue(ctx context.Context, claGroupID string, repositoryExternalID int64, enabledValue bool) error { + f := logrus.Fields{ + "functionName": "v2.repositories.repository.setRepositoryEnabledValue", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "repositoryExternalID": repositoryExternalID, + "enabledValue": enabledValue, + } + // Load the existing model - need to fetch the old values, if available - existingModel, getErr := r.GitLabGetRepository(ctx, repositoryID) + existingModel, getErr := r.GitLabGetRepositoryByExternalID(ctx, repositoryExternalID) if getErr != nil { return getErr } if existingModel == nil { - return fmt.Errorf("unable to locate existing repository entry by ID: %s", repositoryID) + return fmt.Errorf("unable to locate existing repository entry by external ID: %d", repositoryExternalID) } // If we have an old note - grab it/save it @@ -448,10 +495,7 @@ func (r *Repository) setRepositoryEnabledValue(ctx context.Context, repositoryID } _, now := utils.CurrentTime() - _, err := r.dynamoDBClient.UpdateItem(&dynamodb.UpdateItemInput{ - Key: map[string]*dynamodb.AttributeValue{ - "repository_id": {S: aws.String(repositoryID)}, - }, + updateInput := &dynamodb.UpdateItemInput{ ExpressionAttributeNames: map[string]*string{ "#enabled": aws.String(repoModels.RepositoryEnabledColumn), "#note": aws.String(repoModels.RepositoryNoteColumn), @@ -468,9 +512,24 @@ func (r *Repository) setRepositoryEnabledValue(ctx context.Context, repositoryID S: aws.String(now), }, }, - UpdateExpression: aws.String("SET #enabled = :enabledValue, #note = :noteValue, #dateModified = :dateModifiedValue"), + Key: map[string]*dynamodb.AttributeValue{ + repoModels.RepositoryIDColumn: {S: aws.String(existingModel.RepositoryID)}, + }, TableName: aws.String(r.repositoryTableName), - }) + UpdateExpression: aws.String("SET #enabled = :enabledValue, #note = :noteValue, #dateModified = :dateModifiedValue"), + } + + if claGroupID != "" { + updateInput.ExpressionAttributeNames["#claGroupID"] = aws.String(repoModels.RepositoryCLAGroupIDColumn) + updateInput.ExpressionAttributeValues[":claGroupIDValue"] = &dynamodb.AttributeValue{S: aws.String(claGroupID)} + updateExpression := fmt.Sprintf("%s, #claGroupID = :claGroupIDValue ", *updateInput.UpdateExpression) + updateInput.UpdateExpression = aws.String(updateExpression) + } + + _, err := r.dynamoDBClient.UpdateItem(updateInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem with update, error: %+v", err.Error()) + } return err } diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 877ee9829..117ee5277 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -55,13 +55,15 @@ type ServiceInterface interface { GitLabGetRepository(ctx context.Context, repositoryID string) (*v2Models.GitlabRepository, error) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*v2Models.GitlabRepository, error) + GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*v2Models.GitlabRepository, error) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) - GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesAdd) (*v2Models.GitlabRepositoriesList, error) + GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesEnable) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitLabOrganization) ([]*v2Models.GitlabRepository, error) - GitLabEnableRepository(ctx context.Context, repositoryID string) error - GitLabDisableRepository(ctx context.Context, repositoryID string) error + GitLabEnableRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64) error + GitLabEnableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error + GitLabDisableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error } From a11e60cf0e012bac795aa0b2d56c025d40e7c2e5 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Mon, 23 Aug 2021 18:23:44 +0300 Subject: [PATCH 0450/1276] fix gitlab sign url to v4 (#3187) --- cla-backend-go/config/config.go | 1 + cla-backend-go/config/ssm.go | 3 +++ cla-backend-go/v2/gitlab-activity/service.go | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 42e55caec..2852db09b 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -68,6 +68,7 @@ type Config struct { CorporateConsoleV2URL string `json:"corporateConsoleV2URL"` CLAContributorv2Base string `json:"cla-contributor-v2-base"` + ClaAPIV4Base string `json:"cla_api_v4_base"` // SNSEventTopic the topic ARN for events SNSEventTopicARN string `json:"snsEventTopicARN"` diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index c28129a13..2f1706cb5 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -103,6 +103,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-enable-services-for-parent-%s", stage), fmt.Sprintf("cla-signature-query-default-%s", stage), fmt.Sprintf("cla-platform-api-gw-%s", stage), + fmt.Sprintf("cla-api-v4-base-%s", stage), } // For each key to lookup @@ -175,6 +176,8 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.Gitlab.WebHookURI = resp.value case fmt.Sprintf("cla-contributor-v2-base-%s", stage): config.CLAContributorv2Base = resp.value + case fmt.Sprintf("cla-api-v4-base-%s", stage): + config.ClaAPIV4Base = resp.value case fmt.Sprintf("cla-corporate-base-%s", stage): config.CorporateConsoleURL = resp.value diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 6ca2a9f43..d61dae58e 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -235,8 +235,8 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git } func GetFullSignURL(gitlabOrganizationID string, gitlabRepositoryID string, mrID string) string { - return fmt.Sprintf("%s/v2/repository-provider/%s/sign/%s/%s/%s/#/", - config.GetConfig().APIGatewayURL, + return fmt.Sprintf("%s/v4/repository-provider/%s/sign/%s/%s/%s/#/", + config.GetConfig().ClaAPIV4Base, utils.GitLabLower, gitlabOrganizationID, gitlabRepositoryID, From 78a617f549ed23c3e45f659415a3eaa471506401 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 23 Aug 2021 10:30:17 -0700 Subject: [PATCH 0451/1276] Updated GitHub/GitLab Permissions Checks (#3188) --- cla-backend-go/swagger/cla.v2.yaml | 2 + .../v2/repositories/gitlab_services.go | 4 +- cla-backend-go/v2/repositories/handlers.go | 83 +++++++++++++++---- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 24bd81f6d..99f08c281 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1442,6 +1442,8 @@ paths: $ref: '#/responses/unauthorized' '403': $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' '409': $ref: '#/responses/conflict' '500': diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 77bc099b0..303ececdf 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -97,8 +97,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, RepositorySfdcID: projectSFID, ProjectSFID: projectSFID, RepositoryExternalID: repositoryExternalIDString, - RepositoryName: project.Name, - RepositoryFullPath: project.PathWithNamespace, + RepositoryName: project.PathWithNamespace, // Name column is actually the full path for both GitHub and GitLab RepositoryURL: project.WebURL, RepositoryOrganizationName: input.GitlabOrganizationName, RepositoryCLAGroupID: input.ClaGroupID, @@ -381,7 +380,6 @@ func dbModelToGitLabRepository(dbModel *repoModels.RepositoryDBModel) (*v2Models RepositoryClaGroupID: dbModel.RepositoryCLAGroupID, // CLA Group ID RepositoryExternalID: gitLabExternalID, // GitLab unique gitV1Repository ID RepositoryName: dbModel.RepositoryName, // Short repository name - RepositoryFullPath: dbModel.RepositoryFullPath, // Full repository path RepositoryOrganizationName: dbModel.RepositoryOrganizationName, // Group/Organization name RepositoryURL: dbModel.RepositoryURL, // full url RepositoryType: dbModel.RepositoryType, // gitlab diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index afa3223d9..9c66a9e2f 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_organizations" project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_repositories" @@ -47,9 +46,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "projectSFID": params.ProjectSFID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return github_repositories.NewGetProjectGithubRepositoriesNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Get GitHub V3Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Get GitHub repositories for Project %s with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewGetProjectGithubRepositoriesForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -99,9 +106,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "repositoryGitHubIDs": strings.Join(params.GithubRepositoryInput.RepositoryGithubIds, ","), } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return github_repositories.NewAddProjectGithubRepositoryNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Add GitHub V3Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to add GitHub repositories for Project %s with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewAddProjectGithubRepositoryForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -178,9 +193,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "repositoryID": params.RepositoryID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return github_repositories.NewDeleteProjectGithubRepositoryNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Delete GitHub V3Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Get GitHub repositories for Project %s with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewDeleteProjectGithubRepositoryForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -236,9 +259,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "repositoryID": params.RepositoryID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return github_repositories.NewGetProjectGithubRepositoryBranchProtectionNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Query Protected Branch GitHub V3Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Query Protected Branch GitHub Repositories for Project %s with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewGetProjectGithubRepositoryBranchProtectionForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -297,9 +328,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "repositoryID": params.RepositoryID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return github_repositories.NewUpdateProjectGithubRepositoryBranchProtectionNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Update Protected Branch GitHub V3Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Update Protected Branch GitHub Repositories for Project %s with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return github_repositories.NewUpdateProjectGithubRepositoryBranchProtectionForbidden().WithPayload( utils.ErrorResponseForbidden(reqID, msg)) @@ -378,9 +417,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "projectSFID": params.ProjectSFID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_repositories.NewGetProjectGitLabRepositoriesNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Get GitLab Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) + msg := fmt.Sprintf("user %s does not have access to Get GitLab Repositories for Project %s with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) return gitlab_repositories.NewGetProjectGitLabRepositoriesForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } @@ -430,7 +477,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic psc := project_service.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { - return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( + return gitlab_repositories.NewEnableGitLabRepositoryNotFound().WithPayload( utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) } @@ -478,6 +525,14 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "repositoryExternalID": params.RepositoryExternalID, } + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_repositories.NewUnenrollGitLabRepositoryNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Unenroll Gitlab Repositories with Project scope of %s", authUser.UserName, params.ProjectSFID) From 2a145c1a11dd746b182be4dcf2a649abac265332 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 23 Aug 2021 15:16:30 -0700 Subject: [PATCH 0452/1276] Updated GitLab Organizations Swagger (#3189) --- .../common/gitlab-organization-create.yaml | 4 +- .../common/gitlab-organization-update.yaml | 8 ++-- .../v2/gitlab_organizations/handlers.go | 20 ++++---- .../v2/gitlab_organizations/service.go | 4 +- cla-backend-go/v2/signatures/handlers.go | 46 +++++++------------ cla-backend-go/v2/signatures/service.go | 5 +- 6 files changed, 38 insertions(+), 49 deletions(-) diff --git a/cla-backend-go/swagger/common/gitlab-organization-create.yaml b/cla-backend-go/swagger/common/gitlab-organization-create.yaml index 983fe0268..1bb91ac86 100644 --- a/cla-backend-go/swagger/common/gitlab-organization-create.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization-create.yaml @@ -17,9 +17,9 @@ properties: description: The GitLab Group ID example: 13050017 minimum: 1 - group_full_path: + organization_full_path: type: string - description: The GitLab Group full path + description: The GitLab Group/Organization full path example: 'linuxfoundation/product/easycla' minLength: 3 auto_enabled: diff --git a/cla-backend-go/swagger/common/gitlab-organization-update.yaml b/cla-backend-go/swagger/common/gitlab-organization-update.yaml index e9875eca5..dec67909d 100644 --- a/cla-backend-go/swagger/common/gitlab-organization-update.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization-update.yaml @@ -3,15 +3,15 @@ type: object required: - - autoEnabled + - auto_enabled properties: - autoEnabled: + auto_enabled: type: boolean description: Flag to indicate if auto-enabled flag should be enabled. Group/Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. - autoEnabledClaGroupID: + auto_enabled_cla_group_id: type: string description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the GitLab Group/Organization. If autoEnabled is on this field needs to be set as well. - branchProtectionEnabled: + branch_protection_enabled: type: boolean description: Flag to indicate if this Group/Organization is configured to automatically setup branch protection on CLA enabled repositories. x-omitempty: true diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 846f51fe1..9fd0214d7 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -97,7 +97,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic "authEmail": authUser.Email, "projectSFID": params.ProjectSFID, "groupID": params.Body.GroupID, - "groupFullPath": params.Body.GroupFullPath, + "groupFullPath": params.Body.OrganizationFullPath, } // Load the project @@ -117,7 +117,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Quick check of the parameters - if params.Body == nil || (params.Body.GroupID == 0 && params.Body.GroupFullPath == "") { + if params.Body == nil || (params.Body.GroupID == 0 && params.Body.OrganizationFullPath == "") { msg := fmt.Sprintf("missing group ID or group full path in the body: %+v", params.Body) log.WithFields(f).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( @@ -125,16 +125,16 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Clean up/filter the Group Full Path, if needed - if params.Body.GroupFullPath != "" { + if params.Body.OrganizationFullPath != "" { r, regexErr := regexp.Compile(`^http(s)?://`) if regexErr != nil { - msg := fmt.Sprintf("invalid regex for group full path, error: %+v", regexErr) + msg := fmt.Sprintf("invalid regex for group/organization full path, error: %+v", regexErr) log.WithFields(f).WithError(regexErr).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationInternalServerError().WithPayload( utils.ErrorResponseInternalServerErrorWithError(reqID, msg, regexErr)) } - if r.MatchString(params.Body.GroupFullPath) { - groupWithUrl, urlParseErr := url.Parse(params.Body.GroupFullPath) + if r.MatchString(params.Body.OrganizationFullPath) { + groupWithUrl, urlParseErr := url.Parse(params.Body.OrganizationFullPath) if urlParseErr != nil { msg := fmt.Sprintf("invalid group full path provided, error: %+v", urlParseErr) log.WithFields(f).WithError(urlParseErr).Warn(msg) @@ -142,12 +142,12 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequestWithError(reqID, msg, urlParseErr)) } // Update the group full path value - just include the path and not the https://... part - params.Body.GroupFullPath = groupWithUrl.Path + params.Body.OrganizationFullPath = groupWithUrl.Path } // Remove leading slash - if strings.HasPrefix(params.Body.GroupFullPath, "/") { - params.Body.GroupFullPath = params.Body.GroupFullPath[1:] + if strings.HasPrefix(params.Body.OrganizationFullPath, "/") { + params.Body.OrganizationFullPath = params.Body.OrganizationFullPath[1:] } } @@ -178,7 +178,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic // Get the current group name for the event for _, group := range result.List { - if group.OrganizationExternalID == params.Body.GroupID || group.OrganizationFullPath == params.Body.GroupFullPath { + if group.OrganizationExternalID == params.Body.GroupID || group.OrganizationFullPath == params.Body.OrganizationFullPath { // Log the event eventService.LogEventWithContext(ctx, &events.LogEventArgs{ LfUsername: authUser.UserName, diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index b23e824f2..fc48002f7 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -71,7 +71,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, "autoEnabled": utils.BoolValue(input.AutoEnabled), "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), "groupID": input.GroupID, - "groupFullPath": input.GroupFullPath, + "groupFullPath": input.OrganizationFullPath, } psc := v2ProjectService.GetClient() @@ -101,7 +101,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, branchProtectionEnabled = utils.BoolValue(input.BranchProtectionEnabled) } - resp, err := s.repo.AddGitLabOrganization(ctx, parentProjectSFID, projectSFID, input.GroupID, "", input.GroupFullPath, autoEnabled, input.AutoEnabledClaGroupID, branchProtectionEnabled, true) + resp, err := s.repo.AddGitLabOrganization(ctx, parentProjectSFID, projectSFID, input.GroupID, "", input.OrganizationFullPath, autoEnabled, input.AutoEnabledClaGroupID, branchProtectionEnabled, true) if err != nil { log.WithFields(f).WithError(err).Warn("problem adding gitlab organization for project") return nil, err diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 0abcc1592..7ae5c2788 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -889,6 +889,19 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj "companyID": params.CompanyID, } + // Lookup the CLA Group by ID - make sure it's valid + claGroupModel, err := projectRepo.GetCLAGroupByID(ctx, params.ClaGroupID, project.DontLoadRepoDetails) + if err != nil { + log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) + if err == project.ErrProjectDoesNotExist { + return signatures.NewListClaGroupCorporateContributorsNotFound().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseNotFoundWithError(reqID, problemLoadingCLAGroupByID, err)) + } + + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload( + utils.ErrorResponseBadRequest(reqID, problemLoadingCLAGroupByID)) + } + // Make sure the user has provided the companyID if params.CompanyID == nil { msg := "missing companyID as input" @@ -901,44 +914,19 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj if err != nil { msg := fmt.Sprintf("User lookup for company by ID: %s failed : %v", *params.CompanyID, err) log.Warn(msg) - if _, ok := err.(*utils.CompanyNotFound); ok { - return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 404 Not Found - error getting company - " + msg, - Code: "404", - }) - } - return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(&models.ErrorResponse{ - Message: "EasyCLA - 400 Bad Request - error getting company - " + msg, - Code: "400", - }) - } - - // Lookup the CLA Group by ID - make sure it's valid - claGroupModel, err := projectRepo.GetCLAGroupByID(ctx, params.ClaGroupID, project.DontLoadRepoDetails) - if err != nil { - log.WithFields(f).WithError(err).Warn(problemLoadingCLAGroupByID) - if err == project.ErrProjectDoesNotExist { - return signatures.NewListClaGroupCorporateContributorsNotFound().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseNotFoundWithError(reqID, problemLoadingCLAGroupByID, err)) - } - - return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload( - utils.ErrorResponseBadRequest(reqID, problemLoadingCLAGroupByID)) + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } // Make sure CCLA is enabled for this CLA Group if !claGroupModel.ProjectCCLAEnabled { - msg := "cla group does not support corporate contribution" + msg := fmt.Sprintf("CLA Group with ID '%s' does not support corporate contribution", params.ClaGroupID) log.WithFields(f).Warn(msg) - // Return 200 as the retool UI can't handle 400's - return signatures.NewListClaGroupCorporateContributorsOK().WithXRequestID(reqID).WithPayload(&models.CorporateContributorList{ - List: []*models.CorporateContributor{}, // empty list - }) + return signatures.NewListClaGroupCorporateContributorsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, errors.New(msg))) } // Lookup the Project to CLA Group mapping table entries - this will have the correct details projectCLAGroupEntries, projectCLAGroupErr := projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, params.ClaGroupID) - // Should have at least one entry if we're setup correctly - it will have the foundation (parent project/project group) and project details set + // Should have at least one entry if we're set up correctly - it will have the foundation (parent project/project group) and project details set if projectCLAGroupErr != nil || len(projectCLAGroupEntries) == 0 { msg := fmt.Sprintf("unable to load project CLA Group mappings for CLA Group: %s - has this project been migrated to v2?", params.ClaGroupID) log.WithFields(f).Warn(msg) diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 45aa39516..947a1811d 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -157,7 +157,7 @@ func (s *Service) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID st // GetProjectCclaSignaturesCsv returns the ICLA signatures as a CSV file for the specified CLA Group and search term filters func (s *Service) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) { f := logrus.Fields{ - "functionName": "GetProjectCclaSignaturesCsv", + "functionName": "v2.signatures.service.GetProjectCclaSignaturesCsv", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "claGroupID": claGroupID, } @@ -289,7 +289,7 @@ func (s *Service) IsZipPresentOnS3(zipFilePath string) (bool, error) { // GetClaGroupCorporateContributors returns the list of corporate contributors for the specified CLA Group and company func (s *Service) GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID string, searchTerm *string) (*models.CorporateContributorList, error) { f := logrus.Fields{ - "functionName": "GetClaGroupCorporateContributors", + "functionName": "v2.signatures.service.GetClaGroupCorporateContributors", "claGroupID": claGroupID, "companyID": companyID, } @@ -302,6 +302,7 @@ func (s *Service) GetClaGroupCorporateContributors(ctx context.Context, claGroup if err != nil { return nil, err } + log.WithFields(f).Debugf("discovered %d CLA corporate contributors...", len(result.List)) log.WithFields(f).Debug("converting to v2 response model...") var resp models.CorporateContributorList From fa20eda8c14af7ab03874b0aee279ba4f9f4363d Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 23 Aug 2021 17:05:54 -0700 Subject: [PATCH 0453/1276] Added GitLab Permissions Error Details/Response (#3190) --- cla-backend-go/gitlab_api/client_groups.go | 9 +++--- cla-backend-go/tests/gitlab_client_test.go | 3 +- .../v2/gitlab_organizations/service.go | 28 +++++++++++++++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go index ba7a30b60..38720fcb0 100644 --- a/cla-backend-go/gitlab_api/client_groups.go +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -23,7 +23,7 @@ type UserGroup struct { } // GetGroupsListAll returns a complete list of GitLab groups for which the client as authorization/visibility -func GetGroupsListAll(ctx context.Context, client *goGitLab.Client) ([]*goGitLab.Group, error) { +func GetGroupsListAll(ctx context.Context, client *goGitLab.Client, minAccessLevel goGitLab.AccessLevelValue) ([]*goGitLab.Group, error) { f := logrus.Fields{ "functionName": "gitlab_api.client_groups.GetGroupsListAll", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -36,8 +36,9 @@ func GetGroupsListAll(ctx context.Context, client *goGitLab.Client) ([]*goGitLab Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination PerPage: 100, // max is 100 }, - AllAvailable: utils.Bool(true), // Show all the groups you have access to (defaults to false for authenticated users, true for administrators); Attributes owned and min_access_level have precedence - MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. + AllAvailable: utils.Bool(true), // Show all the groups you have access to (defaults to false for authenticated users, true for administrators); Attributes owned and min_access_level have precedence + MinAccessLevel: goGitLab.AccessLevel(minAccessLevel), // Limit by current user minimal access level. + //MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. } var groupList []*goGitLab.Group @@ -128,7 +129,7 @@ func GetGroupByFullPath(ctx context.Context, client *goGitLab.Client, fullPath s utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } - groups, err := GetGroupsListAll(ctx, client) + groups, err := GetGroupsListAll(ctx, client, goGitLab.MaintainerPermissions) //groups, _, err := client.Groups.ListGroups(&goGitLab.ListGroupsOptions{}) if err != nil { msg := fmt.Sprintf("problem fetching groups, error: %+v", err) diff --git a/cla-backend-go/tests/gitlab_client_test.go b/cla-backend-go/tests/gitlab_client_test.go index 46a4924c2..487d77931 100644 --- a/cla-backend-go/tests/gitlab_client_test.go +++ b/cla-backend-go/tests/gitlab_client_test.go @@ -221,6 +221,7 @@ func TestGitLabListGroups(t *testing.T) { // no lint PerPage: 100, }, } + groups, resp, searchErr := gitLabClient.Groups.ListGroups(opts) assert.Nil(t, searchErr, "GitLab List Groups Error is Nil") if searchErr != nil { @@ -232,7 +233,7 @@ func TestGitLabListGroups(t *testing.T) { // no lint assert.Fail(t, fmt.Sprintf("unable to list GitLab groups, status code: %d, body: %s", resp.StatusCode, respBody)) } for _, g := range groups { - t.Logf("name: %s, id: %d, web url: %s, path: %s, full path: %s", g.Name, g.ID, g.WebURL, g.Path, g.FullPath) + t.Logf("name: %s, id: %d, path: %s, full path: %s, web url: %s", g.Name, g.ID, g.Path, g.FullPath, g.WebURL) } } } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index fc48002f7..aafa3506b 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -335,12 +335,12 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } // Query the groups list - groups, groupListErr := gitlab_api.GetGroupsListAll(ctx, gitLabClient) + groupsWithMaintainerPerms, groupListErr := gitlab_api.GetGroupsListAll(ctx, gitLabClient, goGitLab.MaintainerPermissions) if groupListErr != nil { return groupListErr } - for _, g := range groups { + for _, g := range groupsWithMaintainerPerms { // If we have an external group ID or a full path... if (gitLabOrgModel.ExternalGroupID > 0 && g.ID == gitLabOrgModel.ExternalGroupID) || (gitLabOrgModel.OrganizationFullPath != "" && g.FullPath == gitLabOrgModel.OrganizationFullPath) { @@ -366,7 +366,29 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } } - return fmt.Errorf("unable to locate GitLab group by using external ID: %d or full path: %s, found: %d", gitLabOrgModel.ExternalGroupID, gitLabOrgModel.OrganizationFullPath, len(groups)) + // Query the groups list to see if the user has minimal access permissions only + groupsWithMinimalAccessPerms, groupListErr := gitlab_api.GetGroupsListAll(ctx, gitLabClient, goGitLab.MinimalAccessPermissions) + if groupListErr != nil { + return groupListErr + } + + // Loop through the responses - check to see if we have a match, if so, return a specific error message + for _, g := range groupsWithMinimalAccessPerms { + // If we have an external group ID or a full path... + if (gitLabOrgModel.ExternalGroupID > 0 && g.ID == gitLabOrgModel.ExternalGroupID) || + (gitLabOrgModel.OrganizationFullPath != "" && g.FullPath == gitLabOrgModel.OrganizationFullPath) { + msg := "" + if gitLabOrgModel.ExternalGroupID > 0 { + msg = fmt.Sprintf("external ID: %d", g.ID) + } else if gitLabOrgModel.OrganizationFullPath != "" { + msg = fmt.Sprintf("full path: '%s'", g.FullPath) + } + return fmt.Errorf("found the GitLab group '%s' by using the %s filter - however the authenticated user does not have maintainer or above permissions for this GitLab group", g.FullPath, msg) + } + } + + return fmt.Errorf("unable to locate GitLab group by using external ID: %d or full path: %s, found %d groups where user has maintainer or above permissions", + gitLabOrgModel.ExternalGroupID, gitLabOrgModel.OrganizationFullPath, len(groupsWithMaintainerPerms)) } // UpdateGitLabOrganization updates the GitLab organization From 296bed28da9c3fe4f73ac5c34506073bbba25c32 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 23 Aug 2021 19:40:14 -0700 Subject: [PATCH 0454/1276] Bug/Gitlab Redirect contributor console (#3191) - Updated user_gitlab_attribute call with integer in the repository call - Updated signature meta data set(v4) and get (v2) - Updated Store value type in stores table Signed-off-by: Harold Wanyama Co-authored-by: Harold Wanyama --- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/users/repository.go | 2 +- cla-backend-go/v2/gitlab_sign/handlers.go | 9 ++- cla-backend-go/v2/gitlab_sign/service.go | 80 +++++++++++++++-------- cla-backend-go/v2/store/repository.go | 15 +++-- cla-backend/cla/models/dynamo_models.py | 6 +- cla-backend/cla/utils.py | 4 ++ 7 files changed, 83 insertions(+), 37 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 2d38fed75..e1f88712e 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -239,7 +239,7 @@ func server(localMode bool) http.Handler { // initialize github github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) // initialize gitlab - _ = gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) + gitlabApp := gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) // Our backend repository handlers userRepo := user.NewDynamoRepository(awsSession, stage) @@ -309,7 +309,7 @@ func server(localMode bool) http.Handler { githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo) gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo) gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, gitV1Repository, gitV2Repository, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) - gitlabSignService := gitlab_sign.NewService(v2RepositoriesService, gitlabOrganizationRepo, usersService, storeRepository) + gitlabSignService := gitlab_sign.NewService(v2RepositoriesService, gitlabOrganizationRepo, usersService, storeRepository, gitlabApp) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo, githubOrganizationsService) autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, gitV1Repository, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService) v2GithubActivityService := v2GithubActivity.NewService(gitV1Repository, githubOrganizationsRepo, eventsService, autoEnableService, emailService) diff --git a/cla-backend-go/users/repository.go b/cla-backend-go/users/repository.go index 350e98781..484878d02 100644 --- a/cla-backend-go/users/repository.go +++ b/cla-backend-go/users/repository.go @@ -107,7 +107,7 @@ func (repo repository) CreateUser(user *models.User) (*models.User, error) { if user.GitlabID != "" { attributes["user_gitlab_id"] = &dynamodb.AttributeValue{ - S: aws.String(user.GitlabID), + N: aws.String(user.GitlabID), } } diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index acd2ebeed..317fc8c58 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -6,11 +6,13 @@ package gitlab_sign import ( "context" "fmt" + "net/http" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_sign" "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/sirupsen/logrus" @@ -33,7 +35,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. log.WithFields(f).Debugf("Initiating Gitlab sign request for : %+v ", srp) - err := service.GitlabSignRequest(ctx, srp.HTTPRequest, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID, contributorConsoleV2Base, eventService) + consoleURL, err := service.GitlabSignRequest(ctx, srp.HTTPRequest, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID, contributorConsoleV2Base, eventService) if err != nil { msg := fmt.Sprintf("problem initiating sign request for :%+v", srp) @@ -42,6 +44,9 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - return gitlab_sign.NewSignRequestOK() + return middleware.ResponderFunc(func(rw http.ResponseWriter, pr runtime.Producer) { + http.Redirect(rw, srp.HTTPRequest, *consoleURL, http.StatusSeeOther) + }) + }) } diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go index ee4083736..61d79e34b 100644 --- a/cla-backend-go/v2/gitlab_sign/service.go +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -6,10 +6,14 @@ package gitlab_sign import ( "context" "encoding/json" + + // "encoding/json" + "errors" "fmt" "net/http" "net/url" "strconv" + "time" "github.com/sirupsen/logrus" @@ -29,24 +33,25 @@ type service struct { repoService repositories.ServiceInterface gitlabOrgRepo gitlab_organizations.RepositoryInterface userService users.Service - gitlabApp gitlab_api.App + gitlabApp *gitlab_api.App storeRepo store.Repository } type Service interface { - GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) error + GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) (*string, error) } -func NewService(gitlabRepositoryService repositories.ServiceInterface, gitlabOrgRepository gitlab_organizations.RepositoryInterface, userService users.Service, storeRepo store.Repository) Service { +func NewService(gitlabRepositoryService repositories.ServiceInterface, gitlabOrgRepository gitlab_organizations.RepositoryInterface, userService users.Service, storeRepo store.Repository, gitlabApp *gitlab_api.App) Service { return &service{ repoService: gitlabRepositoryService, gitlabOrgRepo: gitlabOrgRepository, userService: userService, + gitlabApp: gitlabApp, storeRepo: storeRepo, } } -func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) error { +func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) (*string, error) { f := logrus.Fields{ "functionName": "v2.gitlab_sign.service.GitlabSignRequest", "organizationID": organizationID, @@ -57,46 +62,46 @@ func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organ organization, err := s.gitlabOrgRepo.GetGitLabOrganization(ctx, organizationID) if err != nil { log.WithFields(f).Debugf("unable to get gitlab organiztion by ID: %s, error: %+v ", organizationID, err) - return nil + return nil, err } if organization.AuthInfo == "" { msg := fmt.Sprintf("organization: %s has no auth details", organizationID) log.WithFields(f).Debug(msg) - return nil + return nil, errors.New(msg) } - gitlabClient, err := gitlab_api.NewGitlabOauthClient(organization.AuthInfo, &s.gitlabApp) + gitlabClient, err := gitlab_api.NewGitlabOauthClient(organization.AuthInfo, s.gitlabApp) if err != nil { log.WithFields(f).Debugf("initializaing gitlab client for gitlab org: %s failed: %v", organizationID, err) - return nil + return nil, err } mergeRequestIDInt, err := strconv.Atoi(mergeRequestID) if err != nil { log.WithFields(f).Debugf("unable to convert organization string value : %s to Int", organizationID) - return err + return nil, err } log.WithFields(f).Debug("Determining return URL from the inbound request ...") mergeRequest, _, err := gitlabClient.MergeRequests.GetMergeRequest(repositoryID, mergeRequestIDInt, &gitlab.GetMergeRequestsOptions{}) if err != nil || mergeRequest == nil { log.WithFields(f).Debugf("unable to fetch MR Web URL: mergeRequestID: %s ", mergeRequestID) - return err + return nil, err } originURL := mergeRequest.WebURL log.WithFields(f).Debugf("Return URL from the inbound request is : %s ", originURL) - err = s.redirectToConsole(ctx, req, gitlabClient, repositoryID, mergeRequestID, originURL, contributorConsoleV2Base, eventService) + consoleURL, err := s.redirectToConsole(ctx, req, gitlabClient, repositoryID, mergeRequestID, originURL, contributorConsoleV2Base, eventService) if err != nil { log.WithFields(f).Debug("unable to redirect to contributor console") - return err + return nil, err } - return nil + return consoleURL, nil } -func (s service) redirectToConsole(ctx context.Context, req *http.Request, gitlabClient *gitlab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) error { +func (s service) redirectToConsole(ctx context.Context, req *http.Request, gitlabClient *gitlab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) (*string, error) { f := logrus.Fields{ "functionName": "v2.gitlab_sign.service.redirectToConsole", "repositoryID": repositoryID, @@ -108,28 +113,51 @@ func (s service) redirectToConsole(ctx context.Context, req *http.Request, gitla if err != nil { msg := fmt.Sprintf("unable to get or create user : %+v ", err) log.WithFields(f).Warn(msg) - return err + return nil, err } - gitlabRepo, err := s.repoService.GitHubGetRepository(ctx, repositoryID) + repoIDInt, err := strconv.Atoi(repositoryID) + if err != nil { + msg := fmt.Sprintf("unable to convert GitlabRepoID: %s to int", repositoryID) + log.WithFields(f).Warn(msg) + return nil, err + } + + log.WithFields(f).Debugf("getting gitlab repository for: %d", repoIDInt) + gitlabRepo, err := s.repoService.GitLabGetRepositoryByExternalID(ctx, int64(repoIDInt)) if err != nil { msg := fmt.Sprintf("unable to find repository by ID: %s , error: %+v ", repositoryID, err) log.WithFields(f).Warn(msg) - return err + return nil, err } + type StoreValue struct { + UserID string `json:"user_id"` + ProjectID string `json:"project_id"` + RepositoryID string `json:"repository_id"` + MergeRequestID string `json:"merge_request_id"` + ReturnURL string `json:"return_url"` + } + + log.WithFields(f).Debugf("setting active signature metadata: claUser: %+v, repository: %+v", claUser, gitlabRepo) // set active signature metadata to track the user signing process key := fmt.Sprintf("active_signature:%s", claUser.UserID) - var value map[string]string - value["user_id"] = claUser.UserID - value["project_id"] = gitlabRepo.RepositoryClaGroupID - value["repository_id"] = repositoryID - value["merge_request_id"] = mergeRequestID + storeValue := StoreValue{ + UserID: claUser.UserID, + ProjectID: gitlabRepo.RepositoryClaGroupID, + RepositoryID: repositoryID, + MergeRequestID: mergeRequestID, + ReturnURL: originURL, + } + json_data, err := json.Marshal(storeValue) + if err != nil { + log.Fatal(err) + } expire := time.Now().AddDate(0, 0, 1).Unix() - jsonVal, _ := json.Marshal(value) + // jsonVal, _ := json.Marshal(value) - err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, jsonVal) + err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to save signature metadata") } @@ -141,10 +169,10 @@ func (s service) redirectToConsole(ctx context.Context, req *http.Request, gitla if err != nil { msg := fmt.Sprintf("unable to redirect to : %s , error: %+v ", consoleURL, err) log.WithFields(f).Warn(msg) - return err + return nil, err } - return nil + return &consoleURL, nil } func (s service) getOrCreateUser(ctx context.Context, gitlabClient *gitlab.Client, eventsService events.Service) (*models.User, error) { diff --git a/cla-backend-go/v2/store/repository.go b/cla-backend-go/v2/store/repository.go index 64785faad..4d93dbf4b 100644 --- a/cla-backend-go/v2/store/repository.go +++ b/cla-backend-go/v2/store/repository.go @@ -19,14 +19,14 @@ import ( //DBStore represents DB Model for the store table type DBStore struct { - Key string - Value []byte - Expire int64 + Key string `dynamodbav:"key"` + Value string `dynamodbav:"value"` + Expire int64 `dynamodbav:"expire"` } // Repository interface type Repository interface { - SetActiveSignatureMetaData(ctx context.Context, key string, expire int64, value []byte) error + SetActiveSignatureMetaData(ctx context.Context, key string, expire int64, value string) error } type repo struct { @@ -45,7 +45,7 @@ func NewRepository(awsSession *session.Session, stage string) Repository { } // SetActiveSignatureMetaData sets active signature meta data -func (r repo) SetActiveSignatureMetaData(ctx context.Context, key string, expire int64, value []byte) error { +func (r repo) SetActiveSignatureMetaData(ctx context.Context, key string, expire int64, value string) error { f := logrus.Fields{ "functionName": "v2.store.repository.SetActiveSignatureMetaData", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -60,12 +60,17 @@ func (r repo) SetActiveSignatureMetaData(ctx context.Context, key string, expire Expire: expire, } + log.WithFields(f).Debugf("key: %s ", store.Key) + log.WithFields(f).Debugf("value: %+s ", store.Value) + v, err := dynamodbattribute.MarshalMap(store) if err != nil { log.WithFields(f).WithError(err).Warn("problem marshalling store record") return err } + log.WithFields(f).Debugf("Marshalled values: %+v", v) + _, err = r.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ Item: v, TableName: &r.storeTableName, diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 2b83cdaad..73fdc9404 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -3561,9 +3561,13 @@ def set(self, key, value): model.save() def get(self, key): + import json model = StoreModel() try: - return model.get(key).value + val = model.get(key).value + if isinstance(val, dict): + val = json.dumps(val) + return val except StoreModel.DoesNotExist: raise cla.models.DoesNotExist("Key not found") diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 6d2a60666..917c9f873 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1201,6 +1201,10 @@ def get_active_signature_return_url(user_id, metadata=None): if metadata is None: cla.log.warning('Could not find active signature for user {}, return URL request failed'.format(user_id)) return None + + # Factor in Gitlab flow process + if "merge_request_id" in metadata.keys(): + return metadata['return_url'] # Get Github ID from metadata github_repository_id = metadata['repository_id'] From fc8ad854d6adff0b1b173238e4eed4b35ebd92f6 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 24 Aug 2021 19:06:31 +0300 Subject: [PATCH 0455/1276] use full repo path when lookup repos on mr callbacks (#3192) --- cla-backend-go/config/config.go | 3 ++ cla-backend-go/config/ssm.go | 3 ++ cla-backend-go/gitlab_api/mr.go | 23 ++-------- cla-backend-go/v2/gitlab-activity/service.go | 43 +++++++++++++++---- .../v2/gitlab-activity/service_test.go | 15 +++++-- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/cla-backend-go/config/config.go b/cla-backend-go/config/config.go index 2852db09b..752ee6188 100644 --- a/cla-backend-go/config/config.go +++ b/cla-backend-go/config/config.go @@ -82,6 +82,9 @@ type Config struct { // CLAV1ApiURL is api url of v1. it is used in v2 sign service ClaV1ApiURL string `json:"cla_v1_api_url"` + // CLALandingPage + CLALandingPage string `json:"cla_landing_page"` + // AcsAPIKey is api key of the acs AcsAPIKey string `json:"acs_api_key"` diff --git a/cla-backend-go/config/ssm.go b/cla-backend-go/config/ssm.go index 2f1706cb5..dde94aa34 100644 --- a/cla-backend-go/config/ssm.go +++ b/cla-backend-go/config/ssm.go @@ -104,6 +104,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint fmt.Sprintf("cla-signature-query-default-%s", stage), fmt.Sprintf("cla-platform-api-gw-%s", stage), fmt.Sprintf("cla-api-v4-base-%s", stage), + fmt.Sprintf("cla-landing-page-%s", stage), } // For each key to lookup @@ -178,6 +179,8 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint config.CLAContributorv2Base = resp.value case fmt.Sprintf("cla-api-v4-base-%s", stage): config.ClaAPIV4Base = resp.value + case fmt.Sprintf("cla-landing-page-%s", stage): + config.CLALandingPage = resp.value case fmt.Sprintf("cla-corporate-base-%s", stage): config.CorporateConsoleURL = resp.value diff --git a/cla-backend-go/gitlab_api/mr.go b/cla-backend-go/gitlab_api/mr.go index 7a454b893..33145c1ef 100644 --- a/cla-backend-go/gitlab_api/mr.go +++ b/cla-backend-go/gitlab_api/mr.go @@ -92,22 +92,7 @@ func SetCommitStatus(client *gitlab.Client, projectID int, commitSha string, sta } // SetMrComment is responsible for setting the comment body for project and merge id -func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitlab.BuildStateValue, message string, targetURL string) error { - covered := fmt.Sprintf(` - covered
    `, targetURL) - failed := fmt.Sprintf(` -covered
    `, targetURL) - - var body string - if state == gitlab.Failed { - body = failed - } else { - body = covered - } - - if message != "" { - body += "

    " + message - } +func SetMrComment(client *gitlab.Client, projectID int, mergeID int, message string) error { notes, _, err := client.Notes.ListMergeRequestNotes(projectID, mergeID, &gitlab.ListMergeRequestNotesOptions{}) if err != nil { @@ -117,7 +102,7 @@ func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitla var previousNote *gitlab.Note if len(notes) > 0 { for _, n := range notes { - if strings.Contains(n.Body, "cla-signed.svg") || strings.Contains(n.Body, "cla-not-signed.svg") { + if strings.Contains(n.Body, "cla-signed.svg") || strings.Contains(n.Body, "cla-not-signed.svg") || strings.Contains(n.Body, "cla-missing-id.svg") || strings.Contains(n.Body, "cla-confirmation-needed.svg") { previousNote = n break } @@ -127,7 +112,7 @@ func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitla if previousNote == nil { log.Debugf("no previous comments found for project id : %d and merge id : %d", projectID, mergeID) _, _, err = client.Notes.CreateMergeRequestNote(projectID, mergeID, &gitlab.CreateMergeRequestNoteOptions{ - Body: &body, + Body: &message, }) if err != nil { return fmt.Errorf("creating comment for project id : %d and merge id : %d : failed %v", projectID, mergeID, err) @@ -135,7 +120,7 @@ func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitla } else { log.Debugf("previous comments found for project id : %d and merge id : %d", projectID, mergeID) _, _, err = client.Notes.UpdateMergeRequestNote(projectID, mergeID, previousNote.ID, &gitlab.UpdateMergeRequestNoteOptions{ - Body: &body, + Body: &message, }) if err != nil { return fmt.Errorf("updtae comment for project id : %d and merge id : %d : failed %v", projectID, mergeID, err) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index d61dae58e..1d209d7ea 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -80,7 +80,6 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git projectName := mergeEvent.Project.Name projectID := mergeEvent.Project.ID mergeID := mergeEvent.ObjectAttributes.IID - repositoryName := mergeEvent.Repository.Name repositoryPath := mergeEvent.Project.PathWithNamespace lastCommitSha := mergeEvent.ObjectAttributes.LastCommit.ID @@ -89,8 +88,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitlabProjectName": projectName, "gitlabProjectID": projectID, - "repositoryName": repositoryName, - "repositoryPath": repositoryPath, + "repositoryName": repositoryPath, "mergeID": mergeID, } @@ -113,7 +111,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git } // try to find the repository via the external id - gitlabRepo, err := s.getGitlabRepoByName(ctx, repositoryName) + gitlabRepo, err := s.getGitlabRepoByName(ctx, repositoryPath) if err != nil { return fmt.Errorf("finding internal repository for gitlab org name failed : %v", err) } @@ -162,7 +160,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git return fmt.Errorf("setting commit status failed : %v", err) } - if err := gitlab_api.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Failed, mrCommentContent, signURL); err != nil { + if err := gitlab_api.SetMrComment(gitlabClient, projectID, mergeID, mrCommentContent); err != nil { return fmt.Errorf("setting comment failed : %v", err) } @@ -173,13 +171,34 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git return fmt.Errorf("setting commit status failed : %v", err) } - if err := gitlab_api.SetMrComment(gitlabClient, projectID, mergeID, gitlab.Success, mrCommentContent, signURL); err != nil { + if err := gitlab_api.SetMrComment(gitlabClient, projectID, mergeID, mrCommentContent); err != nil { return fmt.Errorf("setting comment failed : %v", err) } return err } func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*gitlab.User, signURL string) string { + landingPage := config.GetConfig().CLALandingPage + landingPage += "/#/?version=2" + + var badgeHyperlink string + if len(missingUsers) > 0{ + badgeHyperlink = signURL + }else{ + badgeHyperlink = landingPage + } + + coveredBadge := fmt.Sprintf(` + CLA Signed
    `, badgeHyperlink) + failedBadge := fmt.Sprintf(` +CLA Not Signed
    `, badgeHyperlink) + missingUserIDBadge := fmt.Sprintf(` +CLA Missing ID
    `, badgeHyperlink) + confirmationNeededBadge := fmt.Sprintf(` +CLA Confirmation Needed
    `, badgeHyperlink) + + var body string + var result string failed := ":x:" success := ":white_check_mark:" @@ -191,6 +210,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git result += fmt.Sprintf("
  • %s %s
  • ", success, authorInfo) } result += "" + body = coveredBadge } gitlabSupportURL := "https://about.gitlab.com/support" @@ -206,6 +226,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git please submit a support request ticket. `, failed, authorInfo, gitlabSupportURL, easyCLASupportURL) result += msg + body = missingUserIDBadge } else if errors.Is(missingUser.err, missingCompanyAffiliation) { msg := fmt.Sprintf(`
  • %s is authorized, but they must confirm their affiliation with their company. Start the authorization process @@ -216,7 +237,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git please submit a support request ticket.
  • `, authorInfo, signURL, easyCLASupportURL) result += msg - + body = confirmationNeededBadge } else { msg := fmt.Sprintf(`
  • %s - %s's commit is not authorized under a signed CLA. @@ -225,13 +246,17 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git please submit a support request ticket.
  • `, signURL, failed, authorInfo, signURL, easyCLASupportURL) result += msg - + body = failedBadge } } result += "" } - return result + if result != ""{ + body += "

    " + result + } + + return body } func GetFullSignURL(gitlabOrganizationID string, gitlabRepositoryID string, mrID string) string { diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index 7b651d5a1..1dd8a458b 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -113,10 +113,11 @@ func TestPrepareMrCommentContent(t *testing.T) { missingApprovalContains := "%s's commit is not authorized under a signed CLA" testCases := []struct { - name string - signed []*gitlab.User - missing []*gatedGitlabUser - expectedMsgs []string + name string + signed []*gitlab.User + missing []*gatedGitlabUser + expectedMsgs []string + expectedBadge string }{ { name: "all signed", @@ -125,6 +126,7 @@ func TestPrepareMrCommentContent(t *testing.T) { {ID: 2, Username: "oracle"}, }, expectedMsgs: []string{signedContains, signedContains}, + expectedBadge: "cla-signed.svg", }, { name: "missing id", @@ -135,6 +137,7 @@ func TestPrepareMrCommentContent(t *testing.T) { {err: missingID, User: &gitlab.User{ID: 3, Username: "missing"}}, }, expectedMsgs: []string{signedContains, missingUserContains}, + expectedBadge: "cla-missing-id.svg", }, { name: "missing affiliation", @@ -145,6 +148,7 @@ func TestPrepareMrCommentContent(t *testing.T) { {err: missingCompanyAffiliation, User: &gitlab.User{ID: 4, Username: "affiliationUser"}}, }, expectedMsgs: []string{signedContains, missingAffiliationContains}, + expectedBadge: "cla-confirmation-needed.svg", }, { name: "missing approval", @@ -155,6 +159,7 @@ func TestPrepareMrCommentContent(t *testing.T) { {err: missingCompanyApproval, User: &gitlab.User{ID: 5, Username: "approvalUser"}}, }, expectedMsgs: []string{signedContains, missingApprovalContains}, + expectedBadge: "cla-not-signed.svg", }, } @@ -183,6 +188,8 @@ func TestPrepareMrCommentContent(t *testing.T) { expected := fmt.Sprintf(tc.expectedMsgs[i], getAuthorInfo(allUsers[i])) assert.Contains(tt, p, expected) } + + assert.Contains(tt, result, tc.expectedBadge) }) } } From 81915f7582978dd9c041fde5bafa1bf26ac8d9c1 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 24 Aug 2021 11:15:08 -0700 Subject: [PATCH 0456/1276] Cleanup GitLab Error Messages (#3193) Signed-off-by: David Deal --- cla-backend-go/gitlab_api/client_groups.go | 1 - .../v2/gitlab_organizations/service.go | 28 +++++-------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go index 38720fcb0..196b95531 100644 --- a/cla-backend-go/gitlab_api/client_groups.go +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -38,7 +38,6 @@ func GetGroupsListAll(ctx context.Context, client *goGitLab.Client, minAccessLev }, AllAvailable: utils.Bool(true), // Show all the groups you have access to (defaults to false for authenticated users, true for administrators); Attributes owned and min_access_level have precedence MinAccessLevel: goGitLab.AccessLevel(minAccessLevel), // Limit by current user minimal access level. - //MinAccessLevel: goGitLab.AccessLevel(goGitLab.MaintainerPermissions), // Limit by current user minimal access level. } var groupList []*goGitLab.Group diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index aafa3506b..5117ac230 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -366,29 +366,15 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } } - // Query the groups list to see if the user has minimal access permissions only - groupsWithMinimalAccessPerms, groupListErr := gitlab_api.GetGroupsListAll(ctx, gitLabClient, goGitLab.MinimalAccessPermissions) - if groupListErr != nil { - return groupListErr - } - - // Loop through the responses - check to see if we have a match, if so, return a specific error message - for _, g := range groupsWithMinimalAccessPerms { - // If we have an external group ID or a full path... - if (gitLabOrgModel.ExternalGroupID > 0 && g.ID == gitLabOrgModel.ExternalGroupID) || - (gitLabOrgModel.OrganizationFullPath != "" && g.FullPath == gitLabOrgModel.OrganizationFullPath) { - msg := "" - if gitLabOrgModel.ExternalGroupID > 0 { - msg = fmt.Sprintf("external ID: %d", g.ID) - } else if gitLabOrgModel.OrganizationFullPath != "" { - msg = fmt.Sprintf("full path: '%s'", g.FullPath) - } - return fmt.Errorf("found the GitLab group '%s' by using the %s filter - however the authenticated user does not have maintainer or above permissions for this GitLab group", g.FullPath, msg) - } + msg := "" + if gitLabOrgModel.ExternalGroupID > 0 { + msg = fmt.Sprintf("external ID: %d", gitLabOrgModel.ExternalGroupID) + } else if gitLabOrgModel.OrganizationFullPath != "" { + msg = fmt.Sprintf("full path: '%s'", gitLabOrgModel.OrganizationFullPath) } - return fmt.Errorf("unable to locate GitLab group by using external ID: %d or full path: %s, found %d groups where user has maintainer or above permissions", - gitLabOrgModel.ExternalGroupID, gitLabOrgModel.OrganizationFullPath, len(groupsWithMaintainerPerms)) + return fmt.Errorf("unable to locate the provided GitLab group by %s using the provided permissions - discovered %d groups where user has maintainer or above permissions.", + msg, len(groupsWithMaintainerPerms)) } // UpdateGitLabOrganization updates the GitLab organization From cdce916104ad2a54983105676a6ea804a72f90d3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 24 Aug 2021 16:58:11 -0700 Subject: [PATCH 0457/1276] API Updates to GitLab Group/Org and Repos (#3194) --- cla-backend-go/events/event_data.go | 40 ++++-- .../github_organizations/handlers.go | 4 +- cla-backend-go/repositories/constants.go | 3 + cla-backend-go/swagger/cla.v2.yaml | 74 +++------- .../common/gitlab-organization-create.yaml | 4 +- .../common/gitlab-organization-update.yaml | 7 +- .../common/gitlab-repositories-enable.yaml | 29 ---- .../common/gitlab-repositories-enroll.yaml | 25 ++++ cla-backend-go/tests/utils_list_utils_test.go | 38 ++++++ cla-backend-go/utils/autoenable.go | 4 +- cla-backend-go/utils/context.go | 4 +- cla-backend-go/utils/list_utils.go | 18 +++ cla-backend-go/v2/common/models.go | 1 + .../v2/github_organizations/handlers.go | 4 +- cla-backend-go/v2/gitlab-activity/service.go | 6 +- .../v2/gitlab-activity/service_test.go | 8 +- .../v2/gitlab_organizations/handlers.go | 64 ++++----- .../v2/gitlab_organizations/repository.go | 94 ++++++++----- .../v2/gitlab_organizations/service.go | 120 ++++++++++++++--- .../v2/repositories/gitlab_services.go | 88 ++++++------ cla-backend-go/v2/repositories/handlers.go | 122 +++++------------ cla-backend-go/v2/repositories/models.go | 13 ++ cla-backend-go/v2/repositories/repository.go | 127 ++++++++++++++---- cla-backend-go/v2/repositories/service.go | 12 +- 24 files changed, 541 insertions(+), 368 deletions(-) delete mode 100644 cla-backend-go/swagger/common/gitlab-repositories-enable.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-repositories-enroll.yaml create mode 100644 cla-backend-go/tests/utils_list_utils_test.go create mode 100644 cla-backend-go/utils/list_utils.go create mode 100644 cla-backend-go/v2/repositories/models.go diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 2175748a0..653cc6644 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -208,6 +208,7 @@ type GitlabOrganizationDeletedEventData struct { // GitlabOrganizationUpdatedEventData data model type GitlabOrganizationUpdatedEventData struct { GitlabOrganizationName string + GitLabGroupID int64 AutoEnabled bool AutoEnabledClaGroupID string } @@ -732,13 +733,25 @@ func (ed *GitlabOrganizationDeletedEventData) GetEventDetailsString(args *LogEve // GetEventDetailsString returns the details string for this event func (ed *GitlabOrganizationUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization:%s was updated with auto-enabled: %t", - ed.GitlabOrganizationName, ed.AutoEnabled) + data := "GitHub Organization" // nolint + if ed.GitlabOrganizationName != "" { + data = fmt.Sprintf("%s with name: %s", data, ed.GitlabOrganizationName) + } + if ed.GitLabGroupID > 0 { + data = fmt.Sprintf("%s with group ID: %d", data, ed.GitLabGroupID) + } + data = fmt.Sprintf("%s was updated with auto-enabled: %t", data, ed.AutoEnabled) if ed.AutoEnabledClaGroupID != "" { - data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + data = fmt.Sprintf("%s with auto-enabled-cla-group: %s", data, ed.AutoEnabledClaGroupID) + } + if args.ProjectName != "" { + data = fmt.Sprintf("%s for the project %s", data, args.ProjectName) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" with project SFID %s", args.ProjectName) } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = fmt.Sprintf("%s by the user %s", data, args.UserName) } data = data + "." return data, true @@ -1783,19 +1796,22 @@ func (ed *GitlabOrganizationDeletedEventData) GetEventSummaryString(args *LogEve // GetEventSummaryString returns the summary string for this event func (ed *GitlabOrganizationUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Gitlab Organization: %s was updated with auto-enabled: %t", - ed.GitlabOrganizationName, ed.AutoEnabled) - if ed.AutoEnabledClaGroupID != "" { - data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) + data := "GitHub Organization" // nolint + if ed.GitlabOrganizationName != "" { + data = fmt.Sprintf("%s with name: %s", data, ed.GitlabOrganizationName) } - if args.CLAGroupName != "" { - data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) + if ed.GitLabGroupID > 0 { + data = fmt.Sprintf("%s with group ID: %d", data, ed.GitLabGroupID) + } + data = fmt.Sprintf("%s was updated with auto-enabled: %t", data, ed.AutoEnabled) + if ed.AutoEnabledClaGroupID != "" { + data = fmt.Sprintf("%s with auto-enabled-cla-group: %s", data, ed.AutoEnabledClaGroupID) } if args.ProjectName != "" { - data = data + fmt.Sprintf(" for project %s", args.ProjectName) + data = fmt.Sprintf("%s for the project %s", data, args.ProjectName) } if args.UserName != "" { - data = data + fmt.Sprintf(" by the user %s", args.UserName) + data = fmt.Sprintf("%s by the user %s", data, args.UserName) } data = data + "." return data, true diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index c09ed5ffb..e40db30fe 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -53,7 +53,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev }) } - if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + if !utils.ValidateAutoEnabledClaGroupID(*params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { return github_organizations.NewAddProjectGithubOrganizationBadRequest().WithPayload(&models.ErrorResponse{ Code: "400", Message: "EasyCLA - 400 Bad Request - AutoEnabledClaGroupID can't be empty when AutoEnabled", @@ -148,7 +148,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev }) } - if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + if !utils.ValidateAutoEnabledClaGroupID(*params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ Code: "400", Message: "EasyCLA - 400 Bad Request - AutoEnabledClaGroupID can't be empty when AutoEnabled", diff --git a/cla-backend-go/repositories/constants.go b/cla-backend-go/repositories/constants.go index 69e937a88..c673e5661 100644 --- a/cla-backend-go/repositories/constants.go +++ b/cla-backend-go/repositories/constants.go @@ -42,6 +42,9 @@ const RepositoryDisabled = "disabled" // RepositoryProjectIndex constant const RepositoryProjectIndex = "project-repository-index" +// RepositoryTypeIndex constant +const RepositoryTypeIndex = "repository-type-index" + // RepositoryExternalIDIndex constant const RepositoryExternalIDIndex = "external-repository-index" diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 99f08c281..8809d7dde 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1688,11 +1688,11 @@ paths: tags: - gitlab-organizations - /project/{projectSFID}/gitlab/organizations/{orgName}/config: + /project/{projectSFID}/gitlab/group/{gitLabGroupID}/config: put: - summary: Update Gitlab Organization Configuration - description: Endpoint to adjust the Gitlab Organization Configuration, such as toggling the auto-enable flag - operationId: updateProjectGitlabOrganizationConfig + summary: Update Gitlab Group/Organization Configuration + description: Endpoint to adjust the Gitlab Group/Organization Configuration by GitLab Group ID + operationId: updateProjectGitlabGroupConfig parameters: - $ref: "#/parameters/x-request-id" - $ref: "#/parameters/x-acl" @@ -1702,9 +1702,9 @@ paths: in: path type: string required: true - - name: orgName + - name: gitLabGroupID in: path - type: string + type: integer required: true - in: body name: body @@ -1729,10 +1729,12 @@ paths: tags: - gitlab-organizations + # /project/{projectSFID}/gitlab/organization?organization_full_path=linuxfoundation/product/test: + /project/{projectSFID}/gitlab/organization: delete: - summary: Delete Gitlab organization in the project - description: Endpoint to delete the Gitlab organization for the project - operationId: deleteProjectGitlabOrganization + summary: Delete Gitlab Group/Organization Configuration + description: Endpoint to delete the Gitlab Group/Organization by Group ID + operationId: deleteProjectGitlabGroupConfig parameters: - $ref: "#/parameters/x-request-id" - $ref: "#/parameters/x-acl" @@ -1742,8 +1744,8 @@ paths: in: path type: string required: true - - name: orgName - in: path + - name: organization_full_path + in: query type: string required: true responses: @@ -1766,9 +1768,9 @@ paths: /project/{projectSFID}/gitlab/repositories: put: - summary: Enables GitLab repositories for the CLA Group - description: Endpoint to enable one or more GitLab repositories for the CLA Group - operationId: enableGitLabRepository + summary: Enrolls/Unenrolls GitLab repositories for the CLA Group + description: Endpoint to enroll or unenroll GitLab repositories for the CLA Group + operationId: enrollGitLabRepository parameters: - $ref: "#/parameters/x-request-id" - $ref: "#/parameters/x-acl" @@ -1779,9 +1781,9 @@ paths: type: string required: true - in: body - name: gitlab-repositories-enable + name: gitlab-repositories-enroll schema: - $ref: '#/definitions/gitlab-repositories-enable' + $ref: '#/definitions/gitlab-repositories-enroll' required: true responses: '200': @@ -1839,42 +1841,6 @@ paths: tags: - gitlab-repositories - /project/{projectSFID}/gitlab/repositories/{repositoryExternalID}: - delete: - summary: Unenrolls the GitLab repository from the CLA Group - description: Endpoint to unenroll a GitLab repository from a CLA Group - operationId: unenrollGitLabRepository - parameters: - - $ref: "#/parameters/x-request-id" - - $ref: "#/parameters/x-acl" - - $ref: "#/parameters/x-username" - - $ref: "#/parameters/x-email" - - name: projectSFID - in: path - type: string - required: true - - name: repositoryExternalID - in: path - type: integer - required: true - responses: - '204': - description: 'Resource Deleted' - headers: - x-request-id: - type: string - description: The unique request ID value - assigned/set by the API Gateway based on the session - '400': - $ref: '#/responses/invalid-request' - '401': - $ref: '#/responses/unauthorized' - '403': - $ref: '#/responses/forbidden' - '404': - $ref: '#/responses/not-found' - tags: - - gitlab-repositories - /cla-group/{claGroupID}/icla/signatures: get: summary: List individual signatures for CLA Group @@ -4504,8 +4470,8 @@ definitions: gitlab-repositories-list: $ref: './common/gitlab-repositories-list.yaml' - gitlab-repositories-enable: - $ref: './common/gitlab-repositories-enable.yaml' + gitlab-repositories-enroll: + $ref: './common/gitlab-repositories-enroll.yaml' # --------------------------------------------------------------------------- # CLA Group Definitions diff --git a/cla-backend-go/swagger/common/gitlab-organization-create.yaml b/cla-backend-go/swagger/common/gitlab-organization-create.yaml index 1bb91ac86..fdadba9a0 100644 --- a/cla-backend-go/swagger/common/gitlab-organization-create.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization-create.yaml @@ -27,8 +27,8 @@ properties: description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. default: false auto_enabled_cla_group_id: - type: string - description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. + $ref: './common/properties/internal-id.yaml' + description: Specifies which CLA Group ID to be used when the auto enabled flag in enabled for the GitLab Group/Organization. When the auto enabled flag is set to true, this field needs to be set to a valid CLA Group ID value. branch_protection_enabled: type: boolean description: Flag to indicate if this GitLab Group/Organization is configured to automatically setup branch protection on CLA enabled repositories. diff --git a/cla-backend-go/swagger/common/gitlab-organization-update.yaml b/cla-backend-go/swagger/common/gitlab-organization-update.yaml index dec67909d..fcad44a80 100644 --- a/cla-backend-go/swagger/common/gitlab-organization-update.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization-update.yaml @@ -2,15 +2,14 @@ # SPDX-License-Identifier: MIT type: object -required: - - auto_enabled +description: GitLab Organization Update model properties: auto_enabled: type: boolean description: Flag to indicate if auto-enabled flag should be enabled. Group/Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. auto_enabled_cla_group_id: - type: string - description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the GitLab Group/Organization. If autoEnabled is on this field needs to be set as well. + $ref: './common/properties/internal-id.yaml' + description: Specifies which CLA Group ID to be used when the auto enabled flag in enabled for the GitLab Group/Organization. When the auto enabled flag is set to true, this field needs to be set to a valid CLA Group ID value. branch_protection_enabled: type: boolean description: Flag to indicate if this Group/Organization is configured to automatically setup branch protection on CLA enabled repositories. diff --git a/cla-backend-go/swagger/common/gitlab-repositories-enable.yaml b/cla-backend-go/swagger/common/gitlab-repositories-enable.yaml deleted file mode 100644 index e28d91c14..000000000 --- a/cla-backend-go/swagger/common/gitlab-repositories-enable.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT - -type: object -description: 'GitLab repositories enable model' -properties: - gitlab_organization_name: - type: string - description: The organization name associated with this repository - example: 'The Linux Foundation/product/EasyCLA' - organization_external_id: - type: integer - description: The Gitlab Group/Organization external ID used by GitLab - example: 13050017 - minimum: 1 - organization_full_path: - type: string - description: The Gitlab Group/Organization full path - example: "linuxfoundation/product/easycla" - cla_group_id: - description: CLA Group ID - $ref: './common/properties/internal-id.yaml' - repository_gitlab_ids: - type: array - items: - description: the repository external identifier, such as the GitLab ID of the repository - type: integer - minimum: 1 - example: 7 diff --git a/cla-backend-go/swagger/common/gitlab-repositories-enroll.yaml b/cla-backend-go/swagger/common/gitlab-repositories-enroll.yaml new file mode 100644 index 000000000..6c491a657 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-repositories-enroll.yaml @@ -0,0 +1,25 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +description: 'GitLab repositories enable model' +properties: + cla_group_id: + description: CLA Group ID + $ref: './common/properties/internal-id.yaml' + enroll: + type: array + description: a list of GitLab repositories to enroll + items: + description: the GitLab repository external identifier, such as the GitLab ID of the repository + type: integer + minimum: 1 + example: 7 + unenroll: + type: array + description: a list of GitLab repositories to unenroll + items: + description: the GitLab repository external identifier, such as the GitLab ID of the repository + type: integer + minimum: 1 + example: 7 diff --git a/cla-backend-go/tests/utils_list_utils_test.go b/cla-backend-go/tests/utils_list_utils_test.go new file mode 100644 index 000000000..e6d5f1e08 --- /dev/null +++ b/cla-backend-go/tests/utils_list_utils_test.go @@ -0,0 +1,38 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "testing" + + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestFindInt64Duplicates(t *testing.T) { + type TestCase struct { + A []int64 + B []int64 + Expected []int64 + } + testInputs := []TestCase{ + {nil, nil, []int64{}}, + {nil, []int64{}, []int64{}}, + {[]int64{}, nil, []int64{}}, + {nil, nil, []int64{}}, + {[]int64{}, []int64{}, []int64{}}, + {[]int64{1}, []int64{}, []int64{}}, + {[]int64{}, []int64{1}, []int64{}}, + {[]int64{1, 2}, []int64{}, []int64{}}, + {[]int64{}, []int64{1, 2}, []int64{}}, + {[]int64{1}, []int64{1}, []int64{1}}, + {[]int64{1, 2}, []int64{1}, []int64{1}}, + {[]int64{1, 2}, []int64{1, 3, 4}, []int64{1}}, + {[]int64{1, 2, 3, 4, 5}, []int64{1, 5, 3, 4}, []int64{1, 3, 5, 4}}, + } + + for _, testInput := range testInputs { + assert.ElementsMatch(t, testInput.Expected, utils.FindInt64Duplicates(testInput.A, testInput.B)) + } +} diff --git a/cla-backend-go/utils/autoenable.go b/cla-backend-go/utils/autoenable.go index a3283bfee..478019ba7 100644 --- a/cla-backend-go/utils/autoenable.go +++ b/cla-backend-go/utils/autoenable.go @@ -4,8 +4,8 @@ package utils // ValidateAutoEnabledClaGroupID checks for validation if autoEnabled flag is on autoEnabledClaGroupID is enabled as well -func ValidateAutoEnabledClaGroupID(autoEnabled *bool, autoEnabledClaGroupID string) bool { - if autoEnabled == nil || !*autoEnabled { +func ValidateAutoEnabledClaGroupID(autoEnabled bool, autoEnabledClaGroupID string) bool { + if !autoEnabled { return true } diff --git a/cla-backend-go/utils/context.go b/cla-backend-go/utils/context.go index e1be4a955..49e612a72 100644 --- a/cla-backend-go/utils/context.go +++ b/cla-backend-go/utils/context.go @@ -39,12 +39,12 @@ func NewContextWithUser(authUser *auth.User) context.Context { return context.Background() } - return context.WithValue(context.WithValue(context.Background(), XREQUESTID, requestID), "authUser", authUser) // nolint + return context.WithValue(context.WithValue(context.Background(), XREQUESTID, requestID), CtxAuthUser, authUser) // nolint } // ContextWithRequestAndUser returns a new context with the specified request ID and user func ContextWithRequestAndUser(ctx context.Context, reqID string, authUser *auth.User) context.Context { - return context.WithValue(context.WithValue(ctx, XREQUESTID, reqID), "authUser", authUser) // nolint + return context.WithValue(context.WithValue(ctx, XREQUESTID, reqID), CtxAuthUser, authUser) // nolint } // ContextWithUser returns a new context with the specified user diff --git a/cla-backend-go/utils/list_utils.go b/cla-backend-go/utils/list_utils.go new file mode 100644 index 000000000..999a98a11 --- /dev/null +++ b/cla-backend-go/utils/list_utils.go @@ -0,0 +1,18 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +// FindInt64Duplicates returns true if the two lists include any duplicates, false otherwise. Returns the duplicates +func FindInt64Duplicates(a, b []int64) []int64 { + var duplicates []int64 + for i := 0; i < len(a); i++ { + for j := 0; j < len(b); j++ { + if a[i] == b[j] { + duplicates = append(duplicates, a[i]) + } + } + } + + return duplicates +} diff --git a/cla-backend-go/v2/common/models.go b/cla-backend-go/v2/common/models.go index 6fbd36291..5b94929c3 100644 --- a/cla-backend-go/v2/common/models.go +++ b/cla-backend-go/v2/common/models.go @@ -25,6 +25,7 @@ type GitLabOrganization struct { AutoEnabledClaGroupID string `json:"auto_enabled_cla_group_id,omitempty"` AuthInfo string `json:"auth_info"` AuthState string `json:"auth_state"` + Note string `json:"note,omitempty"` Version string `json:"version,omitempty"` } diff --git a/cla-backend-go/v2/github_organizations/handlers.go b/cla-backend-go/v2/github_organizations/handlers.go index a08053bd7..1d53a10eb 100644 --- a/cla-backend-go/v2/github_organizations/handlers.go +++ b/cla-backend-go/v2/github_organizations/handlers.go @@ -112,7 +112,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + if !utils.ValidateAutoEnabledClaGroupID(*params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { msg := "AutoEnabledClaGroupID can't be empty when AutoEnabled" log.WithFields(f).WithError(err).Warn(msg) return github_organizations.NewAddProjectGithubOrganizationBadRequest().WithPayload( @@ -213,7 +213,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + if !utils.ValidateAutoEnabledClaGroupID(*params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { msg := fmt.Sprintf("AutoEnabledClaGroupID can't be empty when AutoEnabled flag is set to true - issue in request body for project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) log.WithFields(f).Debug(msg) return github_organizations.NewUpdateProjectGithubOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 1d209d7ea..103300a65 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -182,9 +182,9 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git landingPage += "/#/?version=2" var badgeHyperlink string - if len(missingUsers) > 0{ + if len(missingUsers) > 0 { badgeHyperlink = signURL - }else{ + } else { badgeHyperlink = landingPage } @@ -252,7 +252,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git result += "" } - if result != ""{ + if result != "" { body += "

    " + result } diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index 1dd8a458b..14c98dfef 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -125,7 +125,7 @@ func TestPrepareMrCommentContent(t *testing.T) { {ID: 1, Username: "neo"}, {ID: 2, Username: "oracle"}, }, - expectedMsgs: []string{signedContains, signedContains}, + expectedMsgs: []string{signedContains, signedContains}, expectedBadge: "cla-signed.svg", }, { @@ -136,7 +136,7 @@ func TestPrepareMrCommentContent(t *testing.T) { missing: []*gatedGitlabUser{ {err: missingID, User: &gitlab.User{ID: 3, Username: "missing"}}, }, - expectedMsgs: []string{signedContains, missingUserContains}, + expectedMsgs: []string{signedContains, missingUserContains}, expectedBadge: "cla-missing-id.svg", }, { @@ -147,7 +147,7 @@ func TestPrepareMrCommentContent(t *testing.T) { missing: []*gatedGitlabUser{ {err: missingCompanyAffiliation, User: &gitlab.User{ID: 4, Username: "affiliationUser"}}, }, - expectedMsgs: []string{signedContains, missingAffiliationContains}, + expectedMsgs: []string{signedContains, missingAffiliationContains}, expectedBadge: "cla-confirmation-needed.svg", }, { @@ -158,7 +158,7 @@ func TestPrepareMrCommentContent(t *testing.T) { missing: []*gatedGitlabUser{ {err: missingCompanyApproval, User: &gitlab.User{ID: 5, Username: "approvalUser"}}, }, - expectedMsgs: []string{signedContains, missingApprovalContains}, + expectedMsgs: []string{signedContains, missingApprovalContains}, expectedBadge: "cla-not-signed.svg", }, } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 9fd0214d7..e91f48e27 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -21,7 +21,6 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" - "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/sirupsen/logrus" @@ -160,7 +159,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic f["autoEnabled"] = utils.BoolValue(params.Body.AutoEnabled) f["autoEnabledClaGroupID"] = params.Body.AutoEnabledClaGroupID - if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { + if !utils.ValidateAutoEnabledClaGroupID(*params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { msg := "AutoEnabledClaGroupID can't be empty when AutoEnabled" err := fmt.Errorf(msg) log.WithFields(f).Warn(msg) @@ -194,29 +193,21 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_organizations.NewAddProjectGitlabOrganizationOK().WithPayload(result) }) - api.GitlabOrganizationsUpdateProjectGitlabOrganizationConfigHandler = gitlab_organizations.UpdateProjectGitlabOrganizationConfigHandlerFunc(func(params gitlab_organizations.UpdateProjectGitlabOrganizationConfigParams, authUser *auth.User) middleware.Responder { + api.GitlabOrganizationsUpdateProjectGitlabGroupConfigHandler = gitlab_organizations.UpdateProjectGitlabGroupConfigHandlerFunc(func(params gitlab_organizations.UpdateProjectGitlabGroupConfigParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint - if params.Body.AutoEnabled == nil { - return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "EasyCLA - 400 Bad Request - missing auto enable value in body", - }) - } if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { - return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(&models.ErrorResponse{ - Code: "400", - Message: "EasyCLA - 400 Bad Request - AutoEnabledClaGroupID can't be empty when AutoEnabled", - }) + msg := "AutoEnabledClaGroupID can't be empty when AutoEnabled is set to true" + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - err := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, 0, params.OrgName, "", *params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + err := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, params.GitLabGroupID, "", "", params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) if err != nil { if errors.Is(err, projects_cla_groups.ErrCLAGroupDoesNotExist) { - return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, err.Error())) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, err.Error())) } - return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, "updating gitlab org", err)) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, "updating gitlab org", err)) } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ @@ -225,52 +216,53 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic LfUsername: authUser.UserName, UserName: authUser.UserName, EventData: &events.GitlabOrganizationUpdatedEventData{ - GitlabOrganizationName: params.OrgName, - AutoEnabled: *params.Body.AutoEnabled, + GitLabGroupID: params.GitLabGroupID, + AutoEnabledClaGroupID: params.Body.AutoEnabledClaGroupID, + AutoEnabled: params.Body.AutoEnabled, }, }) - return gitlab_organizations.NewUpdateProjectGitlabOrganizationConfigOK() + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigOK() }) - api.GitlabOrganizationsDeleteProjectGitlabOrganizationHandler = gitlab_organizations.DeleteProjectGitlabOrganizationHandlerFunc(func(params gitlab_organizations.DeleteProjectGitlabOrganizationParams, authUser *auth.User) middleware.Responder { + api.GitlabOrganizationsDeleteProjectGitlabGroupConfigHandler = gitlab_organizations.DeleteProjectGitlabGroupConfigHandlerFunc(func(params gitlab_organizations.DeleteProjectGitlabGroupConfigParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsDeleteProjectGitlabOrganizationHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": params.ProjectSFID, - "orgName": params.OrgName, - "authUser": authUser.UserName, - "authEmail": authUser.Email, + "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsDeleteProjectGitlabGroupConfigHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": params.ProjectSFID, + "organizationFullPath": params.OrganizationFullPath, + "authUser": authUser.UserName, + "authEmail": authUser.Email, } // Load the project psc := project_service.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { - return gitlab_organizations.NewDeleteProjectGitlabOrganizationNotFound().WithPayload( + return gitlab_organizations.NewDeleteProjectGitlabGroupConfigNotFound().WithPayload( utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Delete Project GitLab Organizations for Project '%s' with scope of %s", + msg := fmt.Sprintf("user %s does not have access to Delete Project GitLab Group/Organizations for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) - return gitlab_organizations.NewDeleteProjectGitlabOrganizationForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + return gitlab_organizations.NewDeleteProjectGitlabGroupConfigForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - err = service.DeleteGitLabOrganization(ctx, params.ProjectSFID, params.OrgName) + err = service.DeleteGitLabOrganizationByFullPath(ctx, params.ProjectSFID, params.OrganizationFullPath) if err != nil { if strings.Contains(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("project not found with given SFID: %s", params.ProjectSFID) log.WithFields(f).Debug(msg) - return gitlab_organizations.NewDeleteProjectGitlabOrganizationNotFound().WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) + return gitlab_organizations.NewDeleteProjectGitlabGroupConfigNotFound().WithPayload(utils.ErrorResponseNotFoundWithError(reqID, msg, err)) } - msg := fmt.Sprintf("problem deleting Gitlab Organization with project SFID: %s for organization: %s", params.ProjectSFID, params.OrgName) - log.WithFields(f).Debug(msg) - return gitlab_organizations.NewDeleteProjectGitlabOrganizationBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + msg := fmt.Sprintf("problem deleting Gitlab Group with project SFID: %s with path: %s", params.ProjectSFID, params.OrganizationFullPath) + log.WithFields(f).Warn(msg) + return gitlab_organizations.NewDeleteProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ @@ -278,11 +270,11 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic EventType: events.GitlabOrganizationDeleted, ProjectSFID: params.ProjectSFID, EventData: &events.GitlabOrganizationDeletedEventData{ - GitlabOrganizationName: params.OrgName, + GitlabOrganizationName: params.OrganizationFullPath, }, }) - return gitlab_organizations.NewDeleteProjectGitlabOrganizationNoContent() + return gitlab_organizations.NewDeleteProjectGitlabGroupConfigNoContent() }) api.GitlabActivityGitlabOauthCallbackHandler = gitlab_activity.GitlabOauthCallbackHandlerFunc(func(params gitlab_activity.GitlabOauthCallbackParams) middleware.Responder { diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index 54d206003..af939445b 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -48,7 +48,7 @@ type RepositoryInterface interface { GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error + DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgFullPath string) error } // Repository object/struct @@ -212,7 +212,7 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID // Use the nice builder to create the expression expr, err := builder.Build() if err != nil { - log.WithFields(f).Warnf("problem building query expression, error: %+v2Models", err) + log.WithFields(f).WithError(err).Warnf("problem building query expression, error: %+v", err) return nil, err } @@ -293,7 +293,7 @@ func (repo *Repository) GetGitLabOrganizationByName(ctx context.Context, gitLabO var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { - log.WithFields(f).Warnf("problem decoding database results, error: %+v2Models", err) + log.WithFields(f).WithError(err).Warnf("problem decoding database results, error: %+v", err) return nil, err } @@ -341,7 +341,7 @@ func (repo *Repository) GetGitLabOrganizationByExternalID(ctx context.Context, g var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { - log.WithFields(f).Warnf("problem decoding database results, error: %+v2Models", err) + log.WithFields(f).WithError(err).Warnf("problem decoding database results, error: %+v", err) return nil, err } @@ -389,7 +389,7 @@ func (repo *Repository) GetGitLabOrganizationByFullPath(ctx context.Context, gro var resultOutput []*common.GitLabOrganization err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) if err != nil { - log.WithFields(f).Warnf("problem decoding database results, error: %+v2Models", err) + log.WithFields(f).WithError(err).Warnf("problem decoding database results, error: %+v", err) return nil, err } @@ -445,7 +445,7 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi _, currentTime := utils.CurrentTime() gitlabOrg, lookupErr := repo.GetGitLabOrganization(ctx, organizationID) if lookupErr != nil || gitlabOrg == nil { - log.WithFields(f).Warnf("error looking up Gitlab organization by id: %s, error: %+v2Models", organizationID, lookupErr) + log.WithFields(f).WithError(lookupErr).Warnf("error looking up Gitlab organization by id: %s, error: %+v", organizationID, lookupErr) return lookupErr } @@ -500,7 +500,7 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi log.WithFields(f).Debug("updating gitlab organization record...") _, updateErr := repo.dynamoDBClient.UpdateItem(input) if updateErr != nil { - log.WithFields(f).Warnf("unable to update Gitlab organization record, error: %+v2Models", updateErr) + log.WithFields(f).WithError(updateErr).Warnf("unable to update Gitlab organization record, error: %+v", updateErr) return updateErr } @@ -510,7 +510,7 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi // UpdateGitLabOrganization updates the GitLab group based on the specified values func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { f := logrus.Fields{ - "functionName": "gitlab_organizations.repository.UpdateGitLabOrganizationByExternalID", + "functionName": "gitlab_organizations.repository.UpdateGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, "groupID": groupID, @@ -528,7 +528,7 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", groupID) existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) if getErr != nil { - msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v2Models", groupID, groupFullPath) + msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v", groupID, groupFullPath) log.WithFields(f).WithError(getErr).Warn(msg) return errors.New(msg) } @@ -536,7 +536,7 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", groupFullPath) existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) if getErr != nil { - msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v2Models", groupFullPath, getErr) + msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v", groupFullPath, getErr) log.WithFields(f).WithError(getErr).Warn(msg) return errors.New(msg) } @@ -597,55 +597,60 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI TableName: aws.String(repo.gitlabOrgTableName), } - log.WithFields(f).Debugf("updating GitLab organization record: %+v2Models", input) + log.WithFields(f).Debugf("updating GitLab organization record: %+v", input) _, updateErr := repo.dynamoDBClient.UpdateItem(input) if updateErr != nil { - log.WithFields(f).Warnf("unable to update GitLab organization record, error: %+v2Models", updateErr) + log.WithFields(f).WithError(updateErr).Warnf("unable to update GitLab organization record, error: %+v", updateErr) return updateErr } return nil } -// DeleteGitLabOrganization deletes the specified GitLab organization -func (repo *Repository) DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error { +// DeleteGitLabOrganizationByFullPath deletes the specified GitLab organization +func (repo *Repository) DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgFullPath string) error { f := logrus.Fields{ - "functionName": "v1.gitlab_organizations.repository.DeleteGitLabOrganization", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "gitlabOrgName": gitlabOrgName, + "functionName": "v1.gitlab_organizations.repository.DeleteGitLabOrganizationByFullPath", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "gitlabOrgFullPath": gitlabOrgFullPath, } - var gitlabOrganizationID string - orgs, orgErr := repo.GetGitLabOrganizations(ctx, projectSFID) + log.WithFields(f).Debugf("loading GitLab group/organizations list for path: %s", gitlabOrgFullPath) + org, orgErr := repo.GetGitLabOrganizationByFullPath(ctx, gitlabOrgFullPath) if orgErr != nil { - errMsg := fmt.Sprintf("gitlab organization is not found using projectSFID: %s, error: %+v2Models", projectSFID, orgErr) - log.WithFields(f).Warn(errMsg) + errMsg := fmt.Sprintf("GitLab group/organization is not found using group/organization: %s, error: %+v", gitlabOrgFullPath, orgErr) + log.WithFields(f).WithError(orgErr).Warn(errMsg) return errors.New(errMsg) } - - for _, gitLabOrg := range orgs.List { - if strings.EqualFold(gitLabOrg.OrganizationName, gitlabOrgName) { - gitlabOrganizationID = gitLabOrg.OrganizationID - break - } + // Nothing to delete or disable + if org == nil || !org.Enabled { + return nil } - log.WithFields(f).Debug("Deleting GitLab organization...") + log.WithFields(f).Debugf("deleting GitLab group/organization under path: %s...", gitlabOrgFullPath) // Update enabled flag as false _, currentTime := utils.CurrentTime() - note := fmt.Sprintf("Enabled set to false due to org deletion at %s ", currentTime) + note := fmt.Sprintf("Enabled set to false due to org deletion on %s by %s.", currentTime, utils.GetUserNameFromContext(ctx)) + if org.Note != "" { + note = fmt.Sprintf("%s. %s.", org.Note, note) + } _, err := repo.dynamoDBClient.UpdateItem( &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ GitLabOrganizationsOrganizationIDColumn: { - S: aws.String(gitlabOrganizationID), + S: aws.String(org.OrganizationID), }, }, ExpressionAttributeNames: map[string]*string{ - "#E": aws.String(GitLabOrganizationsEnabledColumn), - "#N": aws.String(GitLabOrganizationsNoteColumn), - "#D": aws.String(GitLabOrganizationsDateModifiedColumn), + "#E": aws.String(GitLabOrganizationsEnabledColumn), + "#N": aws.String(GitLabOrganizationsNoteColumn), + "#D": aws.String(GitLabOrganizationsDateModifiedColumn), + "#AI": aws.String(GitLabOrganizationsAuthInfoColumn), + "#AE": aws.String(GitLabOrganizationsAutoEnabledColumn), + "#AECLA": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), + "#EID": aws.String(GitLabOrganizationsExternalGitLabGroupIDColumn), + "#BP": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), }, ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":e": { @@ -657,14 +662,29 @@ func (repo *Repository) DeleteGitLabOrganization(ctx context.Context, projectSFI ":d": { S: aws.String(currentTime), }, + ":ai": { + S: aws.String(""), + }, + ":ae": { + BOOL: aws.Bool(false), + }, + ":aecla": { + S: aws.String(""), + }, + ":eid": { + N: aws.String("0"), + }, + ":bp": { + BOOL: aws.Bool(false), + }, }, - UpdateExpression: aws.String("SET #E = :e, #N = :n, #D = :d"), + UpdateExpression: aws.String("SET #E = :e, #N = :n, #D = :d, #AI = :ai, #AE = :ae, #AECLA = :aecla, #EID = :eid, #BP = :bp"), TableName: aws.String(repo.gitlabOrgTableName), }, ) if err != nil { - errMsg := fmt.Sprintf("error deleting gitlab organization: %s - %+v2Models", gitlabOrgName, err) - log.WithFields(f).Warnf(errMsg) + errMsg := fmt.Sprintf("error updating gitlab organization by path: %s using GitLab group/organization ID: %s - %+v", gitlabOrgFullPath, org.OrganizationID, err) + log.WithFields(f).WithError(err).Warnf(errMsg) return errors.New(errMsg) } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 5117ac230..d4c81331d 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -37,11 +37,13 @@ type ServiceInterface interface { GetGitLabOrganization(ctx context.Context, gitLabOrganizationID string) (*models.GitlabOrganization, error) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) + GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*models.GitlabOrganization, error) + GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error - DeleteGitLabOrganization(ctx context.Context, projectSFID string, gitlabOrgName string) error + DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID string, gitlabOrgFullPath string) error } // Service data model @@ -74,6 +76,32 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, "groupFullPath": input.OrganizationFullPath, } + var existingModel *models.GitlabOrganization + var getErr error + if input.OrganizationFullPath != "" { + existingModel, getErr = s.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) + if getErr != nil { + log.WithFields(f).WithError(getErr).Warnf("problem querying GitLab group/organization using full path: %s", input.OrganizationFullPath) + return nil, getErr + } + } + if input.GroupID > 0 { + existingModel, getErr = s.GetGitLabOrganizationByGroupID(ctx, input.GroupID) + if getErr != nil { + log.WithFields(f).WithError(getErr).Warnf("problem querying GitLab group/organization using group ID: %d", input.GroupID) + return nil, getErr + } + } + + if existingModel != nil { + updateErr := s.UpdateGitLabOrganization(ctx, projectSFID, input.GroupID, "", input.OrganizationFullPath, utils.BoolValue(input.AutoEnabled), input.AutoEnabledClaGroupID, utils.BoolValue(input.BranchProtectionEnabled)) + if updateErr != nil { + log.WithFields(f).WithError(updateErr).Warnf("problem updating GitLab group/organization, error: %+v", updateErr) + return nil, getErr + } + return s.GetGitLabOrganizations(ctx, projectSFID) + } + psc := v2ProjectService.GetClient() project, err := psc.GetProject(projectSFID) if err != nil { @@ -157,7 +185,40 @@ func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganiz } return common.ToModel(dbModel), nil +} + +// GetGitLabOrganizationByFullPath returns the GitLab group/organization using the specified full path +func (s *Service) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByFullPath", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabOrganizationFullPath": gitLabOrganizationFullPath, + } + + log.WithFields(f).Debugf("fetching gitlab group/organization using full path: %s", gitLabOrganizationFullPath) + dbModel, err := s.repo.GetGitLabOrganizationByFullPath(ctx, gitLabOrganizationFullPath) + if err != nil { + return nil, err + } + + return common.ToModel(dbModel), nil +} + +// GetGitLabOrganizationByGroupID returns the GitLab group/organization using the specified group ID +func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByGroupID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabGroupID": gitLabGroupID, + } + + log.WithFields(f).Debugf("fetching gitlab group/organization using group ID: %d", gitLabGroupID) + dbModel, err := s.repo.GetGitLabOrganizationByExternalID(ctx, gitLabGroupID) + if err != nil { + return nil, err + } + return common.ToModel(dbModel), nil } // GetGitLabOrganizations returns a collection of GitLab organizations based on the specified project SFID value @@ -225,7 +286,7 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { log.WithFields(f).WithError(repoErr).Debugf("no GitLab repositories onboarded for project : %s", projectSFID) } else { - log.WithFields(f).WithError(repoErr).Debugf("unexpected error while fetching GitLab group repositories for project: %s, error: %v", projectSFID, repoErr) + log.WithFields(f).WithError(repoErr).Debugf("unexpected error while fetching GitLab group repositories for project: %s, error type: %T, error: %v", projectSFID, repoErr, repoErr) } } @@ -389,32 +450,49 @@ func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID stri return s.repo.UpdateGitLabOrganization(ctx, projectSFID, groupID, organizationName, groupFullPath, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) } -// DeleteGitLabOrganization deletes the specified GitLab organization -func (s *Service) DeleteGitLabOrganization(ctx context.Context, projectSFID string, gitLabOrgName string) error { +// DeleteGitLabOrganizationByFullPath deletes the specified GitLab organization by full path +func (s *Service) DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID string, gitLabOrgFullPath string) error { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.DeleteGitLabOrganization", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "gitLabOrgName": gitLabOrgName, + "functionName": "v2.gitlab_organizations.service.DeleteGitLabOrganizationByFullPath", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "gitLabOrgFullPath": gitLabOrgFullPath, + } + + // Check for enabled repos... + repoList, getRepListErr := s.v2GitRepoService.GitLabGetRepositoriesByProjectSFID(ctx, projectSFID) + if getRepListErr != nil { + // If nothing to delete... + if _, ok := getRepListErr.(*utils.GitLabRepositoryNotFound); ok { + log.WithFields(f).Debugf("no repositories found under GitLab group/organization: %s", gitLabOrgFullPath) + } else { + return getRepListErr + } } - // Lookup the parent - parentProjectSFID, projErr := v2ProjectService.GetClient().GetParentProject(projectSFID) - if projErr != nil { - log.WithFields(f).Warnf("problem fetching project parent SFID, error: %+v", projErr) - return projErr + // Check to see if we still have enabled repos belonging to this GitLab organization/group + var enabledRepoList []string + if repoList != nil && len(repoList.List) > 0 { + for _, repo := range repoList.List { + if strings.HasPrefix(repo.RepositoryName, gitLabOrgFullPath) && repo.Enabled { + enabledRepoList = append(enabledRepoList, repo.RepositoryName) + } + } } - log.WithFields(f).Debugf("retrieved parent of project sfid : %s -> %s", projectSFID, parentProjectSFID) + if len(enabledRepoList) > 0 { + return fmt.Errorf("the following repositories are still enabled under the GitLab Group/Organization: %s - %s", gitLabOrgFullPath, strings.Join(enabledRepoList, ",")) + } - // Todo: Enable this when the repositories are implemented - //err := s.ghRepository.GitHubDisableRepositoriesOfOrganization(ctx, parentProjectSFID, gitLabOrgName) - //if err != nil { - // log.WithFields(f).Warnf("problem disabling repositories for github organizations, error: %+v", projErr) - // return err - //} + // First delete the GitLab project/repos + log.WithFields(f).Debugf("deleting GitLab repos under group: %s", gitLabOrgFullPath) + repoDeleteErr := s.v2GitRepoService.GitLabDeleteRepositories(ctx, gitLabOrgFullPath) + if repoDeleteErr != nil { + log.WithFields(f).WithError(repoDeleteErr).Warnf("problem deleting GitLab repos under group: %s", gitLabOrgFullPath) + return repoDeleteErr + } - return s.repo.DeleteGitLabOrganization(ctx, projectSFID, gitLabOrgName) + return s.repo.DeleteGitLabOrganizationByFullPath(ctx, projectSFID, gitLabOrgFullPath) } func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI { diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 303ececdf..83d69b212 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "sort" "strconv" "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -25,38 +26,38 @@ import ( ) // GitLabAddRepositories service function -func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesEnable) (*v2Models.GitlabRepositoriesList, error) { +func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, input *GitLabAddRepoModel) (*v2Models.GitlabRepositoriesList, error) { f := logrus.Fields{ - "functionName": "v2.repositories.gitlab_services.GitLabAddRepositories", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "organizationName": input.GitlabOrganizationName, - "claGroupID": input.ClaGroupID, - "groupFullPath": input.OrganizationFullPath, - "groupID": input.OrganizationExternalID, + "functionName": "v2.repositories.gitlab_services.GitLabAddRepositories", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": projectSFID, + "groupName": input.GroupName, + "claGroupID": input.ClaGroupID, + "groupFullPath": input.GroupFullPath, + "groupID": input.ExternalID, } var gitLabOrgModel *common.GitLabOrganization var getOrgErr error - if input.GitlabOrganizationName != "" { - log.WithFields(f).Debugf("fetching GitLab organization by name: %s", input.GitlabOrganizationName) - gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByName(ctx, input.GitlabOrganizationName) + if input.GroupName != "" { + log.WithFields(f).Debugf("fetching GitLab group/organization by name: %s", input.GroupName) + gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByName(ctx, input.GroupName) if getOrgErr != nil { - msg := fmt.Sprintf("problem loading GitLab organization by name: %s, error: %v", input.GitlabOrganizationName, getOrgErr) + msg := fmt.Sprintf("problem loading GitLab group/organization by name: %s, error: %v", input.GroupName, getOrgErr) log.WithFields(f).WithError(getOrgErr).Warn(msg) return nil, errors.New(msg) } - } else if input.OrganizationFullPath != "" { - log.WithFields(f).Debugf("fetching GitLab organization by full path: %s", input.OrganizationFullPath) - gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) + } else if input.GroupFullPath != "" { + log.WithFields(f).Debugf("fetching GitLab group/organization by full path: %s", input.GroupFullPath) + gitLabOrgModel, getOrgErr = s.glOrgRepo.GetGitLabOrganizationByFullPath(ctx, input.GroupFullPath) if getOrgErr != nil { - msg := fmt.Sprintf("problem loading GitLab organization by full path: %s, error: %v", input.OrganizationFullPath, getOrgErr) + msg := fmt.Sprintf("problem loading GitLab group/organization by full path: %s, error: %v", input.GroupFullPath, getOrgErr) log.WithFields(f).WithError(getOrgErr).Warn(msg) return nil, errors.New(msg) } } if gitLabOrgModel == nil { - msg := fmt.Sprintf("problem loading GitLab organization by name '%s' or full path '%s'", input.GitlabOrganizationName, input.OrganizationFullPath) + msg := fmt.Sprintf("problem loading GitLab group/organization by name '%s' or full path '%s'", input.GroupName, input.GroupFullPath) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -73,10 +74,10 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, RepositoryFullPath string Error error } - addRepoRespChan := make(chan *GitLabAddRepositoryResponse, len(input.RepositoryGitlabIds)) + addRepoRespChan := make(chan *GitLabAddRepositoryResponse, len(input.ProjectIDList)) // Add each repo - could be a lot of repos, so we run this in a go routine - for _, gitLabProjectID := range input.RepositoryGitlabIds { + for _, gitLabProjectID := range input.ProjectIDList { go func(gitLabProjectID int) { log.WithFields(f).Debugf("loading GitLab project from GitLab using projectID: %d...", gitLabProjectID) project, getProjectErr := gitlab_api.GetProjectByID(ctx, gitLabClient, gitLabProjectID) @@ -99,7 +100,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, RepositoryExternalID: repositoryExternalIDString, RepositoryName: project.PathWithNamespace, // Name column is actually the full path for both GitHub and GitLab RepositoryURL: project.WebURL, - RepositoryOrganizationName: input.GitlabOrganizationName, + RepositoryOrganizationName: input.GroupName, RepositoryCLAGroupID: input.ClaGroupID, RepositoryType: utils.GitLabLower, // should always be gitlab Enabled: false, // we don't enable by default @@ -135,7 +136,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, // Wait for the go routines to finish and load up the results log.WithFields(f).Debug("waiting for add repos to finish...") var lastErr error - for range input.RepositoryGitlabIds { + for range input.ProjectIDList { select { case response := <-addRepoRespChan: if response.Error != nil { @@ -195,12 +196,12 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel } // Build input to the add function - input := &v2Models.GitlabRepositoriesEnable{ - ClaGroupID: projectCLAGroupModel.ClaGroupID, - GitlabOrganizationName: gitLabOrgModel.OrganizationName, - OrganizationExternalID: int64(gitLabOrgModel.ExternalGroupID), - OrganizationFullPath: gitLabOrgModel.OrganizationFullPath, - RepositoryGitlabIds: listProjectIDs, + input := &GitLabAddRepoModel{ + ClaGroupID: projectCLAGroupModel.ClaGroupID, + GroupName: gitLabOrgModel.OrganizationName, + GroupFullPath: gitLabOrgModel.OrganizationFullPath, + ExternalID: int64(gitLabOrgModel.ExternalGroupID), + ProjectIDList: listProjectIDs, } log.WithFields(f).Debugf("adding %d GitLab repositories", len(listProjectIDs)) _, addRepoErr := s.GitLabAddRepositories(ctx, gitLabOrgModel.ProjectSFID, input) @@ -262,6 +263,11 @@ func (s *Service) GitLabGetRepositoriesByProjectSFID(ctx context.Context, projec return nil, err } + // sort result by name + sort.Slice(responses, func(i, j int) bool { + return responses[i].RepositoryName < responses[j].RepositoryName + }) + return &v2Models.GitlabRepositoriesList{ List: responses, }, nil @@ -307,11 +313,13 @@ func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, o }, nil } -// GitLabEnableRepositories assigns repos to -func (s *Service) GitLabEnableRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64) error { +// GitLabEnrollRepositories assigns repos to a CLA Group +func (s *Service) GitLabEnrollRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64, enrollValue bool) error { f := logrus.Fields{ - "functionName": "v2.repositories.gitlab_services.GitLabEnableRepositories", + "functionName": "v2.repositories.gitlab_services.GitLabEnrollRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "claGroupID": claGroupID, + "enrollValue": enrollValue, } type GitLabUpdateRepositoryResponse struct { @@ -322,7 +330,7 @@ func (s *Service) GitLabEnableRepositories(ctx context.Context, claGroupID strin for _, repoID := range repositoryIDList { go func(claGroupID string, repoID int64) { - updateErr := s.GitLabEnableRepository(ctx, claGroupID, repoID) + updateErr := s.GitLabEnrollRepository(ctx, claGroupID, repoID, enrollValue) updateRepoChan <- &GitLabUpdateRepositoryResponse{ RepositoryID: repoID, Error: updateErr, @@ -351,19 +359,19 @@ func (s *Service) GitLabEnableRepositories(ctx context.Context, claGroupID strin return lastErr } -// GitLabEnableRepository service function -func (s *Service) GitLabEnableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error { - return s.gitV2Repository.GitLabEnableRepositoryByID(ctx, claGroupID, repositoryExternalID) +// GitLabEnrollRepository service function enrolls a single GitLab repository to the specified CLA Group +func (s *Service) GitLabEnrollRepository(ctx context.Context, claGroupID string, repositoryExternalID int64, enrollValue bool) error { + return s.gitV2Repository.GitLabEnrollRepositoryByID(ctx, claGroupID, repositoryExternalID, enrollValue) } -// GitLabDisableRepository service function -func (s *Service) GitLabDisableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error { - return s.gitV2Repository.GitLabDisableRepositoryByID(ctx, claGroupID, repositoryExternalID) +// GitLabEnrollCLAGroupRepositories service function +func (s *Service) GitLabEnrollCLAGroupRepositories(ctx context.Context, claGroupID string, enrollValue bool) error { + return s.gitV2Repository.GitLabEnableCLAGroupRepositories(ctx, claGroupID, enrollValue) } -// GitLabDisableCLAGroupRepositories service function -func (s *Service) GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { - return s.gitV2Repository.GitLabDisableCLAGroupRepositories(ctx, claGroupID) +// GitLabDeleteRepositories deletes the repositories under the specified path +func (s *Service) GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error { + return s.gitV2Repository.GitLabDeleteRepositories(ctx, gitLabGroupPath) } // dbModelToGitLabRepository converts the database model to a v2 response model diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index 9c66a9e2f..c547cab27 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -456,28 +456,25 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_repositories.NewGetProjectGitLabRepositoriesOK().WithPayload(response) }) - api.GitlabRepositoriesEnableGitLabRepositoryHandler = gitlab_repositories.EnableGitLabRepositoryHandlerFunc( - func(params gitlab_repositories.EnableGitLabRepositoryParams, authUser *auth.User) middleware.Responder { + api.GitlabRepositoriesEnrollGitLabRepositoryHandler = gitlab_repositories.EnrollGitLabRepositoryHandlerFunc( + func(params gitlab_repositories.EnrollGitLabRepositoryParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint f := logrus.Fields{ - "functionName": "v2.repositories.handlers.GitlabRepositoriesEnableGitLabRepositoryHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "authUser": authUser.UserName, - "authEmail": authUser.Email, - "projectSFID": params.ProjectSFID, - "organizationName": params.GitlabRepositoriesEnable.GitlabOrganizationName, - "claGroupID": params.GitlabRepositoriesEnable.ClaGroupID, - "groupFullPath": params.GitlabRepositoriesEnable.OrganizationFullPath, - "groupID": params.GitlabRepositoriesEnable.OrganizationExternalID, + "functionName": "v2.repositories.handlers.GitlabRepositoriesEnableGitLabRepositoryHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "claGroupID": params.GitlabRepositoriesEnroll.ClaGroupID, } // Load the project psc := project_service.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { - return gitlab_repositories.NewEnableGitLabRepositoryNotFound().WithPayload( + return gitlab_repositories.NewEnrollGitLabRepositoryNotFound().WithPayload( utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) } @@ -485,91 +482,46 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic msg := fmt.Sprintf("user %s does not have access to Add GitLab Repositories for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) log.WithFields(f).Debug(msg) - return gitlab_repositories.NewEnableGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) - } - - if len(params.GitlabRepositoriesEnable.RepositoryGitlabIds) == 0 { - msg := "missing repository GitLab ID values" - return gitlab_repositories.NewEnableGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + return gitlab_repositories.NewEnrollGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } - log.WithFields(f).Debugf("assigning GitLab repository for project: %s and CLA Group: %s", params.ProjectSFID, params.GitlabRepositoriesEnable.ClaGroupID) - enableErr := service.GitLabEnableRepositories(ctx, params.GitlabRepositoriesEnable.ClaGroupID, params.GitlabRepositoriesEnable.RepositoryGitlabIds) - if enableErr != nil { - msg := fmt.Sprintf("problem adding GitLab repositories for projectSFID: %s", params.ProjectSFID) - log.WithFields(f).WithError(enableErr).Warn(msg) - return gitlab_repositories.NewEnableGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, enableErr)) - } - - repoList, getErr := service.GitLabGetRepositoriesByProjectSFID(ctx, params.ProjectSFID) - if getErr != nil { - msg := fmt.Sprintf("problem fetching GitLab repositories for projectSFID: %s", params.ProjectSFID) - log.WithFields(f).WithError(getErr).Warn(msg) - return gitlab_repositories.NewEnableGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, getErr)) + if len(params.GitlabRepositoriesEnroll.Enroll) == 0 && len(params.GitlabRepositoriesEnroll.Unenroll) == 0 { + msg := "missing GitLab ID values for enroll or unenroll - should have at least one value in either list" + return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - return gitlab_repositories.NewEnableGitLabRepositoryOK().WithPayload(repoList) - }) - - api.GitlabRepositoriesUnenrollGitLabRepositoryHandler = gitlab_repositories.UnenrollGitLabRepositoryHandlerFunc( - func(params gitlab_repositories.UnenrollGitLabRepositoryParams, authUser *auth.User) middleware.Responder { - reqID := utils.GetRequestID(params.XREQUESTID) - utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) - ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint - f := logrus.Fields{ - "functionName": "v2.repositories.handlers.GitlabRepositoriesDeleteProjectGitLabRepositoryHandler", - utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "authUser": authUser.UserName, - "authEmail": authUser.Email, - "projectSFID": params.ProjectSFID, - "repositoryExternalID": params.RepositoryExternalID, + if duplicates := utils.FindInt64Duplicates(params.GitlabRepositoriesEnroll.Enroll, params.GitlabRepositoriesEnroll.Unenroll); len(duplicates) > 0 { + msg := fmt.Sprintf("found duplicate entries in both enroll and unenroll: %+v", duplicates) + return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - // Load the project - psc := project_service.GetClient() - projectModel, err := psc.GetProject(params.ProjectSFID) - if err != nil || projectModel == nil { - return gitlab_repositories.NewUnenrollGitLabRepositoryNotFound().WithPayload( - utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) - } - - if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { - msg := fmt.Sprintf("user %s does not have access to Unenroll Gitlab Repositories with Project scope of %s", - authUser.UserName, params.ProjectSFID) - log.WithFields(f).Debug(msg) - return gitlab_repositories.NewUnenrollGitLabRepositoryForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) + if len(params.GitlabRepositoriesEnroll.Enroll) > 0 { + log.WithFields(f).Debugf("enrolling GitLab repository for project: %s and CLA Group: %s", params.ProjectSFID, params.GitlabRepositoriesEnroll.ClaGroupID) + enableErr := service.GitLabEnrollRepositories(ctx, params.GitlabRepositoriesEnroll.ClaGroupID, params.GitlabRepositoriesEnroll.Enroll, true) + if enableErr != nil { + msg := fmt.Sprintf("problem enrolling GitLab repositories for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(enableErr).Warn(msg) + return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, enableErr)) + } } - ghRepo, err := service.GitLabGetRepositoryByExternalID(ctx, params.RepositoryExternalID) - if err != nil { - if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { - msg := fmt.Sprintf("repository not found for repository external ID: %d", params.RepositoryExternalID) - log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewUnenrollGitLabRepositoryNotFound().WithXRequestID(reqID).WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + if len(params.GitlabRepositoriesEnroll.Unenroll) > 0 { + log.WithFields(f).Debugf("unenrolling GitLab repository for project: %s and CLA Group: %s", params.ProjectSFID, params.GitlabRepositoriesEnroll.ClaGroupID) + enableErr := service.GitLabEnrollRepositories(ctx, params.GitlabRepositoriesEnroll.ClaGroupID, params.GitlabRepositoriesEnroll.Unenroll, false) + if enableErr != nil { + msg := fmt.Sprintf("problem unenrolling GitLab repositories for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(enableErr).Warn(msg) + return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, enableErr)) } - - msg := fmt.Sprintf("problem looking up repository using the repostiory external ID: %d", params.RepositoryExternalID) - log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewUnenrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - err = service.GitLabDisableRepository(ctx, "", params.RepositoryExternalID) - if err != nil { - msg := fmt.Sprintf("problem disabling repository for projectSFID: %s, error: %+v", params.ProjectSFID, err) - log.WithFields(f).WithError(err).Warn(msg) - return gitlab_repositories.NewUnenrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + repoList, getErr := service.GitLabGetRepositoriesByProjectSFID(ctx, params.ProjectSFID) + if getErr != nil { + msg := fmt.Sprintf("problem fetching GitLab repositories for projectSFID: %s", params.ProjectSFID) + log.WithFields(f).WithError(getErr).Warn(msg) + return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, getErr)) } - eventService.LogEventWithContext(ctx, &events.LogEventArgs{ - EventType: events.RepositoryDisabled, - ProjectSFID: params.ProjectSFID, - LfUsername: authUser.UserName, - EventData: &events.RepositoryDisabledEventData{ - RepositoryName: ghRepo.RepositoryName, - RepositoryExternalID: ghRepo.RepositoryExternalID, - }, - }) - - return gitlab_repositories.NewUnenrollGitLabRepositoryNoContent().WithXRequestID(reqID) + return gitlab_repositories.NewEnrollGitLabRepositoryOK().WithPayload(repoList) }) } diff --git a/cla-backend-go/v2/repositories/models.go b/cla-backend-go/v2/repositories/models.go new file mode 100644 index 000000000..d25c6a5c0 --- /dev/null +++ b/cla-backend-go/v2/repositories/models.go @@ -0,0 +1,13 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package repositories + +// GitLabAddRepoModel data model for GitLab add repository +type GitLabAddRepoModel struct { + ClaGroupID string + GroupName string + ExternalID int64 + GroupFullPath string + ProjectIDList []int64 +} diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index c5afe0ca5..ccbf1485d 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -31,11 +31,12 @@ type RepositoryInterface interface { GitLabGetRepository(ctx context.Context, repositoryID string) (*repoModels.RepositoryDBModel, error) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*repoModels.RepositoryDBModel, error) + GitLabGetRepositoriesByNamePrefix(ctx context.Context, repositoryNamePrefix string) ([]*repoModels.RepositoryDBModel, error) GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*repoModels.RepositoryDBModel, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *repoModels.RepositoryDBModel) (*repoModels.RepositoryDBModel, error) - GitLabEnableRepositoryByID(ctx context.Context, claGroupID string, repositoryID int64) error - GitLabDisableRepositoryByID(ctx context.Context, claGroupID string, repositoryID int64) error - GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error + GitLabEnrollRepositoryByID(ctx context.Context, claGroupID string, repositoryID int64, enrollValue bool) error + GitLabEnableCLAGroupRepositories(ctx context.Context, claGroupID string, enrollValue bool) error + GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error } // Repository object/struct @@ -121,6 +122,31 @@ func (r *Repository) GitLabGetRepositoryByName(ctx context.Context, repositoryNa return record, nil } +// GitLabGetRepositoriesByNamePrefix returns a list of repositories matching the specified name prefix +func (r *Repository) GitLabGetRepositoriesByNamePrefix(ctx context.Context, repositoryNamePrefix string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) + filter := expression.Name(repoModels.RepositoryNameColumn).BeginsWith(repositoryNamePrefix) + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryTypeIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + RepositoryName: repositoryNamePrefix, + } + } + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabDuplicateRepositoriesFound); ok { + return nil, &utils.GitLabDuplicateRepositoriesFound{ + RepositoryName: repositoryNamePrefix, + } + } + // Some other error + return nil, err + } + + return records, nil +} + // GitLabGetRepositoryByExternalID returns the database model for the specified repository by external ID func (r *Repository) GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*repoModels.RepositoryDBModel, error) { str := strconv.FormatInt(repositoryExternalID, 10) @@ -312,18 +338,13 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string return input, nil } -// GitLabEnableRepositoryByID enables the specified repository -func (r *Repository) GitLabEnableRepositoryByID(ctx context.Context, claGroupID string, repositoryExternalID int64) error { - return r.setRepositoryEnabledValue(ctx, claGroupID, repositoryExternalID, true) -} - -// GitLabDisableRepositoryByID disables the specified repository -func (r *Repository) GitLabDisableRepositoryByID(ctx context.Context, claGroupID string, repositoryExternalID int64) error { - return r.setRepositoryEnabledValue(ctx, claGroupID, repositoryExternalID, false) +// GitLabEnrollRepositoryByID enables the specified repository +func (r *Repository) GitLabEnrollRepositoryByID(ctx context.Context, claGroupID string, repositoryExternalID int64, enrollValue bool) error { + return r.setRepositoryEnabledValue(ctx, claGroupID, repositoryExternalID, enrollValue) } // GitLabEnableCLAGroupRepositories enables the specified CLA Group repositories -func (r *Repository) GitLabEnableCLAGroupRepositories(ctx context.Context, claGroupID string) error { +func (r *Repository) GitLabEnableCLAGroupRepositories(ctx context.Context, claGroupID string, enrollValue bool) error { repositories, err := r.GitHubGetRepositoriesByCLAGroup(ctx, claGroupID) if err != nil { return err @@ -335,7 +356,7 @@ func (r *Repository) GitLabEnableCLAGroupRepositories(ctx context.Context, claGr return parseErr } - enableErr := r.GitLabEnableRepositoryByID(ctx, claGroupID, int64I) + enableErr := r.GitLabEnrollRepositoryByID(ctx, claGroupID, int64I, enrollValue) if enableErr != nil { return enableErr } @@ -344,26 +365,74 @@ func (r *Repository) GitLabEnableCLAGroupRepositories(ctx context.Context, claGr return nil } -// GitLabDisableCLAGroupRepositories disables the GitLab repositories by the specified CLA Group -func (r *Repository) GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error { - repositories, err := r.GitHubGetRepositoriesByCLAGroup(ctx, claGroupID) +// GitLabDeleteRepositories deletes the specified repositories under the GitLap group path +func (r *Repository) GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error { + f := logrus.Fields{ + "functionName": "v2.repositories.repository.GitLabDeleteRepositories", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabGroupPath": gitLabGroupPath, + } + + log.WithFields(f).Debugf("loading repositories with name prefix: %s", gitLabGroupPath) + repositories, err := r.GitLabGetRepositoriesByNamePrefix(ctx, gitLabGroupPath) if err != nil { + // If nothing to delete... + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil + } + log.WithFields(f).WithError(err).Warnf("problem loading repositories with name prefix: %s", gitLabGroupPath) return err } + log.WithFields(f).Debugf("processing repository delete request for %d repositories", len(repositories)) - for _, repo := range repositories { - int64I, parseErr := strconv.ParseInt(repo.RepositoryExternalID, 10, 64) - if parseErr != nil { - return parseErr - } + type GitLabDeleteRepositoryResponse struct { + RepositoryID string + RepositoryName string + RepositoryFullPath string + Error error + } + deleteRepoRespChan := make(chan *GitLabDeleteRepositoryResponse, len(repositories)) - enableErr := r.GitLabDisableRepositoryByID(ctx, claGroupID, int64I) - if enableErr != nil { - return enableErr + for _, repo := range repositories { + go func(repo *repoModels.RepositoryDBModel) { + _, err = r.dynamoDBClient.DeleteItem(&dynamodb.DeleteItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + repoModels.RepositoryIDColumn: {S: aws.String(repo.RepositoryID)}, + }, + TableName: aws.String(r.repositoryTableName), + }) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error deleting repository with ID:%s", repo.RepositoryID) + } + deleteRepoRespChan <- &GitLabDeleteRepositoryResponse{ + RepositoryID: repo.RepositoryID, + RepositoryName: repo.RepositoryName, + RepositoryFullPath: repo.RepositoryFullPath, + Error: err, + } + }(repo) + } + + // Wait for the go routines to finish and load up the results + log.WithFields(f).Debug("waiting for delete repos to finish...") + var lastErr error + for range repositories { + select { + case response := <-deleteRepoRespChan: + if response.Error != nil { + log.WithFields(f).WithError(response.Error).Warn(response.Error.Error()) + lastErr = response.Error + } else { + log.WithFields(f).Debugf("delete repo: %s with ID: %s with full path: %s", response.RepositoryName, response.RepositoryID, response.RepositoryFullPath) + } + case <-ctx.Done(): + log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for delete repositories timed out") + lastErr = fmt.Errorf("delete repositories failed with timeout, error: %v", ctx.Err()) } } - return nil + // Return the last error, hopefully nil if no error occurred... + return lastErr } // getRepositoryWithConditionFilter fetches the repository entry based on the specified condition and filter criteria using the provided index @@ -397,7 +466,6 @@ func (r *Repository) getRepositoryWithConditionFilter(ctx context.Context, condi } if len(results.Items) == 0 { - log.WithFields(f).Debugf("no repositories found matching filter critera: %+v", queryInput) // Generic - no details as we don't know what filter content was provided return nil, &utils.GitLabRepositoryNotFound{} } @@ -493,6 +561,11 @@ func (r *Repository) setRepositoryEnabledValue(ctx context.Context, claGroupID s existingNote = strings.TrimSpace(existingModel.Note) + " " } } + userNameFromCtx := utils.GetUserNameFromContext(ctx) + byUserStr := "" + if userNameFromCtx != "" { + byUserStr = fmt.Sprintf("by user: %s", userNameFromCtx) + } _, now := utils.CurrentTime() updateInput := &dynamodb.UpdateItemInput{ @@ -506,7 +579,7 @@ func (r *Repository) setRepositoryEnabledValue(ctx context.Context, claGroupID s BOOL: aws.Bool(enabledValue), }, ":noteValue": { - S: aws.String(fmt.Sprintf("%s Updated enabled flag to %t on %s.", existingNote, enabledValue, now)), + S: aws.String(fmt.Sprintf("%s Updated enabled flag to %t on %s %s.", existingNote, enabledValue, now, byUserStr)), }, ":dateModifiedValue": { S: aws.String(now), diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index 117ee5277..c303177b7 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -59,12 +59,12 @@ type ServiceInterface interface { GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) - GitLabAddRepositories(ctx context.Context, projectSFID string, input *v2Models.GitlabRepositoriesEnable) (*v2Models.GitlabRepositoriesList, error) + GitLabAddRepositories(ctx context.Context, projectSFID string, input *GitLabAddRepoModel) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitLabOrganization) ([]*v2Models.GitlabRepository, error) - GitLabEnableRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64) error - GitLabEnableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error - GitLabDisableRepository(ctx context.Context, claGroupID string, repositoryExternalID int64) error - GitLabDisableCLAGroupRepositories(ctx context.Context, claGroupID string) error + GitLabEnrollRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64, enrollValue bool) error + GitLabEnrollRepository(ctx context.Context, claGroupID string, repositoryExternalID int64, enrollValue bool) error + GitLabEnrollCLAGroupRepositories(ctx context.Context, claGroupID string, enrollValue bool) error + GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error } // GithubOrgRepo redefine the interface here to avoid circular dependency issues @@ -77,7 +77,7 @@ type GithubOrgRepo interface { GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - DeleteGitLabOrganization(ctx context.Context, projectSFID, gitlabOrgName string) error + DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgName string) error } // Service is the service model/structure From 6f8c4c38d23ff5cdeb7eaef744942465bb21c513 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 24 Aug 2021 20:28:41 -0700 Subject: [PATCH 0458/1276] Added Repository Type Index Lambda Permissions (#3195) --- cla-backend-go/serverless.yml | 1 + cla-backend/serverless.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index cb5c744ee..9808f2d07 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -176,6 +176,7 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 74873fe0b..53adfde2b 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -267,6 +267,7 @@ provider: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" From eb893d4c892cc74f4211e3dc599ba2d333a15861 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 25 Aug 2021 18:28:15 +0300 Subject: [PATCH 0459/1276] Feature/gitlab webhook secret check (#3196) --- cla-backend-go/go.sum | 149 ++++++++++++++++++ cla-backend-go/swagger/cla.v2.yaml | 9 +- cla-backend-go/v2/gitlab-activity/handlers.go | 12 +- cla-backend-go/v2/gitlab-activity/service.go | 10 +- .../v2/gitlab_organizations/service.go | 7 + 5 files changed, 182 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 7a0dc864a..944fa3c75 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -17,28 +17,38 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -46,17 +56,23 @@ github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJ github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -70,32 +86,46 @@ github.com/aws/aws-sdk-go v1.36.27 h1:wc3xLJJHog2SwiqlLnrLUuct/n+dBjB45QhuZw2psV github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -107,13 +137,17 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -122,10 +156,14 @@ github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2 github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -206,38 +244,55 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -267,8 +322,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -289,9 +346,12 @@ github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3 github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -303,8 +363,11 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -313,41 +376,61 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -355,6 +438,7 @@ github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= @@ -364,6 +448,7 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -371,12 +456,14 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -384,25 +471,35 @@ github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2h github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= +github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -417,9 +514,12 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -428,11 +528,17 @@ github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1y github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -445,17 +551,21 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= @@ -467,27 +577,37 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= +github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -514,6 +634,7 @@ github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -535,23 +656,33 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -564,12 +695,15 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -595,6 +729,7 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -613,8 +748,10 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -624,6 +761,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -752,6 +890,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -826,6 +965,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -853,6 +993,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -903,6 +1044,7 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -923,6 +1065,7 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -942,7 +1085,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -965,7 +1110,11 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 8809d7dde..ba88beaac 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3947,8 +3947,7 @@ paths: operationId: gitlabActivity parameters: - $ref: "#/parameters/x-request-id" - - $ref: "#/parameters/x-github-event" - - $ref: "#/parameters/x-hub-signature" + - $ref: "#/parameters/x-gitlab-token" - name: gitlabActivityInput in: body schema: @@ -4329,6 +4328,12 @@ parameters: description: Github event signature which is used for validation of the request body in: header type: string + x-gitlab-token: + name: X-Gitlab-Token + description: Gitlab webhook secret token sent for futher verification + in: header + type: string + required: true definitions: # Common definitions diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index d2dd835ed..e65d3be08 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -5,6 +5,7 @@ package gitlab_activity import ( "context" + "errors" "fmt" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -30,6 +31,11 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. log.WithFields(f).Debugf("handling gitlab activity callback") ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) + if params.XGitlabToken == ""{ + return gitlab_activity.NewGitlabActivityUnauthorized().WithPayload( + utils.ErrorResponseUnauthorized(reqID, "missing webhook secret token")) + } + jsonData, err := params.GitlabActivityInput.MarshalJSON() if err != nil { msg := fmt.Sprintf("unmarshall event data failed : %v", err) @@ -61,10 +67,14 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. utils.ErrorResponseBadRequest(reqID, msg)) } - err = service.ProcessMergeOpenedActivity(ctx, mergeEvent) + err = service.ProcessMergeOpenedActivity(ctx, params.XGitlabToken, mergeEvent) if err != nil { msg := fmt.Sprintf("processing gitlab merge event failed : %v", err) log.WithFields(f).Errorf(msg) + if errors.Is(err, secretTokenMismatch){ + return gitlab_activity.NewGitlabActivityUnauthorized().WithPayload( + utils.ErrorResponseUnauthorized(reqID, msg)) + } return gitlab_activity.NewGitlabActivityInternalServerError().WithPayload( utils.ErrorResponseBadRequest(reqID, msg)) } diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 103300a65..ed0c9e26d 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -38,6 +38,7 @@ var ( missingID = errors.New("user missing in easyCLA records") missingCompanyAffiliation = errors.New("must confirm affiliation with their company") missingCompanyApproval = errors.New("missing in company approval lists") + secretTokenMismatch = errors.New("secret token mismatch") ) type gatedGitlabUser struct { @@ -46,7 +47,7 @@ type gatedGitlabUser struct { } type Service interface { - ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *gitlab.MergeEvent) error + ProcessMergeOpenedActivity(ctx context.Context, secretToken string, mergeEvent *gitlab.MergeEvent) error } type service struct { @@ -76,7 +77,7 @@ func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRe } } -func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *gitlab.MergeEvent) error { +func (s service) ProcessMergeOpenedActivity(ctx context.Context, secretToken string, mergeEvent *gitlab.MergeEvent) error { projectName := mergeEvent.Project.Name projectID := mergeEvent.Project.ID mergeID := mergeEvent.ObjectAttributes.IID @@ -98,6 +99,11 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, mergeEvent *git return fmt.Errorf("fetching internal gitlab org for following path : %s failed : %v", repositoryPath, err) } + log.WithFields(f).Debugf("checking gitlab org : %s auth state agains the webhook secret token", gitlabOrg.OrganizationName) + if gitlabOrg.AuthState != secretToken { + return secretTokenMismatch + } + log.WithFields(f).Debugf("internal gitlab org : %s:%s is associated with external path : %s", gitlabOrg.OrganizationID, gitlabOrg.OrganizationName, repositoryPath) gitlabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index d4c81331d..75836c960 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -201,6 +201,9 @@ func (s *Service) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrg return nil, err } + if dbModel == nil { + return nil, nil + } return common.ToModel(dbModel), nil } @@ -218,6 +221,10 @@ func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGrou return nil, err } + if dbModel == nil { + return nil, nil + } + return common.ToModel(dbModel), nil } From 49b3afff34c9fa818dc1f0247ce24f0e9c92acdb Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 25 Aug 2021 08:43:18 -0700 Subject: [PATCH 0460/1276] Feature/Gitlab Docusign Flow (#3197) Co-authored-by: Harold Wanyama --- cla-backend-go/users/repository.go | 6 + cla-backend-go/utils/conversion.go | 10 + cla-backend/cla/config.py | 1 + cla-backend/cla/controllers/signing.py | 13 ++ cla-backend/cla/models/docusign_models.py | 217 ++++++++++++++++++++++ cla-backend/cla/routes.py | 19 +- 6 files changed, 265 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/users/repository.go b/cla-backend-go/users/repository.go index 484878d02..f9b120fa9 100644 --- a/cla-backend-go/users/repository.go +++ b/cla-backend-go/users/repository.go @@ -123,6 +123,12 @@ func (repo repository) CreateUser(user *models.User) (*models.User, error) { } } + if len(user.Emails) > 0 { + attributes["user_emails"] = &dynamodb.AttributeValue{ + SS: utils.ArrayStringPointer(user.Emails), + } + } + if user.LfUsername != "" { attributes["lf_username"] = &dynamodb.AttributeValue{ S: aws.String(user.LfUsername), diff --git a/cla-backend-go/utils/conversion.go b/cla-backend-go/utils/conversion.go index 973251cc1..f98ab0c7e 100644 --- a/cla-backend-go/utils/conversion.go +++ b/cla-backend-go/utils/conversion.go @@ -50,3 +50,13 @@ func GetNilSliceIfEmpty(slice []string) []string { return slice } + +// ArrayStringPointer converts Array string to Array string pointer +func ArrayStringPointer(input []string) []*string { + var conversion []*string + for _, v := range input { + v2 := v + conversion = append(conversion, &v2) + } + return conversion +} diff --git a/cla-backend/cla/config.py b/cla-backend/cla/config.py index 0a58daeba..8577b98fe 100644 --- a/cla-backend/cla/config.py +++ b/cla-backend/cla/config.py @@ -130,6 +130,7 @@ def get_ssm_key(region, key): # PDF Generation. PDF_SERVICE = 'DocRaptor' + AUTH0_PLATFORM_URL = os.getenv("AUTH0_PLATFORM_URL", "") AUTH0_PLATFORM_CLIENT_ID = os.getenv("AUTH0_PLATFORM_CLIENT_ID", "") AUTH0_PLATFORM_CLIENT_SECRET = os.getenv("AUTH0_PLATFORM_CLIENT_SECRET", "") diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index 1b761a184..701e3d82c 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -42,6 +42,8 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur primary_user_email = github.get_primary_user_email(request) return signing_service.request_individual_signature(str(project_id), str(user_id), return_url, preferred_email=primary_user_email) + elif return_url_type is not None and return_url_type.lower() == "gitlab": + return signing_service.request_individual_signature_gitlab(str(project_id), str(user_id), return_url) def request_corporate_signature(auth_user, @@ -179,6 +181,17 @@ def post_individual_signed(content, installation_id, github_repository_id, chang """ get_signing_service().signed_individual_callback(content, installation_id, github_repository_id, change_request_id) +def post_individual_signed_gitlab(content, user_id): + """ + Handle the posted callback from the signing service after ICLA signature. + + :param content: The POST body from the signing service callback. + :type content: string + :param user_id: The ID of the user that signed. + :type user_id: string + """ + get_signing_service().signed_individual_callback_gitlab(content,user_id) + def post_individual_signed_gerrit(content, user_id): """ diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 83cc5aed9..027488f8b 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -125,6 +125,145 @@ def initialize(self, config): integrator_key=integrator_key) self.s3storage = S3Storage() self.s3storage.initialize(None) + + def request_individual_signature_gitlab(self, project_id, user_id, return_url=None, callback_url=None): + request_info = 'project: {project_id}, user: {user_id} with return_url: {return_url}'.format( + project_id=project_id, user_id=user_id, return_url=return_url) + cla.log.debug('Individual Signature - creating new signature for: {}'.format(request_info)) + + # Ensure this is a valid user + user_id = str(user_id) + try: + user = User() + user.load(user_id) + cla.log.debug('Individual Signature - loaded user name: {}, ' + 'user email: {}, gh user: {}, gh id: {}'. + format(user.get_user_name(), user.get_user_email(), user.get_github_username(), + user.get_user_github_id())) + except DoesNotExist as err: + cla.log.warning('Individual Signature - user ID was NOT found for: {}'.format(request_info)) + return {'errors': {'user_id': str(err)}} + + # Ensure the project exists + try: + project = Project() + project.load(project_id) + cla.log.debug('Individual Signature - loaded project id: {}, name: {}, '. + format(project.get_project_id(), project.get_project_name())) + except DoesNotExist as err: + cla.log.warning('Individual Signature - project ID NOT found for: {}'.format(request_info)) + return {'errors': {'project_id': str(err)}} + + # Check for active signature object with this project. If the user has + # signed the most recent major version, they do not need to sign again. + cla.log.debug('Individual Signature - loading latest user signature for user: {}, project: {}'. + format(user, project)) + latest_signature = user.get_latest_signature(str(project_id)) + cla.log.debug('Individual Signature - loaded latest user signature for user: {}, project: {}'. + format(user, project)) + + cla.log.debug('Individual Signature - loading latest individual document for project: {}'. + format(project)) + last_document = project.get_latest_individual_document() + cla.log.debug('Individual Signature - loaded latest individual document for project: {}'. + format(project)) + + cla.log.debug('Individual Signature - creating default individual values for user: {}'.format(user)) + default_cla_values = create_default_individual_values(user) + cla.log.debug('Individual Signature - created default individual values: {}'.format(default_cla_values)) + + # Generate signature callback url + cla.log.debug('Individual Signature - get active signature metadata') + signature_metadata = cla.utils.get_active_signature_metadata(user_id) + cla.log.debug('Individual Signature - get active signature metadata: {}'.format(signature_metadata)) + + cla.log.debug('Individual Signature - get individual signature callback url') + callback_url = self._generate_individual_signature_callback_url_gitlab(user_id) + cla.log.debug('Individual Signature - get individual signature callback url: {}'.format(callback_url)) + + if latest_signature is not None and \ + last_document.get_document_major_version() == latest_signature.get_signature_document_major_version(): + cla.log.debug('Individual Signature - user already has a signatures with this project: {}'. + format(latest_signature.get_signature_id())) + + # Re-generate and set the signing url - this will update the signature record + self.populate_sign_url(latest_signature, callback_url, default_values=default_cla_values, + preferred_email=user.get_user_email()) + + return {'user_id': user_id, + 'project_id': project_id, + 'signature_id': latest_signature.get_signature_id(), + 'sign_url': latest_signature.get_signature_sign_url()} + else: + cla.log.debug('Individual Signature - user does NOT have a signatures with this project: {}'. + format(project)) + + # Get signature return URL + if return_url is None: + return_url = cla.utils.get_active_signature_return_url(user_id, signature_metadata) + cla.log.debug('Individual Signature - setting signature return_url to {}'.format(return_url)) + + if return_url is None: + cla.log.warning('No active signature found for user - cannot generate ' + 'return_url without knowing where the user came from') + return {'user_id': str(user_id), + 'project_id': str(project_id), + 'signature_id': None, + 'sign_url': None, + 'error': 'No active signature found for user - cannot generate return_url without knowing where the user came from'} + + # Get latest document + try: + cla.log.debug('Individual Signature - loading project latest individual document...') + document = project.get_latest_individual_document() + cla.log.debug('Individual Signature - loaded project latest individual document: {}'.format(document)) + except DoesNotExist as err: + cla.log.warning('Individual Signature - project individual document does NOT exist for: {}'. + format(request_info)) + return {'errors': {'project_id': project_id, 'message': str(err)}} + + # If the CCLA/ICLA template is missing (not created in the project console), we won't have a document + # return an error + if not document: + return {'errors': {'project_id': project_id, 'message': 'missing template document'}} + + # Create new Signature object + cla.log.debug('Individual Signature - creating new signature document ' + 'project_id: {}, user_id: {}, return_url: {}, callback_url: {}'. + format(project_id, user_id, return_url, callback_url)) + signature = Signature(signature_id=str(uuid.uuid4()), + signature_project_id=project_id, + signature_document_major_version=document.get_document_major_version(), + signature_document_minor_version=document.get_document_minor_version(), + signature_reference_id=user_id, + signature_reference_type='user', + signature_reference_name=user.get_user_name(), + signature_type='cla', + signature_return_url_type='Github', + signature_signed=False, + signature_approved=True, + signature_return_url=return_url, + signature_callback_url=callback_url) + + # Set signature ACL + cla.log.debug('Individual Signature - setting ACL using user GH id: {}'.format(user.get_user_github_id())) + signature.set_signature_acl('github:{}'.format(user.get_user_github_id())) + + # Populate sign url + self.populate_sign_url(signature, callback_url, default_values=default_cla_values, + preferred_email=user.get_user_email()) + + # Save signature + signature.save() + cla.log.debug('Individual Signature - Saved signature for: {}'.format(request_info)) + + response = {'user_id': str(user_id), + 'project_id': project_id, + 'signature_id': signature.get_signature_id(), + 'sign_url': signature.get_signature_sign_url()} + + cla.log.debug('Individual Signature - returning response: {}'.format(response)) + return response def request_individual_signature(self, project_id, user_id, return_url=None, callback_url=None, preferred_email=None): @@ -846,6 +985,13 @@ def _generate_individual_signature_callback_url_gerrit(self, user_id): """ return os.path.join(api_base_url, 'v2/signed/gerrit/individual', str(user_id)) + + def _generate_individual_signature_callback_url_gitlab(self, user_id): + """ + Helper function to get a user's active signature callback URL for GitLab + + """ + return os.path.join(api_base_url, 'v2/signed/gitlab/individual', str(user_id)) def _get_corporate_signature_callback_url(self, project_id, company_id): """ @@ -1407,6 +1553,7 @@ def populate_sign_url(self, signature, callback_url=None, 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') + def signed_individual_callback(self, content, installation_id, github_repository_id, change_request_id): """ @@ -1583,6 +1730,76 @@ def signed_individual_callback_gerrit(self, content, user_id): project_id = signature.get_signature_project_id() self.send_to_s3(document_data, project_id, signature_id, 'icla', user_id) cla.log.debug(f'{fn} - uploaded ICLA document to s3') + + def signed_individual_callback_gitlab(self, content, user_id): + fn = 'models.docusign_models.signed_individual_callback_gitlab' + cla.log.debug(f'{fn} - Docusign GitLab ICLA signed callback POST data: {content}') + tree = ET.fromstring(content) + # Get envelope ID. + envelope_id = tree.find('.//' + self.TAGS['envelope_id']).text + # Assume only one signature per signature. + signature_id = tree.find('.//' + self.TAGS['client_user_id']).text + signature = cla.utils.get_signature_instance() + try: + signature.load(signature_id) + except DoesNotExist: + cla.log.error(f'{fn} - DocuSign GitLab ICLA callback returned signed info ' + f'on invalid signature: {content}') + return + # Iterate through recipients and update the signature signature status if changed. + elem = tree.find('.//' + self.TAGS['recipient_statuses'] + + '/' + self.TAGS['recipient_status']) + status = elem.find(self.TAGS['status']).text + if status == 'Completed' and not signature.get_signature_signed(): + cla.log.info(f'{fn} - ICLA signature signed ({signature_id}) - notifying repository service provider') + # Get User + user = cla.utils.get_user_instance() + user.load(user_id) + + cla.log.debug(f'{fn} - updating signature in database - setting signed=true...') + signature.set_signature_signed(True) + populate_signature_from_icla_callback(content, tree, signature) + signature.save() + + # Load the Project by ID and send audit event + project = Project() + try: + project.load(signature.get_signature_project_id()) + event_data = (f'The user {user.get_user_name()} signed an individual CLA for ' + f'project {project.get_project_name()}.') + event_summary = (f'The user {user.get_user_name()} signed an individual CLA for ' + f'project {project.get_project_name()} with project ID: {project.get_project_id()}.') + Event.create_event( + event_type=EventType.IndividualSignatureSigned, + event_cla_group_id=signature.get_signature_project_id(), + event_company_id=None, + event_user_id=user.get_user_id(), + event_user_name=user.get_user_name(), + event_data=event_data, + event_summary=event_summary, + contains_pii=False, + ) + except DoesNotExist as err: + msg = (f'{fn} - unable to load project by CLA Group ID: {signature.get_signature_project_id()}, ' + f'unable to send audit event, error: {err}') + cla.log.warning(msg) + return + + + # Get signed document + document_data = self.get_signed_document(envelope_id, user) + # Send email with signed document. + self.send_signed_document(signature, document_data, user, icla=True) + + # Verify user id exist for saving on storage + if user_id is None: + cla.log.warning(f'{fn} - missing user_id on ICLA for saving signed file on s3 storage') + raise SigningError('Missing user_id on ICLA for saving signed file on s3 storage.') + + # Store document on S3 + project_id = signature.get_signature_project_id() + self.send_to_s3(document_data, project_id, signature_id, 'icla', user_id) + cla.log.debug(f'{fn} - uploaded ICLA document to s3') def signed_corporate_callback(self, content, project_id, company_id): """ diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 5c17879f2..735d7a056 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -1198,7 +1198,7 @@ def request_individual_signature( DATA: {'project_id': 'some-project-id', 'user_id': 'some-user-id', - 'return_url_type': Gerrit/Github. Optional depending on presence of return_url + 'return_url_type': Gerrit/Github/GitLab. Optional depending on presence of return_url 'return_url': } Creates a new signature given project and user IDs. The user will be redirected to the @@ -1348,6 +1348,23 @@ def post_individual_signed( content, installation_id, github_repository_id, change_request_id ) +@hug.post( + "/signed/gitlab/individual/{user_id}", versions=2, +) +def post_individual_signed_gitlab( + body, + user_id: hug.types.uuid +): + """ + POST: /signed/gerritindividual/{user_id} + + Callback URL from signing service upon ICLA signature for a Gitlab user. + """ + content = body.read() + return cla.controllers.signing.post_individual_signed_gitlab( + content, user_id + ) + @hug.post("/signed/gerrit/individual/{user_id}", versions=2) def post_individual_signed_gerrit(body, user_id: hug.types.uuid): From e72c738f41826540161eaf9bf3b035389b241fd6 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Wed, 25 Aug 2021 19:12:35 +0300 Subject: [PATCH 0461/1276] gitlab trigger endpoint (#3198) --- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/swagger/cla.v2.yaml | 44 +++++++++ cla-backend-go/v2/gitlab-activity/handlers.go | 96 ++++++++++++++++++- cla-backend-go/v2/gitlab-activity/service.go | 48 ++++++++-- .../v2/gitlab_organizations/service.go | 2 +- 5 files changed, 181 insertions(+), 11 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index e1f88712e..a32ceac31 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -351,7 +351,7 @@ func server(localMode bool) http.Handler { v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) gitlab_sign.Configure(v2API, gitlabSignService, eventsService, configFile.CLAContributorv2Base) - gitlab_activity.Configure(v2API, gitlabActivityService, eventsService) + gitlab_activity.Configure(v2API, gitlabActivityService, gitlabOrganizationRepo, eventsService, gitlabApp) v1Repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index ba88beaac..3ec200636 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3966,6 +3966,33 @@ paths: tags: - gitlab-activity + /gitlab/trigger: + post: + summary: Gitlab Activity MR Trigger + description: Endpoint is used to trigger specific MR for gitlab + security: [ ] + operationId: gitlabTrigger + parameters: + - $ref: "#/parameters/x-request-id" + - name: gitlabTriggerInput + in: body + schema: + $ref: '#/definitions/gitlab-trigger-input' + responses: + '200': + description: 'Success' + '400': + $ref: '#/responses/invalid-request' + '401': + $ref: '#/responses/unauthorized' + '403': + $ref: '#/responses/forbidden' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-activity + + /repository-provider/gitlab/sign/{organizationID}/{gitlabRepositoryID}/{mergeRequestID}: get: summary: Gitlab sign request handler @@ -4372,6 +4399,23 @@ definitions: type: string additionalProperties: true + gitlab-trigger-input: + type: object + required: + - gitlab_organization_id + - gitlab_external_repository_id + - gitlab_mr_id + properties: + gitlab_organization_id: + type: string + description: the gitlab organization id to which mr belongs to + gitlab_external_repository_id: + type: integer + description: gitlab project identifier associated with mr + gitlab_mr_id: + type: integer + description: gitlab mr id + github-repository-input: type: object required: diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index e65d3be08..fb83dca16 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -7,6 +7,8 @@ import ( "context" "errors" "fmt" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" @@ -19,7 +21,95 @@ import ( gitlabsdk "github.com/xanzy/go-gitlab" ) -func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service) { +func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab_organizations.RepositoryInterface, eventService events.Service, gitLabApp *gitlab_api.App) { + + api.GitlabActivityGitlabTriggerHandler = gitlab_activity.GitlabTriggerHandlerFunc(func(params gitlab_activity.GitlabTriggerParams) middleware.Responder { + requestID, _ := uuid.NewV4() + reqID := requestID.String() + + if params.GitlabTriggerInput == nil || params.GitlabTriggerInput.GitlabOrganizationID == nil || params.GitlabTriggerInput.GitlabExternalRepositoryID == nil || params.GitlabTriggerInput.GitlabMrID == nil{ + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, "missing parameter")) + } + + gitlabOrganizationID := *params.GitlabTriggerInput.GitlabOrganizationID + gitlabExternalRepositoryID := *params.GitlabTriggerInput.GitlabExternalRepositoryID + gitlabMrID := *params.GitlabTriggerInput.GitlabMrID + + f := logrus.Fields{ + "functionName": "gitlab_activity.handlers.GitlabActivityGitlabTriggerHandler", + "requestID": reqID, + "gitlabOrganizationID":gitlabOrganizationID, + "gitlabExternalRepositoryID":gitlabExternalRepositoryID, + "gitlabMrID":gitlabMrID, + } + + log.WithFields(f).Debugf("handling gitlab trigger") + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) + + gitlabOrg, err := gitlabOrgRepo.GetGitLabOrganization(ctx, gitlabOrganizationID) + if err != nil { + msg := fmt.Sprintf("fetching gitlab org failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + if gitlabOrg == nil { + msg := fmt.Sprintf("fetching gitlab org failed no results returned") + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + gitlabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, gitLabApp) + if err != nil { + msg := fmt.Sprintf("initializing gitlab client : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + log.WithFields(f).Debugf("fetching gitlab repository via external id") + gitlabProject, err := gitlab_api.GetProjectByID(ctx, gitlabClient, int(gitlabExternalRepositoryID)) + if err != nil { + msg := fmt.Sprintf("fetching gitlab project failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + gitlabMr, err := gitlab_api.FetchMrInfo(gitlabClient, int(gitlabExternalRepositoryID), int(gitlabMrID)) + if err != nil { + msg := fmt.Sprintf("fetching gitlab mr failed : %v", err) + log.WithFields(f).Errorf(msg) + return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + err = service.ProcessMergeActivity(ctx, gitlabOrg.AuthState, &ProcessMergeActivityInput{ + ProjectName: gitlabProject.Name, + ProjectPath: gitlabProject.PathWithNamespace, + ProjectNamespace: gitlabProject.Namespace.Name, + ProjectID: gitlabProject.ID, + MergeID: int(gitlabMrID), + RepositoryPath: gitlabProject.PathWithNamespace, + LastCommitSha: gitlabMr.SHA, + }) + if err != nil { + msg := fmt.Sprintf("processing gitlab merge event failed : %v", err) + log.WithFields(f).Errorf(msg) + if errors.Is(err, secretTokenMismatch) { + return gitlab_activity.NewGitlabActivityUnauthorized().WithPayload( + utils.ErrorResponseUnauthorized(reqID, msg)) + } + return gitlab_activity.NewGitlabActivityInternalServerError().WithPayload( + utils.ErrorResponseBadRequest(reqID, msg)) + } + + return gitlab_activity.NewGitlabActivityOK() + + }) api.GitlabActivityGitlabActivityHandler = gitlab_activity.GitlabActivityHandlerFunc(func(params gitlab_activity.GitlabActivityParams) middleware.Responder { requestID, _ := uuid.NewV4() @@ -31,7 +121,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. log.WithFields(f).Debugf("handling gitlab activity callback") ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) - if params.XGitlabToken == ""{ + if params.XGitlabToken == "" { return gitlab_activity.NewGitlabActivityUnauthorized().WithPayload( utils.ErrorResponseUnauthorized(reqID, "missing webhook secret token")) } @@ -71,7 +161,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. if err != nil { msg := fmt.Sprintf("processing gitlab merge event failed : %v", err) log.WithFields(f).Errorf(msg) - if errors.Is(err, secretTokenMismatch){ + if errors.Is(err, secretTokenMismatch) { return gitlab_activity.NewGitlabActivityUnauthorized().WithPayload( utils.ErrorResponseUnauthorized(reqID, msg)) } diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index ed0c9e26d..09b6a01f2 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -41,6 +41,17 @@ var ( secretTokenMismatch = errors.New("secret token mismatch") ) +// ProcessMergeActivityInput is used to pass the data needed to trigger a gitlab mr check +type ProcessMergeActivityInput struct { + ProjectName string + ProjectPath string + ProjectNamespace string + ProjectID int + MergeID int + RepositoryPath string + LastCommitSha string +} + type gatedGitlabUser struct { *gitlab.User err error @@ -48,6 +59,7 @@ type gatedGitlabUser struct { type Service interface { ProcessMergeOpenedActivity(ctx context.Context, secretToken string, mergeEvent *gitlab.MergeEvent) error + ProcessMergeActivity(ctx context.Context, secretToken string, input *ProcessMergeActivityInput) error } type service struct { @@ -79,13 +91,38 @@ func NewService(gitlabRepository gitlab_organizations.RepositoryInterface, gitRe func (s service) ProcessMergeOpenedActivity(ctx context.Context, secretToken string, mergeEvent *gitlab.MergeEvent) error { projectName := mergeEvent.Project.Name + projectPath := mergeEvent.Project.PathWithNamespace + projectNamespace := mergeEvent.Project.Namespace projectID := mergeEvent.Project.ID mergeID := mergeEvent.ObjectAttributes.IID repositoryPath := mergeEvent.Project.PathWithNamespace lastCommitSha := mergeEvent.ObjectAttributes.LastCommit.ID + input := &ProcessMergeActivityInput{ + ProjectName: projectName, + ProjectPath: projectPath, + ProjectNamespace: projectNamespace, + ProjectID: projectID, + MergeID: mergeID, + RepositoryPath: repositoryPath, + LastCommitSha: lastCommitSha, + } + + return s.ProcessMergeActivity(ctx, secretToken, input) + +} + +func (s *service) ProcessMergeActivity(ctx context.Context, secretToken string, input *ProcessMergeActivityInput) error { + projectName := input.ProjectName + projectPath := input.ProjectPath + projectNamespace := input.ProjectNamespace + projectID := input.ProjectID + mergeID := input.MergeID + repositoryPath := input.RepositoryPath + lastCommitSha := input.LastCommitSha + f := logrus.Fields{ - "functionName": "ProcessMergeOpenedActivity", + "functionName": "ProcessMergeActivity", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "gitlabProjectName": projectName, "gitlabProjectID": projectID, @@ -94,7 +131,7 @@ func (s service) ProcessMergeOpenedActivity(ctx context.Context, secretToken str } log.WithFields(f).Debugf("looking up for gitlab org in easycla records ...") - gitlabOrg, err := s.getGitlabOrganizationFromMergeEvent(ctx, mergeEvent) + gitlabOrg, err := s.getGitlabOrganizationFromProjectPath(ctx, projectPath, projectNamespace) if err != nil { return fmt.Errorf("fetching internal gitlab org for following path : %s failed : %v", repositoryPath, err) } @@ -279,15 +316,14 @@ func getAuthorInfo(gitlabUser *gitlab.User) string { return fmt.Sprintf("%d:%s", gitlabUser.ID, gitlabUser.Username) } -func (s service) getGitlabOrganizationFromMergeEvent(ctx context.Context, mergeEvent *gitlab.MergeEvent) (*common.GitLabOrganization, error) { - repositoryPath := mergeEvent.Project.PathWithNamespace - parts := strings.Split(repositoryPath, "/") +func (s service) getGitlabOrganizationFromProjectPath(ctx context.Context, projectPath, projectNameSpace string) (*common.GitLabOrganization, error) { + parts := strings.Split(projectPath, "/") organizationName := parts[0] gitlabOrg, err := s.gitlabRepository.GetGitLabOrganizationByName(ctx, organizationName) if err != nil || gitlabOrg == nil { // try getting it with project name as well - gitlabOrg, err = s.gitlabRepository.GetGitLabOrganizationByName(ctx, mergeEvent.Project.Namespace) + gitlabOrg, err = s.gitlabRepository.GetGitLabOrganizationByName(ctx, projectNameSpace) if err != nil || gitlabOrg == nil { return nil, fmt.Errorf("gitlab org : %s doesn't exist : %v", organizationName, err) } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 75836c960..0b81d0ace 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -224,7 +224,7 @@ func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGrou if dbModel == nil { return nil, nil } - + return common.ToModel(dbModel), nil } From 2d66f2d4b347b55aa86f195825889ed4cdcb393a Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 25 Aug 2021 11:28:58 -0700 Subject: [PATCH 0462/1276] Added GitLab Onboarding Filter (#3199) --- cla-backend-go/v2/repositories/gitlab_services.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 83d69b212..90f2222b0 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -9,6 +9,7 @@ import ( "fmt" "sort" "strconv" + "strings" "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -191,8 +192,14 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel // Build a list of project IDs for _, proj := range projectList { - log.WithFields(f).Debugf("id: %d, repo: %s, path: %s, full path: %s, weburl: %s", proj.ID, proj.Name, proj.Path, proj.PathWithNamespace, proj.WebURL) - listProjectIDs = append(listProjectIDs, int64(proj.ID)) + // Ensure we only add GitLab projects/repositories that are in the tree path of the GitLab group/organization - we don't want to reach over into a different tree if the user has access + // The GetGroupProjectListByGroupID call in some cases returns more than expected (need to investigate why) + if strings.HasPrefix(proj.PathWithNamespace, gitLabOrgModel.OrganizationFullPath) { + log.WithFields(f).Debugf("adding - id: %d, repo: %s, path: %s, full path: %s, weburl: %s", proj.ID, proj.Name, proj.Path, proj.PathWithNamespace, proj.WebURL) + listProjectIDs = append(listProjectIDs, int64(proj.ID)) + } else { + log.WithFields(f).Debugf("skipping - id: %d, repo: %s, path: %s, full path: %s, weburl: %s", proj.ID, proj.Name, proj.Path, proj.PathWithNamespace, proj.WebURL) + } } // Build input to the add function From ef1cf883122e5234c160d619c89813219d8c966a Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 25 Aug 2021 12:57:15 -0700 Subject: [PATCH 0463/1276] Added GitLab Repo Query Filter by GitLab Group Prefix (#3200) - Added filter so that GitLab repos are filter based on the GitLab group name as a prefix. This handles the case linuxfoundation/product/asitha vs linuxfoundation/product/asitha-cla matching together. Signed-off-by: David Deal --- .../swagger/common/gitlab-organization.yaml | 4 ++++ cla-backend-go/v2/gitlab-activity/handlers.go | 13 +++++----- .../v2/gitlab_organizations/service.go | 5 ++-- .../v2/repositories/gitlab_services.go | 19 ++++++++++++++- cla-backend-go/v2/repositories/repository.go | 24 ++++++++++++++++++- cla-backend-go/v2/repositories/service.go | 1 + 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/cla-backend-go/swagger/common/gitlab-organization.yaml b/cla-backend-go/swagger/common/gitlab-organization.yaml index 1f577200f..113b4f23b 100644 --- a/cla-backend-go/swagger/common/gitlab-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-organization.yaml @@ -56,6 +56,10 @@ properties: auto_enabled_cla_group_id: type: string description: Specifies which Cla group ID to be used when autoEnabled flag in enabled for the Github Organization. If autoEnabled is on this field needs to be set as well. + branch_protection_enabled: + type: boolean + description: Flag to indicate if this GitHub Organization is configured to automatically setup branch protection on CLA enabled repositories. + x-omitempty: false auth_info: type: string description: auth info diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index fb83dca16..ee08cec23 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" @@ -27,7 +28,7 @@ func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab requestID, _ := uuid.NewV4() reqID := requestID.String() - if params.GitlabTriggerInput == nil || params.GitlabTriggerInput.GitlabOrganizationID == nil || params.GitlabTriggerInput.GitlabExternalRepositoryID == nil || params.GitlabTriggerInput.GitlabMrID == nil{ + if params.GitlabTriggerInput == nil || params.GitlabTriggerInput.GitlabOrganizationID == nil || params.GitlabTriggerInput.GitlabExternalRepositoryID == nil || params.GitlabTriggerInput.GitlabMrID == nil { return gitlab_activity.NewGitlabActivityBadRequest().WithPayload( utils.ErrorResponseBadRequest(reqID, "missing parameter")) } @@ -37,11 +38,11 @@ func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab gitlabMrID := *params.GitlabTriggerInput.GitlabMrID f := logrus.Fields{ - "functionName": "gitlab_activity.handlers.GitlabActivityGitlabTriggerHandler", - "requestID": reqID, - "gitlabOrganizationID":gitlabOrganizationID, - "gitlabExternalRepositoryID":gitlabExternalRepositoryID, - "gitlabMrID":gitlabMrID, + "functionName": "gitlab_activity.handlers.GitlabActivityGitlabTriggerHandler", + "requestID": reqID, + "gitlabOrganizationID": gitlabOrganizationID, + "gitlabExternalRepositoryID": gitlabExternalRepositoryID, + "gitlabMrID": gitlabMrID, } log.WithFields(f).Debugf("handling gitlab trigger") diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 0b81d0ace..d7b8ed1db 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -288,7 +288,8 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string continue } - repoList, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByProjectSFID(ctx, projectSFID) + log.WithFields(f).Debugf("filtering repositories based on group path: %s", org.OrganizationFullPath) + repoList, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByNamePrefix(ctx, fmt.Sprintf("%s/", org.OrganizationFullPath)) if repoErr != nil { if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { log.WithFields(f).WithError(repoErr).Debugf("no GitLab repositories onboarded for project : %s", projectSFID) @@ -306,7 +307,7 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string OrganizationFullPath: org.OrganizationFullPath, OrganizationExternalID: org.OrganizationExternalID, InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), - BranchProtectionEnabled: false, // TODO review this - why not modeled? + BranchProtectionEnabled: org.BranchProtectionEnabled, ConnectionStatus: "", // updated below Repositories: []*models.GitlabProjectRepository{}, // updated below } diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 90f2222b0..ccd37c90c 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -305,7 +305,24 @@ func (s *Service) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupI // GitLabGetRepositoriesByOrganizationName returns the list of repositories associated with the Organization/Group name func (s *Service) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) { - dbModels, err := s.gitV2Repository.GitHubGetRepositoriesByOrganizationName(ctx, orgName) + dbModels, err := s.gitV2Repository.GitLabGetRepositoriesByOrganizationName(ctx, orgName) + if err != nil { + return nil, err + } + + responses, err := dbModelsToGitLabRepositories(dbModels) + if err != nil { + return nil, err + } + + return &v2Models.GitlabRepositoriesList{ + List: responses, + }, nil +} + +// GitLabGetRepositoriesByNamePrefix returns a list of repositories that match the name prefix +func (s *Service) GitLabGetRepositoriesByNamePrefix(ctx context.Context, repositoryNamePrefix string) (*v2Models.GitlabRepositoriesList, error) { + dbModels, err := s.gitV2Repository.GitLabGetRepositoriesByNamePrefix(ctx, repositoryNamePrefix) if err != nil { return nil, err } diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index ccbf1485d..4bc6f3848 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -31,6 +31,7 @@ type RepositoryInterface interface { GitLabGetRepository(ctx context.Context, repositoryID string) (*repoModels.RepositoryDBModel, error) GitLabGetRepositoryByName(ctx context.Context, repositoryName string) (*repoModels.RepositoryDBModel, error) + GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) GitLabGetRepositoriesByNamePrefix(ctx context.Context, repositoryNamePrefix string) ([]*repoModels.RepositoryDBModel, error) GitLabGetRepositoryByExternalID(ctx context.Context, repositoryExternalID int64) (*repoModels.RepositoryDBModel, error) GitLabAddRepository(ctx context.Context, projectSFID string, input *repoModels.RepositoryDBModel) (*repoModels.RepositoryDBModel, error) @@ -256,8 +257,29 @@ func (r *Repository) GitHubGetRepositoriesByProjectSFID(ctx context.Context, pro return records, nil } -// GitHubGetRepositoriesByOrganizationName returns a list of repositories associated with the specified organization name +// GitHubGetRepositoriesByOrganizationName returns a list of GitHub repositories associated with the specified organization name func (r *Repository) GitHubGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) { + condition := expression.Key(repoModels.RepositoryOrganizationNameColumn).Equal(expression.Value(orgName)) + filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitHubType)) + + records, err := r.getRepositoriesWithConditionFilter(ctx, condition, filter, repoModels.RepositoryOrganizationNameIndex) + if err != nil { + // Catch the error - return the same error with the appropriate details + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil, &utils.GitLabRepositoryNotFound{ + OrganizationName: orgName, + } + } + + // Some other error + return nil, err + } + + return records, nil +} + +// GitLabGetRepositoriesByOrganizationName returns a list of GitLab repositories associated with the specified organization name +func (r *Repository) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) ([]*repoModels.RepositoryDBModel, error) { condition := expression.Key(repoModels.RepositoryOrganizationNameColumn).Equal(expression.Value(orgName)) filter := expression.Name(repoModels.RepositoryTypeColumn).Equal(expression.Value(utils.GitLabLower)) diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index c303177b7..fca7deff8 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -59,6 +59,7 @@ type ServiceInterface interface { GitLabGetRepositoriesByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByCLAGroup(ctx context.Context, claGroupID string, enabled bool) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) + GitLabGetRepositoriesByNamePrefix(ctx context.Context, repositoryNamePrefix string) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositories(ctx context.Context, projectSFID string, input *GitLabAddRepoModel) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitLabOrganization) ([]*v2Models.GitlabRepository, error) GitLabEnrollRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64, enrollValue bool) error From 5c94335e7cc7ea659bc8d0697a786843208297a3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 25 Aug 2021 15:05:30 -0700 Subject: [PATCH 0464/1276] Updated GitLab Group Configuration Update API (#3201) - Updated GitLab group configuration API logic, return status in the response, resolved branch protection flag issue, added note to record. Signed-off-by: David Deal --- cla-backend-go/swagger/cla.v2.yaml | 2 + cla-backend-go/v2/common/models.go | 27 ++++----- .../v2/gitlab_organizations/handlers.go | 58 +++++++++++++++++-- .../v2/gitlab_organizations/repository.go | 29 ++++++---- 4 files changed, 87 insertions(+), 29 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 3ec200636..c014011ae 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1718,6 +1718,8 @@ paths: x-request-id: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gitlab-project-organizations' '400': $ref: '#/responses/invalid-request' '401': diff --git a/cla-backend-go/v2/common/models.go b/cla-backend-go/v2/common/models.go index 5b94929c3..427ed6c2f 100644 --- a/cla-backend-go/v2/common/models.go +++ b/cla-backend-go/v2/common/models.go @@ -32,19 +32,20 @@ type GitLabOrganization struct { // ToModel converts to models.GitlabOrganization func ToModel(in *GitLabOrganization) *models2.GitlabOrganization { return &models2.GitlabOrganization{ - OrganizationID: in.OrganizationID, - DateCreated: in.DateCreated, - DateModified: in.DateModified, - OrganizationName: in.OrganizationName, - OrganizationFullPath: in.OrganizationFullPath, - OrganizationURL: in.OrganizationURL, - OrganizationSfid: in.OrganizationSFID, - Version: in.Version, - Enabled: in.Enabled, - AutoEnabled: in.AutoEnabled, - AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, - ProjectSfid: in.ProjectSFID, - OrganizationExternalID: int64(in.ExternalGroupID), + OrganizationID: in.OrganizationID, + DateCreated: in.DateCreated, + DateModified: in.DateModified, + OrganizationName: in.OrganizationName, + OrganizationFullPath: in.OrganizationFullPath, + OrganizationURL: in.OrganizationURL, + OrganizationSfid: in.OrganizationSFID, + Version: in.Version, + Enabled: in.Enabled, + AutoEnabled: in.AutoEnabled, + AutoEnabledClaGroupID: in.AutoEnabledClaGroupID, + BranchProtectionEnabled: in.BranchProtectionEnabled, + ProjectSfid: in.ProjectSFID, + OrganizationExternalID: int64(in.ExternalGroupID), } } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index e91f48e27..502606e55 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -195,24 +195,57 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic api.GitlabOrganizationsUpdateProjectGitlabGroupConfigHandler = gitlab_organizations.UpdateProjectGitlabGroupConfigHandlerFunc(func(params gitlab_organizations.UpdateProjectGitlabGroupConfigParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) + utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsAddProjectGitlabOrganizationHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "authUser": authUser.UserName, + "authEmail": authUser.Email, + "projectSFID": params.ProjectSFID, + "gitLabGroupID": params.GitLabGroupID, + "autoEnabled": params.Body.AutoEnabled, + "autoEnabledCLAGroupID": params.Body.AutoEnabledClaGroupID, + "branchProtectionEnabled": params.Body.BranchProtectionEnabled, + } + + // Load the project + psc := project_service.GetClient() + projectModel, err := psc.GetProject(params.ProjectSFID) + if err != nil || projectModel == nil { + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigNotFound().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) + } + + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { + msg := fmt.Sprintf("user %s does not have access to Update Project GitLab Group/Organizations for Project '%s' with scope of %s", + authUser.UserName, projectModel.Name, params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigForbidden().WithPayload( + utils.ErrorResponseForbidden(reqID, msg)) + } + if !utils.ValidateAutoEnabledClaGroupID(params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID) { msg := "AutoEnabledClaGroupID can't be empty when AutoEnabled is set to true" return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - err := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, params.GitLabGroupID, "", "", params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) - if err != nil { - if errors.Is(err, projects_cla_groups.ErrCLAGroupDoesNotExist) { - return gitlab_organizations.NewUpdateProjectGitlabGroupConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, err.Error())) + updateErr := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, params.GitLabGroupID, "", "", params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + if updateErr != nil { + if errors.Is(updateErr, projects_cla_groups.ErrCLAGroupDoesNotExist) { + msg := fmt.Sprintf("problem updating GitLab group/organization for project %s with SFID: %s - CLA Group wth ID: %s was not found, error: %+v", projectModel.Name, projectModel.ID, params.Body.AutoEnabledClaGroupID, updateErr) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, msg)) } - return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, "updating gitlab org", err)) + msg := fmt.Sprintf("problem updating GitLab group/organization for project %s with SFID: %s, error: %+v", projectModel.Name, projectModel.ID, updateErr) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, updateErr)) } eventService.LogEventWithContext(ctx, &events.LogEventArgs{ EventType: events.GitlabOrganizationUpdated, ProjectSFID: params.ProjectSFID, + ProjectName: projectModel.Name, + CLAGroupID: params.Body.AutoEnabledClaGroupID, LfUsername: authUser.UserName, UserName: authUser.UserName, EventData: &events.GitlabOrganizationUpdatedEventData{ @@ -222,7 +255,20 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic }, }) - return gitlab_organizations.NewUpdateProjectGitlabGroupConfigOK() + results, err := service.GetGitLabOrganizations(ctx, params.ProjectSFID) + if err != nil { + if strings.ContainsAny(err.Error(), "getProjectNotFound") { + msg := fmt.Sprintf("Gitlab organization with project SFID not found: %s", params.ProjectSFID) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigNotFound().WithPayload(utils.ErrorResponseNotFound(reqID, msg)) + } + + msg := fmt.Sprintf("failed to locate Gitlab organization by project SFID: %s, error: %+v", params.ProjectSFID, err) + log.WithFields(f).Debug(msg) + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err)) + } + + return gitlab_organizations.NewUpdateProjectGitlabGroupConfigOK().WithPayload(results) }) api.GitlabOrganizationsDeleteProjectGitlabGroupConfigHandler = gitlab_organizations.DeleteProjectGitlabGroupConfigHandlerFunc(func(params gitlab_organizations.DeleteProjectGitlabGroupConfigParams, authUser *auth.User) middleware.Responder { diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index af939445b..f244da8b3 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -549,21 +549,27 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI } _, currentTime := utils.CurrentTime() + note := fmt.Sprintf("Updated configuration on %s by %s.", currentTime, utils.GetUserNameFromContext(ctx)) + if existingRecord.Note != "" { + note = fmt.Sprintf("%s. %s", existingRecord.Note, note) + } + expressionAttributeNames := map[string]*string{ - "#A": aws.String(GitLabOrganizationsAutoEnabledColumn), - "#C": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), - "#B": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), - "#M": aws.String(GitLabOrganizationsDateModifiedColumn), - "#E": aws.String(GitLabOrganizationsEnabledColumn), + "#AE": aws.String(GitLabOrganizationsAutoEnabledColumn), + "#AECLA": aws.String(GitLabOrganizationsAutoEnabledCLAGroupIDColumn), + "#BP": aws.String(GitLabOrganizationsBranchProtectionEnabledColumn), + "#M": aws.String(GitLabOrganizationsDateModifiedColumn), + "#E": aws.String(GitLabOrganizationsEnabledColumn), + "#N": aws.String(GitLabOrganizationsNoteColumn), } expressionAttributeValues := map[string]*dynamodb.AttributeValue{ - ":a": { + ":ae": { BOOL: aws.Bool(autoEnabled), }, - ":c": { + ":aecla": { S: aws.String(autoEnabledClaGroupID), }, - ":b": { + ":bp": { BOOL: aws.Bool(branchProtectionEnabled), }, ":m": { @@ -572,8 +578,11 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI ":e": { BOOL: aws.Bool(enabled), }, + ":n": { + S: aws.String(note), + }, } - updateExpression := "SET #A = :a, #C = :c, #B = :b, #M = :m, #E = :e " + updateExpression := "SET #AE = :ae, #AECLA = :aecla, #BP = :bp, #M = :m, #E = :e, #N = :n " if organizationName != "" { expressionAttributeNames["#N"] = aws.String(GitLabOrganizationsOrganizationNameColumn) @@ -633,7 +642,7 @@ func (repo *Repository) DeleteGitLabOrganizationByFullPath(ctx context.Context, _, currentTime := utils.CurrentTime() note := fmt.Sprintf("Enabled set to false due to org deletion on %s by %s.", currentTime, utils.GetUserNameFromContext(ctx)) if org.Note != "" { - note = fmt.Sprintf("%s. %s.", org.Note, note) + note = fmt.Sprintf("%s. %s", org.Note, note) } _, err := repo.dynamoDBClient.UpdateItem( &dynamodb.UpdateItemInput{ From e65799efb2916b52b22e53b5e740a80ede5bc0f8 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 25 Aug 2021 17:32:00 -0700 Subject: [PATCH 0465/1276] Added Checks for Duplicate GitLab Groups/Orgs Used By Other Projects (#3202) --- cla-backend-go/utils/errors.go | 34 +++++++++++++++++++ .../v2/gitlab_organizations/handlers.go | 4 +++ .../v2/gitlab_organizations/service.go | 32 +++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/cla-backend-go/utils/errors.go b/cla-backend-go/utils/errors.go index 67193de67..d93b59f5f 100644 --- a/cla-backend-go/utils/errors.go +++ b/cla-backend-go/utils/errors.go @@ -66,6 +66,40 @@ func (e *CLAGroupNotFound) Unwrap() error { return e.Err } +// ProjectSummary is a quick data model for the project name and ID +type ProjectSummary struct { + ID string + Name string +} + +// ProjectConflict is an error model for project conflict +type ProjectConflict struct { + Message string + ProjectA ProjectSummary + ProjectB ProjectSummary + Err error +} + +// Error is an error string function for CLA Group not found errors +func (e *ProjectConflict) Error() string { + msg := fmt.Sprintf("%s - ", e.Message) + msg = fmt.Sprintf("%s conflict between project %s (%s) and project %s (%s)", + msg, + e.ProjectA.Name, e.ProjectA.ID, + e.ProjectB.Name, e.ProjectB.ID, + ) + if e.Err == nil { + return strings.TrimSpace(msg) + } + + return strings.TrimSpace(fmt.Sprintf("%s, error: %+v", msg, e.Err)) +} + +// Unwrap method returns its contained error +func (e *ProjectConflict) Unwrap() error { + return e.Err +} + // CLAGroupNameConflict is an error model for CLA Group name conflicts type CLAGroupNameConflict struct { CLAGroupID string diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 502606e55..7c2be448b 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -169,6 +169,10 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic result, err := service.AddGitLabOrganization(ctx, params.ProjectSFID, params.Body) if err != nil { + if _, ok := err.(*utils.ProjectConflict); ok { + return gitlab_organizations.NewAddProjectGitlabOrganizationConflict().WithPayload( + utils.ErrorResponseConflict(reqID, err.Error())) + } msg := fmt.Sprintf("unable to add GitLab organization, error: %+v", err) log.WithFields(f).WithError(err).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationBadRequest().WithPayload( diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index d7b8ed1db..ba3e06f9d 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -93,7 +93,39 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, } } + // If we have an existing record/entry if existingModel != nil { + // Check to make sure another project doesn't own this GitLab Group - only care about conflicts if it is enabled + if existingModel.ProjectSfid != projectSFID && existingModel.Enabled { + psc := project_service.GetClient() + requestedProjectModel, projectLookupErr := psc.GetProject(projectSFID) + if projectLookupErr != nil || requestedProjectModel == nil { + return nil, projectLookupErr + } + existingProjectModel, projectLookupErr := psc.GetProject(existingModel.ProjectSfid) + if projectLookupErr != nil || existingProjectModel == nil { + log.WithFields(f).WithError(projectLookupErr).Warnf("unable to lookup project with SFID: %s", existingModel.ProjectSfid) + return nil, projectLookupErr + } + msg := fmt.Sprintf("unable to add or update the GitLab Group/Organization - already taken by another project: %s (%s) - unable to add to this project: %s (%s)", + existingProjectModel.Name, existingModel.ProjectSfid, + requestedProjectModel.Name, projectSFID) + log.WithFields(f).Warn(msg) + + // Return the error model + return nil, &utils.ProjectConflict{ + Message: "unable to add or update the GitLab Group/Organization - already taken by another project", + ProjectA: utils.ProjectSummary{ + Name: requestedProjectModel.Name, + ID: projectSFID, + }, + ProjectB: utils.ProjectSummary{ + Name: existingProjectModel.Name, + ID: existingModel.ProjectSfid, + }, + } + } + updateErr := s.UpdateGitLabOrganization(ctx, projectSFID, input.GroupID, "", input.OrganizationFullPath, utils.BoolValue(input.AutoEnabled), input.AutoEnabledClaGroupID, utils.BoolValue(input.BranchProtectionEnabled)) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warnf("problem updating GitLab group/organization, error: %+v", updateErr) From e1601bc30dbad3823ce97c369070b935b3907fcc Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Thu, 26 Aug 2021 08:34:49 -0700 Subject: [PATCH 0466/1276] Feature/GitLab MR Update (#3204) - Added integration with /v4/gitlab/trigger endpoint - Updated ICLA callback for gitlab upon icla sign completion Signed-off-by: Harold Wanyama --- cla-backend/cla/controllers/signing.py | 4 +- cla-backend/cla/models/docusign_models.py | 38 ++++++++++++++---- cla-backend/cla/routes.py | 11 +++-- cla-backend/cla/utils.py | 49 ++++++++++++++++++++++- 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index 701e3d82c..b8cf5f6d0 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -181,7 +181,7 @@ def post_individual_signed(content, installation_id, github_repository_id, chang """ get_signing_service().signed_individual_callback(content, installation_id, github_repository_id, change_request_id) -def post_individual_signed_gitlab(content, user_id): +def post_individual_signed_gitlab(content, user_id, organization_id, gitlab_repository_id, merge_request_id): """ Handle the posted callback from the signing service after ICLA signature. @@ -190,7 +190,7 @@ def post_individual_signed_gitlab(content, user_id): :param user_id: The ID of the user that signed. :type user_id: string """ - get_signing_service().signed_individual_callback_gitlab(content,user_id) + get_signing_service().signed_individual_callback_gitlab(content,user_id, organization_id, gitlab_repository_id, merge_request_id) def post_individual_signed_gerrit(content, user_id): diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 027488f8b..a742dd9ff 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -16,6 +16,7 @@ import xml.etree.ElementTree as ET from typing import Dict, Any, Optional, List from urllib.parse import urlparse +import requests import pydocusign # type: ignore from attr import dataclass @@ -178,7 +179,7 @@ def request_individual_signature_gitlab(self, project_id, user_id, return_url=No cla.log.debug('Individual Signature - get active signature metadata: {}'.format(signature_metadata)) cla.log.debug('Individual Signature - get individual signature callback url') - callback_url = self._generate_individual_signature_callback_url_gitlab(user_id) + callback_url = cla.utils.get_individual_signature_callback_url_gitlab(user_id, signature_metadata) cla.log.debug('Individual Signature - get individual signature callback url: {}'.format(callback_url)) if latest_signature is not None and \ @@ -985,13 +986,7 @@ def _generate_individual_signature_callback_url_gerrit(self, user_id): """ return os.path.join(api_base_url, 'v2/signed/gerrit/individual', str(user_id)) - - def _generate_individual_signature_callback_url_gitlab(self, user_id): - """ - Helper function to get a user's active signature callback URL for GitLab - """ - return os.path.join(api_base_url, 'v2/signed/gitlab/individual', str(user_id)) def _get_corporate_signature_callback_url(self, project_id, company_id): """ @@ -1731,7 +1726,31 @@ def signed_individual_callback_gerrit(self, content, user_id): self.send_to_s3(document_data, project_id, signature_id, 'icla', user_id) cla.log.debug(f'{fn} - uploaded ICLA document to s3') - def signed_individual_callback_gitlab(self, content, user_id): + def _update_gitlab_mr(self, organization_id: str , gitlab_repository_id: int, merge_request_id: int) -> None: + """ + Helper function that updates mr upon a successful signing + param organization_id: Gitlab group id + rtype organization_id: int + param gitlab_repository_id: Gitlab repository + rtype: int + param merge_request_id: Gitlab MR + rtype: int + """ + fn = 'models.docusign_models._update_gitlab_mr' + try: + url = f'{cla.config.PLATFORM_GATEWAY_URL}/cla-service/v4/gitlab/trigger' + payload = { + "gitlab_external_repository_id": gitlab_repository_id, + "gitlab_mr_id": merge_request_id, + "gitlab_organization_id": organization_id + } + requests.post(url, data=payload) + cla.log.debug(f'{fn} - Updating GitLab MR with payload: {payload}') + except requests.exceptions.HTTPError as err: + msg = f'{fn} - Unable to update GitLab MR: {merge_request_id}, error: {err}' + cla.log.warning(msg) + + def signed_individual_callback_gitlab(self, content, user_id, organization_id, gitlab_repository_id, merge_request_id): fn = 'models.docusign_models.signed_individual_callback_gitlab' cla.log.debug(f'{fn} - Docusign GitLab ICLA signed callback POST data: {content}') tree = ET.fromstring(content) @@ -1761,6 +1780,9 @@ def signed_individual_callback_gitlab(self, content, user_id): populate_signature_from_icla_callback(content, tree, signature) signature.save() + #Update repository provider (GitLab) + self._update_gitlab_mr(organization_id, gitlab_repository_id, merge_request_id) + # Load the Project by ID and send audit event project = Project() try: diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 735d7a056..4ab7db905 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -1349,20 +1349,23 @@ def post_individual_signed( ) @hug.post( - "/signed/gitlab/individual/{user_id}", versions=2, + "/signed/gitlab/individual/{user_id}/{organization_id}/{gitlab_repository_id}/{merge_request_id}", versions=2, ) def post_individual_signed_gitlab( body, - user_id: hug.types.uuid + user_id: hug.types.uuid, + organization_id: hug.types.text, + gitlab_repository_id: hug.types.number, + merge_request_id: hug.types.number, ): """ - POST: /signed/gerritindividual/{user_id} + POST: /signed/gitlab/individual/{user_id}/{organization_id}/{gitlab_repository_id}/{merge_request_id} Callback URL from signing service upon ICLA signature for a Gitlab user. """ content = body.read() return cla.controllers.signing.post_individual_signed_gitlab( - content, user_id + content, user_id, organization_id, gitlab_repository_id, merge_request_id ) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 917c9f873..8b4fd5074 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -24,7 +24,7 @@ from cla.models import DoesNotExist from cla.models.dynamo_models import User, Signature, Repository, \ Company, Project, Document, \ - GitHubOrg, Gerrit, UserPermissions, Event, CompanyInvite, ProjectCLAGroup, CCLAWhitelistRequest, CLAManagerRequest + GitHubOrg, Gerrit, UserPermissions, Event, CompanyInvite, ProjectCLAGroup, CCLAWhitelistRequest, CLAManagerRequest, GitlabOrg from cla.models.event_types import EventType API_BASE_URL = os.environ.get('CLA_API_BASE', '') @@ -1239,6 +1239,22 @@ def get_installation_id_from_github_repository(github_repository_id): # Get this organization's installation ID return organization.get_organization_installation_id() +def get_organization_id_from_gitlab_repository(gitlab_repository_id): + # Get repository ID that references the gitlab ID. + try: + repository = Repository().get_repository_by_external_id(gitlab_repository_id, 'gitlab') + except DoesNotExist: + return None + # Get GitLabGroup from this repository + gitLabOrg = GitlabOrg() + try: + gitLabOrg.load(repository.get_repository_organization_name()) + except DoesNotExist: + return None + + #return GitLab organization ID + return gitLabOrg.get_organization_id() + def get_project_id_from_github_repository(github_repository_id): # Get repository ID that references the github ID. @@ -1281,6 +1297,37 @@ def get_individual_signature_callback_url(user_id, metadata=None): return os.path.join(API_BASE_URL, 'v2/signed/individual', str(installation_id), str(metadata['repository_id']), str(metadata['pull_request_id'])) +def get_individual_signature_callback_url_gitlab(user_id, metadata=None): + """ + Helper function to get a user's active signature callback URL. + + :param user_id: The user ID in question. + :type user_id: string + :param metadata: The signature metadata + :type metadata: dict + :return: The callback URL that will be hit by the signing service provider. + :rtype: string + """ + if metadata is None: + metadata = get_active_signature_metadata(user_id) + if metadata is None: + cla.log.warning('Could not find active signature for user {}, callback URL request failed'.format(user_id)) + return None + + # Get GitLab ID from metadata + gitlab_repository_id = metadata['repository_id'] + + # Get organization id + organization_id = get_organization_id_from_gitlab_repository(gitlab_repository_id) + + if organization_id is None: + cla.log.error('Could not find GitLab organization ID that is configured for this repository ID: %s', + gitlab_repository_id) + return None + + return os.path.join(API_BASE_URL, 'v2/signed/gitlab/individual',str(user_id), str(organization_id), str(metadata['repository_id']), + str(metadata['merge_request_id'])) + def request_individual_signature(installation_id, github_repository_id, user, change_request_id, callback_url=None): """ From 1137fddab6789e807bac82029ceee04919128822 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 26 Aug 2021 16:23:49 -0700 Subject: [PATCH 0467/1276] Added/Updated GitLab Org Response Models (#3206) --- cla-backend-go/serverless.yml | 1 + .../common/gitlab-project-organization.yaml | 11 + cla-backend-go/utils/project_helpers.go | 2 +- cla-backend-go/v2/common/models.go | 9 +- .../v2/gitlab_organizations/constants.go | 2 + .../v2/gitlab_organizations/handlers.go | 63 +++- .../v2/gitlab_organizations/repository.go | 312 +++++++++++------- .../v2/gitlab_organizations/service.go | 114 +++---- cla-backend-go/v2/project-service/client.go | 18 +- .../v2/repositories/gitlab_services.go | 1 + cla-backend-go/v2/repositories/service.go | 14 +- cla-backend/serverless.yml | 3 +- 12 files changed, 337 insertions(+), 213 deletions(-) diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index 9808f2d07..c8f8b0b3c 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -198,6 +198,7 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" diff --git a/cla-backend-go/swagger/common/gitlab-project-organization.yaml b/cla-backend-go/swagger/common/gitlab-project-organization.yaml index f33b7fcf5..be9d40e84 100644 --- a/cla-backend-go/swagger/common/gitlab-project-organization.yaml +++ b/cla-backend-go/swagger/common/gitlab-project-organization.yaml @@ -4,6 +4,12 @@ type: object description: GitLab Project Organization properties: + project_sfid: + description: The project SFID + $ref: './common/properties/external-id.yaml' + parent_project_sfid: + description: The parent project SFID + $ref: './common/properties/external-id.yaml' auto_enabled: type: boolean description: Flag to indicate if auto-enabled flag should be enabled. Organizations with auto-enable turned on will automatically include any new repositories to the EasyCLA configuration. @@ -51,6 +57,11 @@ properties: - partial_connection - connection_failure - no_connection + connection_status_message: + type: string + description: An optional connection status message + example: 'Token was revoked. You have to re-authorize from the user.' + x-omitempty: true repositories: type: array items: diff --git a/cla-backend-go/utils/project_helpers.go b/cla-backend-go/utils/project_helpers.go index 066b4c622..2934f3821 100644 --- a/cla-backend-go/utils/project_helpers.go +++ b/cla-backend-go/utils/project_helpers.go @@ -18,7 +18,7 @@ func IsProjectHaveParent(project *models.ProjectOutputDetailed) bool { return project != nil && project.Foundation != nil && project.Foundation.ID != "" && project.Foundation.Name != "" } -// IsProjectHasRootParent determines if the a given project has a root parent. A root parent is a parent that is empty parent or the parent is TLF or LFProjects +// IsProjectHasRootParent determines if a given project has a root parent. A root parent is a parent that is empty parent or the parent is TLF or LFProjects func IsProjectHasRootParent(project *models.ProjectOutputDetailed) bool { return project.Foundation == nil || (project.Foundation != nil && project.Foundation.ID != "" && (project.Foundation.Name == TheLinuxFoundation || project.Foundation.Name == LFProjectsLLC)) } diff --git a/cla-backend-go/v2/common/models.go b/cla-backend-go/v2/common/models.go index 427ed6c2f..b2fe363e7 100644 --- a/cla-backend-go/v2/common/models.go +++ b/cla-backend-go/v2/common/models.go @@ -61,14 +61,16 @@ func ToModels(input []*GitLabOrganization) []*models2.GitlabOrganization { // GitLabAddOrganization is data model for GitLab add organization requests type GitLabAddOrganization struct { OrganizationID string `json:"organization_id"` - ExternalGroupID int `json:"external_gitlab_group_id"` + ExternalGroupID int64 `json:"external_gitlab_group_id"` DateCreated string `json:"date_created,omitempty"` DateModified string `json:"date_modified,omitempty"` OrganizationName string `json:"organization_name,omitempty"` OrganizationNameLower string `json:"organization_name_lower,omitempty"` + OrganizationFullPath string `json:"organization_full_path,omitempty"` OrganizationURL string `json:"organization_url,omitempty"` OrganizationSFID string `json:"organization_sfid,omitempty"` ProjectSFID string `json:"project_sfid"` + ParentProjectSFID string `json:"parent_project_sfid"` Enabled bool `json:"enabled"` AutoEnabled bool `json:"auto_enabled"` BranchProtectionEnabled bool `json:"branch_protection_enabled"` @@ -77,3 +79,8 @@ type GitLabAddOrganization struct { AuthState string `json:"auth_state"` Version string `json:"version,omitempty"` } + +// ExternalGroupIDAsInt returns the external group ID as an integer value +func (m *GitLabAddOrganization) ExternalGroupIDAsInt() int { + return int(m.ExternalGroupID) +} diff --git a/cla-backend-go/v2/gitlab_organizations/constants.go b/cla-backend-go/v2/gitlab_organizations/constants.go index d84966b2a..bbd072a0c 100644 --- a/cla-backend-go/v2/gitlab_organizations/constants.go +++ b/cla-backend-go/v2/gitlab_organizations/constants.go @@ -4,6 +4,8 @@ package gitlab_organizations const ( + // GitLabOrganizationsProjectSFIDColumn constant + GitLabOrganizationsProjectSFIDColumn = "project_sfid" // GitLabOrganizationsOrganizationIDColumn constant GitLabOrganizationsOrganizationIDColumn = "organization_id" // GitLabOrganizationsOrganizationSFIDColumn constant diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 7c2be448b..7d147b366 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -11,12 +11,14 @@ import ( "regexp" "strings" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + "github.com/go-openapi/runtime" - project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + projectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" - gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + gitlabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/gofrs/uuid" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" @@ -50,7 +52,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Load the project - psc := project_service.GetClient() + psc := projectService.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { return gitlab_organizations.NewGetProjectGitlabOrganizationsNotFound().WithPayload( @@ -100,13 +102,20 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Load the project - psc := project_service.GetClient() + psc := projectService.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) } + // Load the project parent + parentProjectModel, err := psc.GetParentProjectModel(params.ProjectSFID) + if err != nil || parentProjectModel == nil { + return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate parent project from project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Add Project GitLab Organizations for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) @@ -123,6 +132,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequest(reqID, msg)) } + orgURL := params.Body.OrganizationFullPath // Clean up/filter the Group Full Path, if needed if params.Body.OrganizationFullPath != "" { r, regexErr := regexp.Compile(`^http(s)?://`) @@ -167,7 +177,25 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequestWithError(reqID, msg, err)) } - result, err := service.AddGitLabOrganization(ctx, params.ProjectSFID, params.Body) + // If the parent is TLF, then use the same project SFID value for the parent SFID value + parentProjectSFID := parentProjectModel.ID + if utils.IsProjectHasRootParent(projectModel) { + parentProjectSFID = params.ProjectSFID + } + + // Convert the various input parameters and values to an add GitLab Group/Org model + inputModel := &common.GitLabAddOrganization{ + ProjectSFID: params.ProjectSFID, + ParentProjectSFID: parentProjectSFID, // could be the same SFID as the project SFID if parent is TLF + AutoEnabled: utils.BoolValue(params.Body.AutoEnabled), + AutoEnabledClaGroupID: params.Body.AutoEnabledClaGroupID, + BranchProtectionEnabled: utils.BoolValue(params.Body.BranchProtectionEnabled), + ExternalGroupID: params.Body.GroupID, + OrganizationURL: orgURL, + OrganizationFullPath: params.Body.OrganizationFullPath, + } + + result, err := service.AddGitLabOrganization(ctx, inputModel) if err != nil { if _, ok := err.(*utils.ProjectConflict); ok { return gitlab_organizations.NewAddProjectGitlabOrganizationConflict().WithPayload( @@ -215,13 +243,20 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Load the project - psc := project_service.GetClient() + psc := projectService.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { return gitlab_organizations.NewUpdateProjectGitlabGroupConfigNotFound().WithPayload( utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate project with ID: %s", params.ProjectSFID))) } + // Load the project parent + parentProjectModel, err := psc.GetParentProjectModel(params.ProjectSFID) + if err != nil || parentProjectModel == nil { + return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( + utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate parent project from project with ID: %s", params.ProjectSFID))) + } + if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user %s does not have access to Update Project GitLab Group/Organizations for Project '%s' with scope of %s", authUser.UserName, projectModel.Name, params.ProjectSFID) @@ -235,7 +270,17 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_organizations.NewUpdateProjectGitlabGroupConfigBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) } - updateErr := service.UpdateGitLabOrganization(ctx, params.ProjectSFID, params.GitLabGroupID, "", "", params.Body.AutoEnabled, params.Body.AutoEnabledClaGroupID, params.Body.BranchProtectionEnabled) + inputModel := &common.GitLabAddOrganization{ + ProjectSFID: params.ProjectSFID, + ParentProjectSFID: parentProjectModel.ID, + AutoEnabled: params.Body.AutoEnabled, + AutoEnabledClaGroupID: params.Body.AutoEnabledClaGroupID, + BranchProtectionEnabled: params.Body.BranchProtectionEnabled, + ExternalGroupID: params.GitLabGroupID, + Enabled: true, + } + + updateErr := service.UpdateGitLabOrganization(ctx, inputModel) if updateErr != nil { if errors.Is(updateErr, projects_cla_groups.ErrCLAGroupDoesNotExist) { msg := fmt.Sprintf("problem updating GitLab group/organization for project %s with SFID: %s - CLA Group wth ID: %s was not found, error: %+v", projectModel.Name, projectModel.ID, params.Body.AutoEnabledClaGroupID, updateErr) @@ -289,7 +334,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // Load the project - psc := project_service.GetClient() + psc := projectService.GetClient() projectModel, err := psc.GetProject(params.ProjectSFID) if err != nil || projectModel == nil { return gitlab_organizations.NewDeleteProjectGitlabGroupConfigNotFound().WithPayload( @@ -368,7 +413,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // now fetch the oauth credentials and store to db - oauthResp, err := gitlab_api.FetchOauthCredentials(params.Code) + oauthResp, err := gitlabApi.FetchOauthCredentials(params.Code) if err != nil { msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) log.WithFields(f).WithError(err).Warn(msg) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index f244da8b3..b12b893ef 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -28,10 +28,12 @@ import ( // indexes const ( - // GitlabOrgSFIDIndex the index for the SFID - GitlabOrgSFIDIndex = "gitlab-org-sfid-index" - // GitlabOrgLowerNameIndex the index for the group/org naem in lower case - GitlabOrgLowerNameIndex = "gitlab-organization-name-lower-search-index" + // GitLabOrgOrganizationSFIDIndex the index for the Project Parent SFID + GitLabOrgOrganizationSFIDIndex = "gitlab-org-sfid-index" + // GitLabOrgProjectSFIDIndex the index for the Project SFID + GitLabOrgProjectSFIDIndex = "gitlab-project-sfid-index" + // GitLabOrgLowerNameIndex the index for the group/org name in lower case + GitLabOrgLowerNameIndex = "gitlab-organization-name-lower-search-index" // GitLabExternalIDIndex the index for the external ID GitLabExternalIDIndex = "gitlab-external-group-id-index" // GitLabFullPathIndex the index for the full path @@ -40,14 +42,14 @@ const ( // RepositoryInterface is interface for gitlab org data model type RepositoryInterface interface { - AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, groupName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) + AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) (*v2Models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error - UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error + UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) error DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgFullPath string) error } @@ -68,60 +70,59 @@ func NewRepository(awsSession *session.Session, stage string) RepositoryInterfac } // AddGitLabOrganization adds the specified values to the GitLab Group/Org table -func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) { +func (repo *Repository) AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.AddGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "parentProjectSFID": parentProjectSFID, - "projectSFID": projectSFID, - "groupID": groupID, - "organizationName": organizationName, - "groupFullPath": groupFullPath, - "autoEnabled": autoEnabled, - "autoEnabledClaGroupID": autoEnabledClaGroupID, - "branchProtectionEnabled": branchProtectionEnabled, + "parentProjectSFID": input.ParentProjectSFID, + "projectSFID": input.ProjectSFID, + "groupID": input.ExternalGroupID, + "organizationName": input.OrganizationName, + "groupFullPath": input.OrganizationFullPath, + "autoEnabled": input.AutoEnabled, + "autoEnabledClaGroupID": input.AutoEnabledClaGroupID, + "branchProtectionEnabled": input.BranchProtectionEnabled, "enabled": enabled, } var existingRecord *common.GitLabOrganization var getErr error - if groupID != 0 { - log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", groupID) + if input.ExternalGroupID != 0 { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", input.ExternalGroupID) // First, let's check to see if we have an existing gitlab organization with the same name - existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) + existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, input.ExternalGroupID) if getErr != nil { - log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by ID: %d - ok to create a new record", groupID) + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by ID: %d - ok to create a new record", input.ExternalGroupID) } - } else if groupFullPath != "" { - log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", groupFullPath) + } else if input.OrganizationFullPath != "" { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", input.OrganizationFullPath) // First, let's check to see if we have an existing gitlab organization with the same name - existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) + existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) if getErr != nil { - log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by full path: %s - ok to create a new record", groupFullPath) + log.WithFields(f).WithError(getErr).Debugf("unable to locate existing GitLab group by full path: %s - ok to create a new record", input.OrganizationFullPath) } } if existingRecord != nil { - log.WithFields(f).Debugf("An existing GitLab organization with ID %d or full path: %s exists in our database", groupID, groupFullPath) + log.WithFields(f).Debugf("An existing GitLab organization with ID %d or full path: %s exists in our database", input.ExternalGroupID, input.OrganizationFullPath) // If everything matches... - if projectSFID == existingRecord.ProjectSFID { + if input.ProjectSFID == existingRecord.ProjectSFID { log.WithFields(f).Debug("existing GitLab organization with same SFID - should be able to update it") - updateErr := repo.UpdateGitLabOrganization(ctx, projectSFID, groupID, organizationName, groupFullPath, - autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, enabled) + updateErr := repo.UpdateGitLabOrganization(ctx, input, enabled) if updateErr != nil { return nil, updateErr } - if groupID > 0 { + if input.ExternalGroupID > 0 { // Return the updated record - if gitlabOrg, err := repo.GetGitLabOrganizationByExternalID(ctx, groupID); err != nil { + if gitlabOrg, err := repo.GetGitLabOrganizationByExternalID(ctx, input.ExternalGroupID); err != nil { return nil, err } else { return common.ToModel(gitlabOrg), nil } - } else if groupFullPath != "" { + } else if input.OrganizationFullPath != "" { // Return the updated record - if gitlabOrg, err := repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath); err != nil { + if gitlabOrg, err := repo.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath); err != nil { return nil, err } else { return common.ToModel(gitlabOrg), nil @@ -152,50 +153,50 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, parentProject OrganizationID: organizationID.String(), DateCreated: currentTime, DateModified: currentTime, - OrganizationName: organizationName, - OrganizationNameLower: strings.ToLower(organizationName), - OrganizationFullPath: groupFullPath, - ExternalGroupID: int(groupID), - OrganizationSFID: parentProjectSFID, - ProjectSFID: projectSFID, + OrganizationName: input.OrganizationName, + OrganizationNameLower: strings.ToLower(input.OrganizationName), + OrganizationURL: input.OrganizationURL, + OrganizationFullPath: input.OrganizationFullPath, + ExternalGroupID: input.ExternalGroupIDAsInt(), + OrganizationSFID: input.ParentProjectSFID, + ProjectSFID: input.ProjectSFID, Enabled: enabled, - AutoEnabled: autoEnabled, - AutoEnabledClaGroupID: autoEnabledClaGroupID, - BranchProtectionEnabled: branchProtectionEnabled, + AutoEnabled: input.AutoEnabled, + AutoEnabledClaGroupID: input.AutoEnabledClaGroupID, + BranchProtectionEnabled: input.BranchProtectionEnabled, AuthState: authStateNonce.String(), Version: "v1", - // OrganizationURL: set later when we can authenticate to the API } - log.WithFields(f).Debug("Encoding GitLab organization record for adding to the database...") + log.WithFields(f).Debug("encoding GitLab organization record for adding to the database...") av, err := dynamodbattribute.MarshalMap(gitlabOrg) if err != nil { log.WithFields(f).WithError(err).Warn("unable to marshall request for query") return nil, err } - log.WithFields(f).Debug("Adding gitlab organization record to the database...") + log.WithFields(f).Debug("adding gitlab organization record to the database...") _, err = repo.dynamoDBClient.PutItem(&dynamodb.PutItemInput{ Item: av, TableName: aws.String(repo.gitlabOrgTableName), ConditionExpression: aws.String("attribute_not_exists(organization_name)"), }) if err != nil { - if aerr, ok := err.(awserr.Error); ok { - switch aerr.Code() { + if aErr, ok := err.(awserr.Error); ok { + switch aErr.Code() { case dynamodb.ErrCodeConditionalCheckFailedException: - log.WithFields(f).WithError(err).Warn("gitlab organization already exists") - return nil, fmt.Errorf("gitlab organization already exists") + log.WithFields(f).WithError(err).Warn("gitlab group/organization already exists") + return nil, fmt.Errorf("gitlab group/organization already exists") } } - log.WithFields(f).WithError(err).Warn("cannot put gitlab organization in dynamodb") + log.WithFields(f).WithError(err).Warn("cannot put gitlab group/organization in dynamodb") return nil, err } return common.ToModel(gitlabOrg), nil } -// GetGitLabOrganizations get GitLab organizations based on the project SFID +// GetGitLabOrganizations get GitLab organizations based on the project SFID or parent project SFID func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.repository.GetGitLabOrganizations", @@ -203,52 +204,56 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID "projectSFID": projectSFID, } - condition := expression.Key(GitLabOrganizationsOrganizationSFIDColumn).Equal(expression.Value(projectSFID)) - builder := expression.NewBuilder().WithKeyCondition(condition) - - filter := expression.Name("enabled").Equal(expression.Value(true)) - builder = builder.WithFilter(filter) - - // Use the nice builder to create the expression - expr, err := builder.Build() - if err != nil { - log.WithFields(f).WithError(err).Warnf("problem building query expression, error: %+v", err) - return nil, err + type getResponseChannelModel struct { + Response *v2Models.GitlabOrganizations + Error error } + // A channel for the responses from the go routines + responseChannel := make(chan *getResponseChannelModel) - // Assemble the query input parameters - queryInput := &dynamodb.QueryInput{ - ExpressionAttributeNames: expr.Names(), - ExpressionAttributeValues: expr.Values(), - KeyConditionExpression: expr.KeyCondition(), - ProjectionExpression: expr.Projection(), - FilterExpression: expr.Filter(), - TableName: aws.String(repo.gitlabOrgTableName), - IndexName: aws.String(GitlabOrgSFIDIndex), - } - - results, err := repo.dynamoDBClient.Query(queryInput) - if err != nil { - log.WithFields(f).Warnf("error retrieving gitlab_organizations using project_sfid = %s. error = %s", projectSFID, err.Error()) - return nil, err - } + // Search the project SFID column + go func(ctx context.Context, projectSFID string) { + condition := expression.Key(GitLabOrganizationsProjectSFIDColumn).Equal(expression.Value(projectSFID)) + filter := expression.Name("enabled").Equal(expression.Value(true)) + response, err := repo.getOrganizationsWithConditionFilter(ctx, condition, filter, GitLabOrgProjectSFIDIndex) + responseChannel <- &getResponseChannelModel{ + Response: response, + Error: err, + } + }(ctx, projectSFID) + + // Search the organization SFID (parent sfid) column + go func(ctx context.Context, projectSFID string) { + condition := expression.Key(GitLabOrganizationsOrganizationSFIDColumn).Equal(expression.Value(projectSFID)) + filter := expression.Name("enabled").Equal(expression.Value(true)) + response, err := repo.getOrganizationsWithConditionFilter(ctx, condition, filter, GitLabOrgOrganizationSFIDIndex) + responseChannel <- &getResponseChannelModel{ + Response: response, + Error: err, + } + }(ctx, projectSFID) - if len(results.Items) == 0 { - log.WithFields(f).Debug("no results from query") - return &v2Models.GitlabOrganizations{ - List: []*v2Models.GitlabOrganization{}, - }, nil + // Fetch the results and combine, no duplicates + fullResponse := &v2Models.GitlabOrganizations{ + List: nil, } - var resultOutput []*common.GitLabOrganization - err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) - if err != nil { - return nil, err + for i := 0; i < 2; i++ { + select { + case response := <-responseChannel: + if response.Error != nil { + log.WithFields(f).WithError(response.Error).Warnf("unable to load CLA Group") + return nil, response.Error + } + // Update the full response with the data from the channel + updateResponse(fullResponse, response.Response) + case <-ctx.Done(): + log.WithFields(f).WithError(ctx.Err()).Warnf("waiting for GitLab group/organization query timed out") + return nil, fmt.Errorf("querying GitLab group/organization failed : %v", ctx.Err()) + } } - log.WithFields(f).Debug("building response model...") - gitlabOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) - return &v2Models.GitlabOrganizations{List: gitlabOrgList}, nil + return fullResponse, nil } // GetGitLabOrganizationByName get GitLab organization by name @@ -276,7 +281,7 @@ func (repo *Repository) GetGitLabOrganizationByName(ctx context.Context, gitLabO ProjectionExpression: expr.Projection(), FilterExpression: expr.Filter(), TableName: aws.String(repo.gitlabOrgTableName), - IndexName: aws.String(GitlabOrgLowerNameIndex), + IndexName: aws.String(GitLabOrgLowerNameIndex), } log.WithFields(f).Debugf("querying for GitLab organization by name using organization_name_lower=%s...", strings.ToLower(gitLabOrganizationName)) @@ -508,42 +513,43 @@ func (repo *Repository) UpdateGitLabOrganizationAuth(ctx context.Context, organi } // UpdateGitLabOrganization updates the GitLab group based on the specified values -func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error { +func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) error { f := logrus.Fields{ "functionName": "gitlab_organizations.repository.UpdateGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "groupID": groupID, - "groupFullPath": groupFullPath, - "organizationName": organizationName, - "autoEnabled": autoEnabled, - "autoEnabledClaGroupID": autoEnabledClaGroupID, - "branchProtectionEnabled": branchProtectionEnabled, + "projectSFID": input.ProjectSFID, + "groupID": input.ExternalGroupID, + "groupFullPath": input.OrganizationFullPath, + "organizationName": input.OrganizationName, + "autoEnabled": input.AutoEnabled, + "autoEnabledClaGroupID": input.AutoEnabledClaGroupID, + "branchProtectionEnabled": input.BranchProtectionEnabled, + "enabled": enabled, "tableName": repo.gitlabOrgTableName, } var existingRecord *common.GitLabOrganization var getErr error - if groupID > 0 { - log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", groupID) - existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, groupID) + if input.ExternalGroupID > 0 { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab organization with ID: %d", input.ExternalGroupID) + existingRecord, getErr = repo.GetGitLabOrganizationByExternalID(ctx, input.ExternalGroupID) if getErr != nil { - msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v", groupID, groupFullPath) + msg := fmt.Sprintf("unable to locate existing GitLab group by ID: %d, error: %+v", input.ExternalGroupID, input.OrganizationFullPath) log.WithFields(f).WithError(getErr).Warn(msg) return errors.New(msg) } - } else if groupFullPath != "" { - log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", groupFullPath) - existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, groupFullPath) + } else if input.OrganizationFullPath != "" { + log.WithFields(f).Debugf("checking to see if we have an existing GitLab group full path with value: %s", input.OrganizationFullPath) + existingRecord, getErr = repo.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) if getErr != nil { - msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v", groupFullPath, getErr) + msg := fmt.Sprintf("unable to locate existing GitLab group by full path: %s, error: %+v", input.OrganizationFullPath, getErr) log.WithFields(f).WithError(getErr).Warn(msg) return errors.New(msg) } } if existingRecord == nil { - msg := fmt.Sprintf("error looking up GitLab group using group ID: %d or full path: %s - no results", groupID, groupFullPath) + msg := fmt.Sprintf("error looking up GitLab group using group ID: %d or full path: %s - no results", input.ExternalGroupID, input.OrganizationFullPath) log.WithFields(f).Warn(msg) return errors.New(msg) } @@ -564,13 +570,13 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI } expressionAttributeValues := map[string]*dynamodb.AttributeValue{ ":ae": { - BOOL: aws.Bool(autoEnabled), + BOOL: aws.Bool(input.AutoEnabled), }, ":aecla": { - S: aws.String(autoEnabledClaGroupID), + S: aws.String(input.AutoEnabledClaGroupID), }, ":bp": { - BOOL: aws.Bool(branchProtectionEnabled), + BOOL: aws.Bool(input.BranchProtectionEnabled), }, ":m": { S: aws.String(currentTime), @@ -584,17 +590,17 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI } updateExpression := "SET #AE = :ae, #AECLA = :aecla, #BP = :bp, #M = :m, #E = :e, #N = :n " - if organizationName != "" { + if input.OrganizationName != "" { expressionAttributeNames["#N"] = aws.String(GitLabOrganizationsOrganizationNameColumn) - expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(organizationName)} + expressionAttributeValues[":n"] = &dynamodb.AttributeValue{S: aws.String(input.OrganizationName)} updateExpression = fmt.Sprintf("%s, #N = :n ", updateExpression) expressionAttributeNames["#NL"] = aws.String(GitLabOrganizationsOrganizationNameColumn) - expressionAttributeValues[":nl"] = &dynamodb.AttributeValue{S: aws.String(strings.ToLower(organizationName))} + expressionAttributeValues[":nl"] = &dynamodb.AttributeValue{S: aws.String(strings.ToLower(input.OrganizationName))} updateExpression = fmt.Sprintf("%s, #NL = :nl ", updateExpression) } - input := &dynamodb.UpdateItemInput{ + updateItemInput := &dynamodb.UpdateItemInput{ Key: map[string]*dynamodb.AttributeValue{ GitLabOrganizationsOrganizationIDColumn: { S: aws.String(existingRecord.OrganizationID), @@ -607,7 +613,7 @@ func (repo *Repository) UpdateGitLabOrganization(ctx context.Context, projectSFI } log.WithFields(f).Debugf("updating GitLab organization record: %+v", input) - _, updateErr := repo.dynamoDBClient.UpdateItem(input) + _, updateErr := repo.dynamoDBClient.UpdateItem(updateItemInput) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warnf("unable to update GitLab organization record, error: %+v", updateErr) return updateErr @@ -702,13 +708,87 @@ func (repo *Repository) DeleteGitLabOrganizationByFullPath(ctx context.Context, func buildGitlabOrganizationListModels(ctx context.Context, gitlabOrganizations []*common.GitLabOrganization) []*v2Models.GitlabOrganization { f := logrus.Fields{ - "functionName": "buildGitlabOrganizationListModels", + "functionName": "v2.gitlab_organizations.repository.buildGitlabOrganizationListModels", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } log.WithFields(f).Debugf("fetching gitlab info for the list") // Convert the database model to a response model return common.ToModels(gitlabOrganizations) +} + +// getOrganizationsWithConditionFilter fetches the repository entry based on the specified condition and filter criteria +// using the provided index +func (repo *Repository) getOrganizationsWithConditionFilter(ctx context.Context, condition expression.KeyConditionBuilder, filter expression.ConditionBuilder, indexName string) (*v2Models.GitlabOrganizations, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.repository.getOrganizationsWithConditionFilter", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "indexName": indexName, + } + + builder := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter) - // TODO: Fetch the gitlab information + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem building query expression, error: %+v", err) + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.gitlabOrgTableName), + IndexName: aws.String(indexName), + } + + log.WithFields(f).Debugf("query: %+v", queryInput) + results, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).Warnf("problem retrieving gitlab_organizations, error = %s", err.Error()) + return nil, err + } + + if len(results.Items) == 0 { + log.WithFields(f).Debug("no results from query") + return &v2Models.GitlabOrganizations{ + List: []*v2Models.GitlabOrganization{}, + }, nil + } + + var resultOutput []*common.GitLabOrganization + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) + if err != nil { + return nil, err + } + + log.WithFields(f).Debugf("building response model for %d results...", len(resultOutput)) + gitlabOrgList := buildGitlabOrganizationListModels(ctx, resultOutput) + return &v2Models.GitlabOrganizations{List: gitlabOrgList}, nil +} + +func updateResponse(fullResponse, response *v2Models.GitlabOrganizations) { + if fullResponse.List == nil { + fullResponse.List = response.List + return + } + + if response != nil && response.List != nil { + for _, item := range response.List { + found := false + for _, fr := range fullResponse.List { + if fr.OrganizationID == item.OrganizationID { + found = true + break + } + } + if !found { + fullResponse.List = append(fullResponse.List, item) + } + } + } } diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index ba3e06f9d..ee35c1fdb 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -17,10 +17,10 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/common" "github.com/communitybridge/easycla/cla-backend-go/config" - gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + gitlabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/go-openapi/strfmt" - project_service "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + projectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" @@ -33,7 +33,7 @@ import ( // ServiceInterface contains functions of GitlabOrganizations service type ServiceInterface interface { - AddGitLabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) + AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) (*models.GitlabProjectOrganizations, error) GetGitLabOrganization(ctx context.Context, gitLabOrganizationID string) (*models.GitlabOrganization, error) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) @@ -41,8 +41,8 @@ type ServiceInterface interface { GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) - UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error - UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error + UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) error + UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlabApi.OauthSuccessResponse) error DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID string, gitlabOrgFullPath string) error } @@ -51,7 +51,7 @@ type Service struct { repo RepositoryInterface v2GitRepoService repositories.ServiceInterface claGroupRepository projects_cla_groups.Repository - gitLabApp *gitlab_api.App + gitLabApp *gitlabApi.App } // NewService creates a new gitlab organization service @@ -60,19 +60,20 @@ func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceI repo: repo, v2GitRepoService: v2GitRepoService, claGroupRepository: claGroupRepository, - gitLabApp: gitlab_api.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), + gitLabApp: gitlabApi.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } // AddGitLabOrganization adds the specified GitLab organization -func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, input *models.GitlabCreateOrganization) (*models.GitlabProjectOrganizations, error) { +func (s *Service) AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) (*models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.AddGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "autoEnabled": utils.BoolValue(input.AutoEnabled), - "branchProtectionEnabled": utils.BoolValue(input.BranchProtectionEnabled), - "groupID": input.GroupID, + "projectSFID": input.ProjectSFID, + "parentProjectSFID": input.ParentProjectSFID, + "autoEnabled": input.AutoEnabled, + "branchProtectionEnabled": input.BranchProtectionEnabled, + "groupID": input.ExternalGroupID, "groupFullPath": input.OrganizationFullPath, } @@ -85,10 +86,10 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, return nil, getErr } } - if input.GroupID > 0 { - existingModel, getErr = s.GetGitLabOrganizationByGroupID(ctx, input.GroupID) + if input.ExternalGroupID > 0 { + existingModel, getErr = s.GetGitLabOrganizationByGroupID(ctx, input.ExternalGroupID) if getErr != nil { - log.WithFields(f).WithError(getErr).Warnf("problem querying GitLab group/organization using group ID: %d", input.GroupID) + log.WithFields(f).WithError(getErr).Warnf("problem querying GitLab group/organization using group ID: %d", input.ExternalGroupID) return nil, getErr } } @@ -96,9 +97,9 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, // If we have an existing record/entry if existingModel != nil { // Check to make sure another project doesn't own this GitLab Group - only care about conflicts if it is enabled - if existingModel.ProjectSfid != projectSFID && existingModel.Enabled { - psc := project_service.GetClient() - requestedProjectModel, projectLookupErr := psc.GetProject(projectSFID) + if existingModel.ProjectSfid != input.ProjectSFID && existingModel.Enabled { + psc := projectService.GetClient() + requestedProjectModel, projectLookupErr := psc.GetProject(input.ProjectSFID) if projectLookupErr != nil || requestedProjectModel == nil { return nil, projectLookupErr } @@ -109,7 +110,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, } msg := fmt.Sprintf("unable to add or update the GitLab Group/Organization - already taken by another project: %s (%s) - unable to add to this project: %s (%s)", existingProjectModel.Name, existingModel.ProjectSfid, - requestedProjectModel.Name, projectSFID) + requestedProjectModel.Name, input.ProjectSFID) log.WithFields(f).Warn(msg) // Return the error model @@ -117,7 +118,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, Message: "unable to add or update the GitLab Group/Organization - already taken by another project", ProjectA: utils.ProjectSummary{ Name: requestedProjectModel.Name, - ID: projectSFID, + ID: input.ProjectSFID, }, ProjectB: utils.ProjectSummary{ Name: existingProjectModel.Name, @@ -126,49 +127,23 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, projectSFID string, } } - updateErr := s.UpdateGitLabOrganization(ctx, projectSFID, input.GroupID, "", input.OrganizationFullPath, utils.BoolValue(input.AutoEnabled), input.AutoEnabledClaGroupID, utils.BoolValue(input.BranchProtectionEnabled)) + updateErr := s.UpdateGitLabOrganization(ctx, input) if updateErr != nil { log.WithFields(f).WithError(updateErr).Warnf("problem updating GitLab group/organization, error: %+v", updateErr) return nil, getErr } - return s.GetGitLabOrganizations(ctx, projectSFID) + return s.GetGitLabOrganizations(ctx, input.ProjectSFID) } - psc := v2ProjectService.GetClient() - project, err := psc.GetProject(projectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warn("problem loading project details from the project service") - return nil, err - } - - var parentProjectSFID string - if utils.StringValue(project.Parent) == "" || (project.Foundation != nil && - (project.Foundation.Name == utils.TheLinuxFoundation || project.Foundation.Name == utils.LFProjectsLLC)) { - parentProjectSFID = projectSFID - } else { - parentProjectSFID = utils.StringValue(project.Parent) - } - f["parentProjectSFID"] = parentProjectSFID - log.WithFields(f).Debug("located parentProjectID...") - log.WithFields(f).Debug("adding GitLab organization...") - autoEnabled := false - if input.AutoEnabled != nil { - autoEnabled = utils.BoolValue(input.AutoEnabled) - } - branchProtectionEnabled := false - if input.BranchProtectionEnabled != nil { - branchProtectionEnabled = utils.BoolValue(input.BranchProtectionEnabled) - } - - resp, err := s.repo.AddGitLabOrganization(ctx, parentProjectSFID, projectSFID, input.GroupID, "", input.OrganizationFullPath, autoEnabled, input.AutoEnabledClaGroupID, branchProtectionEnabled, true) + resp, err := s.repo.AddGitLabOrganization(ctx, input, true) if err != nil { log.WithFields(f).WithError(err).Warn("problem adding gitlab organization for project") return nil, err } log.WithFields(f).Debugf("created GitLab organization with ID: %s", resp.OrganizationID) - return s.GetGitLabOrganizations(ctx, projectSFID) + return s.GetGitLabOrganizations(ctx, input.ProjectSFID) } // GetGitLabOrganization returns the GitLab organization based on the specified GitLab Organization ID @@ -334,6 +309,8 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string AutoEnabled: org.AutoEnabled, AutoEnableClaGroupID: org.AutoEnabledClaGroupID, AutoEnabledClaGroupName: strings.TrimSpace(autoEnabledCLAGroupName), + ProjectSfid: org.ProjectSfid, + ParentProjectSfid: org.OrganizationSfid, OrganizationName: org.OrganizationName, OrganizationURL: org.OrganizationURL, OrganizationFullPath: org.OrganizationFullPath, @@ -348,21 +325,24 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string rorg.ConnectionStatus = utils.NoConnection } else { if err != nil { - log.WithFields(f).Errorf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) + log.WithFields(f).Warnf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) rorg.ConnectionStatus = utils.ConnectionFailure + rorg.ConnectionStatusMessage = err.Error() } else { // We've been authenticated by the user - great, see if we can determine the list of repos... - glClient, clientErr := gitlab_api.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) + glClient, clientErr := gitlabApi.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) if clientErr != nil { - log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, clientErr) + log.WithFields(f).Warnf("using gitlab client for gitlab group id: %d, internal group/org ID: %s failed: %v", org.OrganizationExternalID, org.OrganizationID, clientErr) rorg.ConnectionStatus = utils.ConnectionFailure + rorg.ConnectionStatusMessage = clientErr.Error() } else { rorg.Repositories = s.updateRepositoryStatus(glClient, toGitLabProjectResponse(repoList)) user, _, userErr := glClient.Users.CurrentUser() if userErr != nil { - log.WithFields(f).Errorf("using gitlab client for gitlab org : %s failed : %v", org.OrganizationID, userErr) + log.WithFields(f).Warnf("using gitlab client for gitlab org: %s failed : %v", org.OrganizationID, userErr) rorg.ConnectionStatus = utils.ConnectionFailure + rorg.ConnectionStatusMessage = userErr.Error() } else { log.WithFields(f).Debugf("connected to user : %s for gitlab org : %s", user.Name, org.OrganizationID) rorg.ConnectionStatus = utils.Connected @@ -379,9 +359,9 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string sort.Slice(out.List, func(i, j int) bool { return strings.ToLower(out.List[i].OrganizationName) < strings.ToLower(out.List[j].OrganizationName) }) - for _, orgList := range out.List { - sort.Slice(orgList.Repositories, func(i, j int) bool { - return strings.ToLower(orgList.Repositories[i].RepositoryName) < strings.ToLower(orgList.Repositories[j].RepositoryName) + for _, projectOrganization := range out.List { + sort.Slice(projectOrganization.Repositories, func(i, j int) bool { + return strings.ToLower(projectOrganization.Repositories[i].RepositoryName) < strings.ToLower(projectOrganization.Repositories[j].RepositoryName) }) } @@ -411,7 +391,7 @@ func (s *Service) GetGitLabOrganizationByState(ctx context.Context, gitLabOrgani } // UpdateGitLabOrganizationAuth updates the GitLab organization authentication information -func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlab_api.OauthSuccessResponse) error { +func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlabApi.OauthSuccessResponse) error { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.UpdateGitLabOrganizationAuth", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -419,7 +399,7 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } log.WithFields(f).Debugf("updating gitlab org auth") - authInfoEncrypted, err := gitlab_api.EncryptAuthInfo(oauthResp, s.gitLabApp) + authInfoEncrypted, err := gitlabApi.EncryptAuthInfo(oauthResp, s.gitLabApp) if err != nil { return fmt.Errorf("encrypt failed : %v", err) } @@ -430,13 +410,13 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani } // Get a reference to the GItLab client - gitLabClient, err := gitlab_api.NewGitlabOauthClientFromAccessToken(oauthResp.AccessToken) + gitLabClient, err := gitlabApi.NewGitlabOauthClientFromAccessToken(oauthResp.AccessToken) if err != nil { return fmt.Errorf("initializing gitlab client : %v", err) } // Query the groups list - groupsWithMaintainerPerms, groupListErr := gitlab_api.GetGroupsListAll(ctx, gitLabClient, goGitLab.MaintainerPermissions) + groupsWithMaintainerPerms, groupListErr := gitlabApi.GetGroupsListAll(ctx, gitLabClient, goGitLab.MaintainerPermissions) if groupListErr != nil { return groupListErr } @@ -474,20 +454,20 @@ func (s *Service) UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrgani msg = fmt.Sprintf("full path: '%s'", gitLabOrgModel.OrganizationFullPath) } - return fmt.Errorf("unable to locate the provided GitLab group by %s using the provided permissions - discovered %d groups where user has maintainer or above permissions.", + return fmt.Errorf("unable to locate the provided GitLab group by %s using the provided permissions - discovered %d groups where user has maintainer or above permissions", msg, len(groupsWithMaintainerPerms)) } // UpdateGitLabOrganization updates the GitLab organization -func (s *Service) UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool) error { +func (s *Service) UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) error { // check if valid cla group id is passed - if autoEnabledClaGroupID != "" { - if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, autoEnabledClaGroupID); err != nil { + if input.AutoEnabledClaGroupID != "" { + if _, err := s.claGroupRepository.GetCLAGroupNameByID(ctx, input.AutoEnabledClaGroupID); err != nil { return err } } - return s.repo.UpdateGitLabOrganization(ctx, projectSFID, groupID, organizationName, groupFullPath, autoEnabled, autoEnabledClaGroupID, branchProtectionEnabled, true) + return s.repo.UpdateGitLabOrganization(ctx, input, true) } // DeleteGitLabOrganizationByFullPath deletes the specified GitLab organization by full path @@ -563,7 +543,7 @@ func toGitLabProjectResponse(gitLabListRepos *models.GitlabRepositoriesList) []* var repoList []*models.GitlabProjectRepository for _, repo := range gitLabListRepos.List { - parentProjectSFID, err := project_service.GetClient().GetParentProject(repo.RepositoryProjectSfid) + parentProjectSFID, err := projectService.GetClient().GetParentProject(repo.RepositoryProjectSfid) if err != nil { log.WithFields(f).Warnf("unable to lookup project parent SFID using SFID: %s", repo.RepositoryProjectSfid) } diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index 601627266..b0dd1822b 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -149,13 +149,9 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { "apiGWHost": apiGWHost, } - // Use our helper function to lookup the parent, if it exists + // Use our helper function to find the parent, if it exists parentModel, err := pmm.GetParentProjectModel(projectSFID) - if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) - return projectSFID, err - } - if parentModel == nil { + if err != nil || parentModel == nil { log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) return projectSFID, err } @@ -187,11 +183,11 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut return nil, nil } - // Does this project they have a parent? projectModel.Parent is deprecated and no longer returned, use project.Foundation.ID/Name attribute instead - if existingModel.Foundation.Name == utils.TheLinuxFoundation || existingModel.Foundation.Name == utils.LFProjectsLLC { - log.WithFields(f).Debugf("no parent for projectSFID %s or %s or %s is the parent...", projectSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC) - return nil, nil - } + //// Does this project they have a parent? projectModel.Parent is deprecated and no longer returned, use project.Foundation.ID/Name attribute instead + //if existingModel.Foundation.Name == utils.TheLinuxFoundation || existingModel.Foundation.Name == utils.LFProjectsLLC { + // log.WithFields(f).Debugf("no parent for projectSFID %s or %s or %s is the parent...", projectSFID, utils.TheLinuxFoundation, utils.LFProjectsLLC) + // return nil, nil + //} // Grab the parent ID once projectParentSFID := utils.GetProjectParentSFID(existingModel) diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index ccd37c90c..9eaaf2378 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -100,6 +100,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, ProjectSFID: projectSFID, RepositoryExternalID: repositoryExternalIDString, RepositoryName: project.PathWithNamespace, // Name column is actually the full path for both GitHub and GitLab + RepositoryFullPath: project.PathWithNamespace, RepositoryURL: project.WebURL, RepositoryOrganizationName: input.GroupName, RepositoryCLAGroupID: input.ClaGroupID, diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index fca7deff8..e2411433d 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -68,17 +68,17 @@ type ServiceInterface interface { GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error } -// GithubOrgRepo redefine the interface here to avoid circular dependency issues -type GithubOrgRepo interface { - AddGitLabOrganization(ctx context.Context, parentProjectSFID string, projectSFID string, groupID int64, groupName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) (*v2Models.GitlabOrganization, error) +// GitLabOrgRepo redefine the interface here to avoid circular dependency issues +type GitLabOrgRepo interface { + AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) (*v2Models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error - UpdateGitLabOrganization(ctx context.Context, projectSFID string, groupID int64, organizationName, groupFullPath string, autoEnabled bool, autoEnabledClaGroupID string, branchProtectionEnabled bool, enabled bool) error - DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgName string) error + UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) error + DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgFullPath string) error } // Service is the service model/structure @@ -87,7 +87,7 @@ type Service struct { gitV2Repository RepositoryInterface projectsClaGroupsRepo projects_cla_groups.Repository ghOrgRepo github_organizations.RepositoryInterface - glOrgRepo GithubOrgRepo + glOrgRepo GitLabOrgRepo gitLabApp *gitlab_api.App eventService events.Service } @@ -99,7 +99,7 @@ var ( ) // NewService creates a new githubOrganizations service -func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository RepositoryInterface, pcgRepo projects_cla_groups.Repository, ghOrgRepo github_organizations.RepositoryInterface, glOrgRepo GithubOrgRepo, eventService events.Service) ServiceInterface { +func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository RepositoryInterface, pcgRepo projects_cla_groups.Repository, ghOrgRepo github_organizations.RepositoryInterface, glOrgRepo GitLabOrgRepo, eventService events.Service) ServiceInterface { return &Service{ gitV1Repository: gitV1Repository, gitV2Repository: gitV2Repository, diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 53adfde2b..f3b6e1438 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -289,6 +289,7 @@ provider: - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" @@ -609,7 +610,7 @@ resources: isProd: { "Fn::Equals": [ "${env:STAGE}", "prod" ] } isStaging: { "Fn::Equals": [ "${env:STAGE}", "staging" ] } isDev: { "Fn::Equals": [ "${env:STAGE}", "dev" ] } - isNotProd: {"Fn::Or": [{"Condition": "isDev"}, {"Condition": "isStaging" }]} + isNotProd: { "Fn::Or": [ { "Condition": "isDev" }, { "Condition": "isStaging" } ] } # true when a TSL certificate should be created by serverless (false created externally) ShouldGenerateCertificate: Fn::Not: [ Fn::Equals: [ "${env:STAGE}", "prod" ] ] From b4f1c84952804bd14f0705ab3e89abbb77485103 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Fri, 27 Aug 2021 09:02:08 -0700 Subject: [PATCH 0468/1276] Feature/GitLab Emp Signature Update (#3208) - Updated Pynamodb API for User model with gitlab details - Refactored individual signature docusign flow for gitlab user - Added emp acknowledgement flow for a gitlab user Signed-off-by: Harold Wanyama --- cla-backend/cla/controllers/signing.py | 27 ++-- cla-backend/cla/models/docusign_models.py | 186 +++++----------------- cla-backend/cla/models/dynamo_models.py | 56 ++++++- 3 files changed, 108 insertions(+), 161 deletions(-) diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index b8cf5f6d0..78a163239 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -11,7 +11,7 @@ import cla from cla.models import DoesNotExist -from cla.models.dynamo_models import Signature +from cla.models.dynamo_models import Signature, User from cla.user_service import UserService from cla.utils import get_signing_service, get_signature_instance, get_email_service, \ get_repository_service, get_project_instance, get_company_instance @@ -36,14 +36,22 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur signing_service = get_signing_service() if return_url_type is not None and return_url_type.lower() == "gerrit": return signing_service.request_individual_signature_gerrit(str(project_id), str(user_id), return_url) - elif return_url_type is not None and return_url_type.lower() == "github": - # fetching the primary for the account - github = get_repository_service("github") - primary_user_email = github.get_primary_user_email(request) - return signing_service.request_individual_signature(str(project_id), str(user_id), return_url, + elif return_url_type is not None and (return_url_type.lower() == "github" or return_url_type.lower == "gitlab"): + if return_url_type == "github": + # fetching the primary for the account + github = get_repository_service("github") + primary_user_email = github.get_primary_user_email(request) + elif return_url_type == "gitlab": + try: + cla.log.debug(f"Fetching user details for: {user_id}") + user = User() + user.load(user_id) + except DoesNotExist as err: + cla.log.warning('Individual Signature - user ID was NOT found for: {}'.format(user_id)) + return {'errors': {'user_id': str(err)}} + primary_user_email = user.get_user_email() + return signing_service.request_individual_signature(str(project_id), str(user_id), return_url, return_url_type, preferred_email=primary_user_email) - elif return_url_type is not None and return_url_type.lower() == "gitlab": - return signing_service.request_individual_signature_gitlab(str(project_id), str(user_id), return_url) def request_corporate_signature(auth_user, @@ -113,9 +121,10 @@ def request_employee_signature(project_id, company_id, user_id, return_url_type, cla.log.error(f'{fn} - return type is gerrit - invoking: request_employee_signature_gerrit') return signing_service.request_employee_signature_gerrit(str(project_id), str(company_id), str(user_id), return_url) - elif return_url_type is not None and return_url_type.lower() == "github": + elif return_url_type is not None and (return_url_type.lower() == "github" or return_url_type.lower() == "gitlab"): cla.log.error(f'{fn} - return type is github - invoking: request_employee_signature') return signing_service.request_employee_signature(str(project_id), str(company_id), str(user_id), return_url) + else: msg = (f'{fn} - unsupported return type {return_url_type} for ' f'cla group: {project_id}, ' diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index a742dd9ff..5194b1be9 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -126,147 +126,8 @@ def initialize(self, config): integrator_key=integrator_key) self.s3storage = S3Storage() self.s3storage.initialize(None) - - def request_individual_signature_gitlab(self, project_id, user_id, return_url=None, callback_url=None): - request_info = 'project: {project_id}, user: {user_id} with return_url: {return_url}'.format( - project_id=project_id, user_id=user_id, return_url=return_url) - cla.log.debug('Individual Signature - creating new signature for: {}'.format(request_info)) - - # Ensure this is a valid user - user_id = str(user_id) - try: - user = User() - user.load(user_id) - cla.log.debug('Individual Signature - loaded user name: {}, ' - 'user email: {}, gh user: {}, gh id: {}'. - format(user.get_user_name(), user.get_user_email(), user.get_github_username(), - user.get_user_github_id())) - except DoesNotExist as err: - cla.log.warning('Individual Signature - user ID was NOT found for: {}'.format(request_info)) - return {'errors': {'user_id': str(err)}} - - # Ensure the project exists - try: - project = Project() - project.load(project_id) - cla.log.debug('Individual Signature - loaded project id: {}, name: {}, '. - format(project.get_project_id(), project.get_project_name())) - except DoesNotExist as err: - cla.log.warning('Individual Signature - project ID NOT found for: {}'.format(request_info)) - return {'errors': {'project_id': str(err)}} - - # Check for active signature object with this project. If the user has - # signed the most recent major version, they do not need to sign again. - cla.log.debug('Individual Signature - loading latest user signature for user: {}, project: {}'. - format(user, project)) - latest_signature = user.get_latest_signature(str(project_id)) - cla.log.debug('Individual Signature - loaded latest user signature for user: {}, project: {}'. - format(user, project)) - - cla.log.debug('Individual Signature - loading latest individual document for project: {}'. - format(project)) - last_document = project.get_latest_individual_document() - cla.log.debug('Individual Signature - loaded latest individual document for project: {}'. - format(project)) - - cla.log.debug('Individual Signature - creating default individual values for user: {}'.format(user)) - default_cla_values = create_default_individual_values(user) - cla.log.debug('Individual Signature - created default individual values: {}'.format(default_cla_values)) - # Generate signature callback url - cla.log.debug('Individual Signature - get active signature metadata') - signature_metadata = cla.utils.get_active_signature_metadata(user_id) - cla.log.debug('Individual Signature - get active signature metadata: {}'.format(signature_metadata)) - - cla.log.debug('Individual Signature - get individual signature callback url') - callback_url = cla.utils.get_individual_signature_callback_url_gitlab(user_id, signature_metadata) - cla.log.debug('Individual Signature - get individual signature callback url: {}'.format(callback_url)) - - if latest_signature is not None and \ - last_document.get_document_major_version() == latest_signature.get_signature_document_major_version(): - cla.log.debug('Individual Signature - user already has a signatures with this project: {}'. - format(latest_signature.get_signature_id())) - - # Re-generate and set the signing url - this will update the signature record - self.populate_sign_url(latest_signature, callback_url, default_values=default_cla_values, - preferred_email=user.get_user_email()) - - return {'user_id': user_id, - 'project_id': project_id, - 'signature_id': latest_signature.get_signature_id(), - 'sign_url': latest_signature.get_signature_sign_url()} - else: - cla.log.debug('Individual Signature - user does NOT have a signatures with this project: {}'. - format(project)) - - # Get signature return URL - if return_url is None: - return_url = cla.utils.get_active_signature_return_url(user_id, signature_metadata) - cla.log.debug('Individual Signature - setting signature return_url to {}'.format(return_url)) - - if return_url is None: - cla.log.warning('No active signature found for user - cannot generate ' - 'return_url without knowing where the user came from') - return {'user_id': str(user_id), - 'project_id': str(project_id), - 'signature_id': None, - 'sign_url': None, - 'error': 'No active signature found for user - cannot generate return_url without knowing where the user came from'} - - # Get latest document - try: - cla.log.debug('Individual Signature - loading project latest individual document...') - document = project.get_latest_individual_document() - cla.log.debug('Individual Signature - loaded project latest individual document: {}'.format(document)) - except DoesNotExist as err: - cla.log.warning('Individual Signature - project individual document does NOT exist for: {}'. - format(request_info)) - return {'errors': {'project_id': project_id, 'message': str(err)}} - - # If the CCLA/ICLA template is missing (not created in the project console), we won't have a document - # return an error - if not document: - return {'errors': {'project_id': project_id, 'message': 'missing template document'}} - - # Create new Signature object - cla.log.debug('Individual Signature - creating new signature document ' - 'project_id: {}, user_id: {}, return_url: {}, callback_url: {}'. - format(project_id, user_id, return_url, callback_url)) - signature = Signature(signature_id=str(uuid.uuid4()), - signature_project_id=project_id, - signature_document_major_version=document.get_document_major_version(), - signature_document_minor_version=document.get_document_minor_version(), - signature_reference_id=user_id, - signature_reference_type='user', - signature_reference_name=user.get_user_name(), - signature_type='cla', - signature_return_url_type='Github', - signature_signed=False, - signature_approved=True, - signature_return_url=return_url, - signature_callback_url=callback_url) - - # Set signature ACL - cla.log.debug('Individual Signature - setting ACL using user GH id: {}'.format(user.get_user_github_id())) - signature.set_signature_acl('github:{}'.format(user.get_user_github_id())) - - # Populate sign url - self.populate_sign_url(signature, callback_url, default_values=default_cla_values, - preferred_email=user.get_user_email()) - - # Save signature - signature.save() - cla.log.debug('Individual Signature - Saved signature for: {}'.format(request_info)) - - response = {'user_id': str(user_id), - 'project_id': project_id, - 'signature_id': signature.get_signature_id(), - 'sign_url': signature.get_signature_sign_url()} - - cla.log.debug('Individual Signature - returning response: {}'.format(response)) - return response - - def request_individual_signature(self, project_id, user_id, return_url=None, callback_url=None, + def request_individual_signature(self, project_id, user_id, return_url=None, return_url_type="github", callback_url=None, preferred_email=None): request_info = 'project: {project_id}, user: {user_id} with return_url: {return_url}'.format( project_id=project_id, user_id=user_id, return_url=return_url) @@ -319,7 +180,11 @@ def request_individual_signature(self, project_id, user_id, return_url=None, cal cla.log.debug('Individual Signature - get active signature metadata: {}'.format(signature_metadata)) cla.log.debug('Individual Signature - get individual signature callback url') - callback_url = cla.utils.get_individual_signature_callback_url(user_id, signature_metadata) + if return_url_type == "github": + callback_url = cla.utils.get_individual_signature_callback_url(user_id, signature_metadata) + elif return_url_type == "gitlab": + callback_url = cla.utils.get_individual_signature_callback_url_gitlab(user_id, signature_metadata) + cla.log.debug('Individual Signature - get individual signature callback url: {}'.format(callback_url)) if latest_signature is not None and \ @@ -387,8 +252,12 @@ def request_individual_signature(self, project_id, user_id, return_url=None, cal signature_callback_url=callback_url) # Set signature ACL - cla.log.debug('Individual Signature - setting ACL using user GH id: {}'.format(user.get_user_github_id())) - signature.set_signature_acl('github:{}'.format(user.get_user_github_id())) + if return_url_type == "github": + acl = user.get_user_github_id() + elif return_url_type == "gitlab": + acl = user.get_user_github_id() + cla.log.debug('Individual Signature - setting ACL using user {} id: {}'.format(return_url_type, acl)) + signature.set_signature_acl('github:{}'.format(acl)) # Populate sign url self.populate_sign_url(signature, callback_url, default_values=default_cla_values, @@ -769,7 +638,7 @@ def check_and_prepare_employee_signature(project_id, company_id, user_id) -> dic return {'success': {'the employee is ready to sign the CCLA'}} - def request_employee_signature(self, project_id, company_id, user_id, return_url=None): + def request_employee_signature(self, project_id, company_id, user_id, return_url=None, return_url_type="github"): fn = 'docusign_models.check_and_prepare_employee_signature' request_info = f'cla group: {project_id}, company: {company_id}, user: {user_id} with return_url: {return_url}' @@ -836,7 +705,10 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url cla.log.info(f'{fn} - created new signature document for: {request_info} - signature: {new_signature}') # Set signature ACL - acl_value = f'github:{user.get_user_github_id()}' + if return_url_type == "github": + acl_value = f'github:{user.get_user_github_id()}' + elif return_url_type == "gitlab": + acl_value = f'gitlab:{user.get_user_gitlab_id()}' cla.log.info(f'{fn} - assigning signature acl with value: {acl_value} for: {request_info}') new_signature.set_signature_acl(acl_value) @@ -867,12 +739,24 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url github_repository_id = signature_metadata['repository_id'] change_request_id = signature_metadata['pull_request_id'] - # Get repository - installation_id = cla.utils.get_installation_id_from_github_repository(github_repository_id) - if installation_id is None: - return {'errors': {'github_repository_id': 'The given github repository ID does not exist. '}} + if return_url_type == "github": + # Get repository + installation_id = cla.utils.get_installation_id_from_github_repository(github_repository_id) + if installation_id is None: + return {'errors': {'github_repository_id': 'The given github repository ID does not exist. '}} + + update_repository_provider(installation_id, github_repository_id, change_request_id) + + elif return_url_type == "gitlab": + gitlab_repository_id = signature_metadata['repository_id'] + merge_request_id = signature_metadata['merge_request_id'] + organization_id = cla.utils.get_organization_id_from_gitlab_repository(gitlab_repository_id) + self._update_gitlab_mr(organization_id, gitlab_repository_id, merge_request_id) + + if organization_id is None: + return {'errors': {'gitlab_repository_id': 'The given github repository ID does not exist. '}} + - update_repository_provider(installation_id, github_repository_id, change_request_id) cla.utils.delete_active_signature_metadata(user_id) else: cla.log.info(f'{fn} - cla group requires ICLA signature from employee - PR has been left unchanged') @@ -1807,6 +1691,8 @@ def signed_individual_callback_gitlab(self, content, user_id, organization_id, g cla.log.warning(msg) return + # Remove the active signature metadata. + cla.utils.delete_active_signature_metadata(user.get_user_id()) # Get signed document document_data = self.get_signed_document(envelope_id, user) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 73fdc9404..21d5a69e1 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -169,6 +169,34 @@ class Meta: # This attribute is the hash key for the index. user_github_username = UnicodeAttribute(hash_key=True) +class GitLabIDIndex(GlobalSecondaryIndex): + """ + This class represents a global secondary index for querying users by github username. + """ + + class Meta: + index_name = "gitlab-id-index" + write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) + read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) + projection = AllProjection() + + # This attribute is the hash key for the index. + user_gitlab_id = UnicodeAttribute(hash_key=True) + +class GitLabUsernameIndex(GlobalSecondaryIndex): + """ + This class represents a global secondary index for querying users by github username. + """ + + class Meta: + index_name = "gitlab-username-index" + write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) + read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) + projection = AllProjection() + + # This attribute is the hash key for the index. + user_gitlab_username = UnicodeAttribute(hash_key=True) + class LFUsernameIndex(GlobalSecondaryIndex): """ @@ -1495,6 +1523,10 @@ class Meta: user_github_id = NumberAttribute(null=True) user_github_username = UnicodeAttribute(null=True) user_github_username_index = GitHubUsernameIndex() + user_gitlab_id = NumberAttribute(null=True) + user_gitlab_username = UnicodeAttribute(null=True) + user_gitlab_id_index = GitLabIDIndex() + user_github_username_index = GitLabUsernameIndex() user_ldap_id = UnicodeAttribute(null=True) user_github_id_index = GitHubUserIndex() github_user_external_id_index = GithubUserExternalIndex() @@ -1516,6 +1548,8 @@ def __init__( user_external_id=None, user_github_id=None, user_github_username=None, + user_gitlab_id=None, + user_gitlab_username=None, user_ldap_id=None, lf_username=None, lf_sub=None, @@ -1539,12 +1573,14 @@ def __init__( self.model.user_company_id = user_company_id self.model.note = note self._preferred_email = preferred_email + self.model.user_gitlab_id = user_gitlab_id + self.model.user_gitlab_username = user_gitlab_username def __str__(self): return ( "id: {}, username: {}, gh id: {}, gh username: {}, " "lf email: {}, emails: {}, ldap id: {}, lf username: {}, " - "user company id: {}, note: {}, user external id: {}" + "user company id: {}, note: {}, user external id: {}, user gitlab id: {}, user gitlab username: {}" ).format( self.model.user_id, self.model.user_github_username, @@ -1556,7 +1592,9 @@ def __str__(self): self.model.lf_username, self.model.user_company_id, self.model.note, - self.model.user_external_id + self.model.user_external_id, + self.model.user_gitlab_id, + self.model.user_gitlab_username, ) def to_dict(self): @@ -1565,6 +1603,8 @@ def to_dict(self): ret["user_github_id"] = None if ret["user_ldap_id"] == "null": ret["user_ldap_id"] = None + if ret["user_gitlab_id"] == "null": + ret["user_gitlab_id"] = None return ret def log_info(self, msg): @@ -1661,6 +1701,12 @@ def get_user_github_id(self): def get_github_username(self): return self.model.user_github_username + + def get_user_gitlab_id(self): + return self.model.user_gitlab_id + + def get_user_gitlab_username(self): + return self.model.user_gitlab_username def get_user_github_username(self): """ @@ -1716,6 +1762,12 @@ def set_user_github_id(self, user_github_id): def set_user_github_username(self, user_github_username): self.model.user_github_username = user_github_username + + def set_user_gitlab_id(self, user_gitlab_id): + self.model.user_gitlab_id = user_gitlab_id + + def set_user_gitlab_username(self, user_gitlab_username): + self.model.user_gitlab_username = user_gitlab_username def set_note(self, note): self.model.note = note From 51ed1b51d990633271c23c4512ae3ada289d44ff Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 27 Aug 2021 15:36:35 -0700 Subject: [PATCH 0469/1276] Added GitLab Repository Check Lambda (#3209) - Added lambda logic - Added Makefile build steps - Updated serverless deployment - Updated circle CI/CD build and deployment Signed-off-by: David Deal --- .circleci/config.yml | 3 + cla-backend-go/.gitignore | 31 +-- cla-backend-go/Makefile | 19 +- .../cmd/gitlab_repository_check/README.md | 21 ++ .../handler/handler.go | 237 ++++++++++++++++++ .../handler/server_aws_lambda.go | 22 ++ .../handler/server_standalone.go | 25 ++ .../cmd/gitlab_repository_check/main.go | 11 + cla-backend-go/events/event_data.go | 55 ++++ cla-backend-go/events/event_types.go | 1 + cla-backend-go/utils/context.go | 14 ++ cla-backend-go/utils/lambda.go | 26 ++ cla-backend-go/utils/properties.go | 28 +++ .../v2/gitlab_organizations/handlers.go | 4 +- .../v2/gitlab_organizations/repository.go | 89 ++++++- .../v2/gitlab_organizations/service.go | 153 +++++++---- .../v2/repositories/gitlab_services.go | 54 +++- cla-backend-go/v2/repositories/repository.go | 33 +++ cla-backend-go/v2/repositories/service.go | 10 +- cla-backend/serverless.yml | 18 ++ 20 files changed, 763 insertions(+), 91 deletions(-) create mode 100644 cla-backend-go/cmd/gitlab_repository_check/README.md create mode 100644 cla-backend-go/cmd/gitlab_repository_check/handler/handler.go create mode 100644 cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go create mode 100644 cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go create mode 100644 cla-backend-go/cmd/gitlab_repository_check/main.go create mode 100644 cla-backend-go/utils/lambda.go create mode 100644 cla-backend-go/utils/properties.go diff --git a/.circleci/config.yml b/.circleci/config.yml index ad3195388..72af5e1cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -278,6 +278,7 @@ jobs: - cla-backend-go/dynamo-events-lambda - cla-backend-go/zipbuilder-scheduler-lambda - cla-backend-go/zipbuilder-lambda + - cla-backend-go/gitlab-repository-check-lambda - cla-backend-go/functional-tests buildGoBackendCommon: @@ -353,6 +354,7 @@ jobs: cp ~/cla-backend-go/dynamo-events-lambda ~/project/cla-backend/ cp ~/cla-backend-go/zipbuilder-scheduler-lambda ~/project/cla-backend/ cp ~/cla-backend-go/zipbuilder-lambda ~/project/cla-backend/ + cp ~/cla-backend-go/gitlab-repository-check-lambda ~/project/cla-backend/ ls -alF ~/project/cla-backend/ pushd ~/project/cla-backend @@ -366,6 +368,7 @@ jobs: if [[ ! -f dynamo-events-lambda ]]; then echo "Missing dynamo-events-lambda binary file. Exiting..."; exit 1; fi if [[ ! -f zipbuilder-lambda ]]; then echo "Missing zipbuilder-lambda binary file. Exiting..."; exit 1; fi if [[ ! -f zipbuilder-scheduler-lambda ]]; then echo "Missing zipbuilder-scheduler-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f gitlab-repository-check-lambda ]]; then echo "Missing gitlab-repository-check-lambda binary file. Exiting..."; exit 1; fi if [[ ! -f serverless.yml ]]; then echo "Missing serverless.yml file. Exiting..."; exit 1; fi if [[ ! -f serverless-authorizer.yml ]]; then echo "Missing serverless-authorizer.yml file. Exiting..."; exit 1; fi yarn sls deploy --force --stage ${STAGE} --region us-east-1 diff --git a/cla-backend-go/.gitignore b/cla-backend-go/.gitignore index 9dc1cd0b5..19cb1e7ba 100644 --- a/cla-backend-go/.gitignore +++ b/cla-backend-go/.gitignore @@ -4,27 +4,16 @@ cla-backend-go cla cla-mac* -backend-aws-lambda -backend-aws-lambda-mac -backend-aws-lambda-linux -user-subscribe-lambda -user-subscribe-lambda-mac -build-user-subscribe-lambda-linux -build-user-subscribe-lambda-mac -metrics-aws-lambda -metrics-aws-lambda-mac -metrics-report-lambda -metrics-report-lambda-mac -functional-tests -functional-tests-linux -functional-tests-mac -dynamo-events-lambda -dynamo-events-lambda-mac -dynamo-events-lambda-linux -zipbuilder-lambda -zipbuilder-lambda-mac -zipbuilder-scheduler-lambda-mac -zipbuilder-scheduler-lambda +backend-aws-lambda* +user-subscribe-lambda* +build-user-subscribe-lambda* +metrics-aws-lambda* +metrics-report-lambda* +functional-tests* +dynamo-events-lambda* +zipbuilder-lambda* +zipbuilder-scheduler-lambda* +gitlab-repository-check-lambda* *env.json db/schema.sql diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 89cfb5ab2..439414290 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -8,6 +8,7 @@ METRICS_REPORT_BIN = metrics-report-lambda DYNAMO_EVENTS_BIN = dynamo-events-lambda ZIPBUILDER_SCHEDULER_BIN = zipbuilder-scheduler-lambda ZIPBUILDER_BIN = zipbuilder-lambda +GITLAB_REPO_CHECK_BIN = gitlab-repository-check-lambda FUNCTIONAL_TESTS_BIN = functional-tests USER_SUBSCRIBE_BIN = user-subscribe-lambda MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) @@ -27,12 +28,12 @@ GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') .PHONY: generate setup tool-setup setup-dev setup-deploy clean-all clean swagger up fmt test run deps build build-mac build-aws-lambda user-subscribe-lambda qc lint all: all-mac -all-mac: clean swagger deps fmt build-mac build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac test lint -all-linux: clean swagger deps fmt build-linux build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux test lint +all-mac: clean swagger deps fmt build-mac build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac build-gitlab-repository-check-lambda-mac test lint +all-linux: clean swagger deps fmt build-linux build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux build-gitlab-repository-check-lambda-linux test lint lambdas-mac: build-aws-lambda-mac -build-lambdas-mac: build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-metrics-report-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac +build-lambdas-mac: build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-metrics-report-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac build-gitlab-repository-check-lambda-mac lambdas: build-lambdas-linux -build-lambdas-linux: build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-metrics-report-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux +build-lambdas-linux: build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-metrics-report-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux build-gitlab-repository-check-lambda-linux generate: swagger @@ -298,6 +299,16 @@ build-zipbuilder-lambda-mac: deps env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(ZIPBUILDER_BIN)-mac cmd/zipbuilder_lambda/main.go @chmod +x $(ZIPBUILDER_BIN)-mac +build-gitlab-repository-check-lambda-linux: deps + @echo "Building a statically linked Linux OSX amd64 binary..." + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(GITLAB_REPO_CHECK_BIN) cmd/gitlab_repository_check/main.go + @chmod +x $(GITLAB_REPO_CHECK_BIN) + +build-gitlab-repository-check-lambda-mac: deps + @echo "Building a statically linked Mac OSX amd64 binary..." + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(GITLAB_REPO_CHECK_BIN)-mac cmd/gitlab_repository_check/main.go + @chmod +x $(GITLAB_REPO_CHECK_BIN)-mac + build-functional-tests: build-functional-tests-linux build-functional-tests-linux: deps @echo "Building Functional Tests for Linux amd64 binary..." diff --git a/cla-backend-go/cmd/gitlab_repository_check/README.md b/cla-backend-go/cmd/gitlab_repository_check/README.md new file mode 100644 index 000000000..5571beeed --- /dev/null +++ b/cla-backend-go/cmd/gitlab_repository_check/README.md @@ -0,0 +1,21 @@ +# GitLab Repository Check Lambda + +GitLab (currently) does not support sending callback/webhook events for GitLab project add or delete events. As a +result, we created a small lambda that runs periodically to check for any new GitLab project +(repository) add or deletes. + +The process/algorithm is: + +1. Query our database for registered GitLab Groups - filter by the enabled flag is true and where the Auto Enable flag + is true +1. For each GitLab group in our database... + 1. Create a new GitLab API client instance using the authorization token for the Git Group + 1. Query the GitLab API for the project list under the group (include sub-groups). This grabs the list of current GitLab projects under the GitLab group. + 1. Query for GitLab project in DB matching this GitLab group path + 1. Identify deltas - this identifies how many new and deleted GitLap projects we need to process + 1. If any new GitLab projects, add to the DB, set enabled, create an event log + 1. If any removed/deleted GitLab projects, remove from the DB, create an event log + +## References + +- [GitLab Feature request discussion thread](https://gitlab.com/gitlab-com/marketing/community-relations/opensource-program/linux-foundation/-/issues/4#note_653255564) diff --git a/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go b/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go new file mode 100644 index 000000000..ca8aabdab --- /dev/null +++ b/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go @@ -0,0 +1,237 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package handler + +import ( + "context" + "strconv" + + v1Company "github.com/communitybridge/easycla/cla-backend-go/company" + "github.com/communitybridge/easycla/cla-backend-go/events" + "github.com/communitybridge/easycla/cla-backend-go/gerrits" + "github.com/communitybridge/easycla/cla-backend-go/github" + "github.com/communitybridge/easycla/cla-backend-go/github_organizations" + gitLabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + ini "github.com/communitybridge/easycla/cla-backend-go/init" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/project" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + v1Repositories "github.com/communitybridge/easycla/cla-backend-go/repositories" + "github.com/communitybridge/easycla/cla-backend-go/users" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + v2Repositories "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" + + "strings" + + "github.com/sirupsen/logrus" + goGitLab "github.com/xanzy/go-gitlab" +) + +// Init initializes the handler +func Init() { + f := logrus.Fields{ + "functionName": "cmd.gitlab_repository_check.handler.Init", + } + ctx := utils.NewContext() + f[utils.XREQUESTID] = ctx.Value(utils.XREQUESTID) + log.WithFields(f).Debug("initializing...") + + // General initialization + ini.Init() + ini.GetConfig() +} + +// Handler is invoked each time the lambda is triggered - https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html +func Handler(ctx context.Context) error { + f := logrus.Fields{ + "functionName": "cmd.update-project-statistics.Handler", + } + + // Add the x-request-id to the context + ctx = utils.NewContextFromParent(ctx) + f[utils.XREQUESTID] = ctx.Value(utils.XREQUESTID) + stage := strings.ToLower(utils.GetProperty("STAGE")) + f["stage"] = stage + + awsSession, err := ini.GetAWSSession() + if err != nil { + log.WithFields(f).WithError(err).Panic("Unable to load AWS session") + } + + configFile := ini.GetConfig() + + // initialize GitHub + github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) + // initialize GitLab + gitLabApp := gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) + + // Repository Layer + usersRepo := users.NewRepository(awsSession, stage) + eventsRepo := events.NewRepository(awsSession, stage) + v1CompanyRepo := v1Company.NewRepository(awsSession, stage) + gerritRepo := gerrits.NewRepository(awsSession, stage) + v1ProjectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) + gitV1Repository := v1Repositories.NewRepository(awsSession, stage) + gitV2Repository := v2Repositories.NewRepository(awsSession, stage) + githubOrganizationsRepo := github_organizations.NewRepository(awsSession, stage) + gitlabOrganizationRepo := gitlab_organizations.NewRepository(awsSession, stage) + v1CLAGroupRepo := project.NewRepository(awsSession, stage, gitV1Repository, gerritRepo, v1ProjectClaGroupRepo) + + // Service Layer + + type combinedRepo struct { + users.UserRepository + v1Company.IRepository + project.ProjectRepository + projects_cla_groups.Repository + } + + // Our service layer handlers + eventsService := events.NewService(eventsRepo, combinedRepo{ + usersRepo, + v1CompanyRepo, + v1CLAGroupRepo, + v1ProjectClaGroupRepo, + }) + + v2RepositoriesService := v2Repositories.NewService(gitV1Repository, gitV2Repository, v1ProjectClaGroupRepo, githubOrganizationsRepo, gitlabOrganizationRepo, eventsService) + //gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo) + + // Query GitLab Groups + // for each group + // if enabled and auto-enabled = true + // load token and client + // query for GitLab API repository list + // query for GitLab repositories in DB for this group path + // identify deltas + // if new, add to DB, create event log + // if deleted, remove from DB, create event log + + gitLabGroups, err := gitlabOrganizationRepo.GetGitLabOrganizationsEnabledWithAutoEnabled(ctx) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem querying for GitLab group/organizations that are enabled with auto-enabled flag set to true") + return err + } + + log.WithFields(f).Debugf("start - checking %d GitLab projects for add/delete events", len(gitLabGroups.List)) + for _, gitLabGroup := range gitLabGroups.List { + gitLabClient, gitLabClientErr := gitLabApi.NewGitlabOauthClient(gitLabGroup.AuthInfo, gitLabApp) + if gitLabClientErr != nil { + log.WithFields(f).WithError(gitLabClientErr).Warnf("problem loading GitLab client for group/organization: %s", gitLabGroup.OrganizationURL) + return gitLabClientErr + } + + gitLabProjects, getGitLabAPIError := gitLabApi.GetGroupProjectListByGroupID(ctx, gitLabClient, int(gitLabGroup.OrganizationExternalID)) + if getGitLabAPIError != nil { + log.WithFields(f).WithError(getGitLabAPIError).Warnf("problem loading GitLab projects for group/organization: %s using the groupID: %d - skipping GitLab Group/Organziation", gitLabGroup.OrganizationFullPath, gitLabGroup.OrganizationExternalID) + continue + } + gitLabDBProjects, getProjectListDBErr := gitV2Repository.GitLabGetRepositoriesByNamePrefix(ctx, gitLabGroup.OrganizationFullPath) + if getProjectListDBErr != nil { + log.WithFields(f).WithError(getProjectListDBErr).Warnf("problem loading GitLab projects for group/organization: %s from the database - skipping GitLab Group/Organziation", gitLabGroup.OrganizationFullPath) + continue + } + + newGitLabProjects := getNewProjects(gitLabProjects, gitLabDBProjects) + log.WithFields(f).Debugf("Found %d GitLab projects/repositories that were added for GitLab Group: %s", len(newGitLabProjects), gitLabGroup.OrganizationFullPath) + if len(newGitLabProjects) > 0 { + var gitLabProjectIDList []int64 + + // Build a quick list of the GitLab Project/repo ID values - the add repositories takes a list + for _, newGitLabProject := range newGitLabProjects { + gitLabProjectIDList = append(gitLabProjectIDList, int64(newGitLabProject.ID)) + } + + // Add the repositories - will generate a log event + _, addErr := v2RepositoriesService.GitLabAddRepositoriesWithEnabledFlag(ctx, gitLabGroup.ProjectSfid, &v2Repositories.GitLabAddRepoModel{ + ClaGroupID: gitLabGroup.AutoEnabledClaGroupID, + GroupName: gitLabGroup.OrganizationName, + ExternalID: gitLabGroup.OrganizationExternalID, + GroupFullPath: gitLabGroup.OrganizationFullPath, + ProjectIDList: gitLabProjectIDList, + }, true) // set to enabled when adding since this was added as a result of the auto-enable feature + if addErr != nil { + log.WithFields(f).WithError(addErr).Warnf("problem adding GitLab projects for group/organization: %s to the database", gitLabGroup.OrganizationFullPath) + } else { + log.WithFields(f).Debugf("added %d GitLab projects for group/organization: %s to the database", len(newGitLabProjects), gitLabGroup.OrganizationFullPath) + } + } + + deletedGitLabProjects := getDeletedProjects(gitLabProjects, gitLabDBProjects) + log.WithFields(f).Debugf("Found %d GitLab projects/repositories that were removed from the GitLab Group: %s", len(newGitLabProjects), gitLabGroup.OrganizationFullPath) + if len(deletedGitLabProjects) > 0 { + for _, gitLabProjectDBRecord := range deletedGitLabProjects { + repositoryExternalID, parseIntErr := strconv.ParseInt(gitLabProjectDBRecord.RepositoryExternalID, 10, 64) + if parseIntErr != nil { + log.WithFields(f).WithError(parseIntErr).Warnf("problem converting repository %s external ID string value: %s to integer", gitLabProjectDBRecord.RepositoryFullPath, gitLabProjectDBRecord.RepositoryExternalID) + } else { + deleteErr := v2RepositoriesService.GitLabDeleteRepositoryByExternalID(ctx, repositoryExternalID) + if deleteErr != nil { + log.WithFields(f).WithError(deleteErr).Warnf("problem deleting repository %s external ID string value: %s to integer", gitLabProjectDBRecord.RepositoryFullPath, gitLabProjectDBRecord.RepositoryExternalID) + } else { + log.WithFields(f).Debugf("deleted GitLab project %s for group/organization: %s from the database", gitLabProjectDBRecord.RepositoryName, gitLabGroup.OrganizationFullPath) + } + } + } + } + } + + log.WithFields(f).Debugf("done - checked %d GitLab projects for add/delete events", len(gitLabGroups.List)) + return nil +} + +// getNewProjects is a helper function to determine if we have any new GitLab projects that are not in our database +func getNewProjects(gitLabProjects []*goGitLab.Project, gitLabDBProjects []*v1Repositories.RepositoryDBModel) []*goGitLab.Project { + var response []*goGitLab.Project + // For each GitLab Project/Repo + for _, gitLabProject := range gitLabProjects { + found := false + + // For each GitLab Project/Repo in the database + for _, gitLabDBProject := range gitLabDBProjects { + // Compare the full name/path + if strings.ToLower(gitLabProject.PathWithNamespace) == strings.ToLower(gitLabDBProject.RepositoryFullPath) { + found = true + break + } + } + + // Didn't find the GitLab Project Repo from GitLab defined in our database - must have been added! + if !found { + // Add to our list + response = append(response, gitLabProject) + } + } + + return response +} + +// getDeletedProjects is a helper function to determine if we have any new GitLab projects that were removed from GitLab but are still in our database +func getDeletedProjects(gitLabProjects []*goGitLab.Project, gitLabDBProjects []*v1Repositories.RepositoryDBModel) []*v1Repositories.RepositoryDBModel { + var response []*v1Repositories.RepositoryDBModel + // For each GitLab Project/Repo in the database + for _, gitLabDBProject := range gitLabDBProjects { + found := false + + // For each GitLab Project/Repo + for _, gitLabProject := range gitLabProjects { + // Compare the full name/path + if strings.ToLower(gitLabProject.PathWithNamespace) == strings.ToLower(gitLabDBProject.RepositoryFullPath) { + found = true + break + } + + } + + // Didn't find the GitLab Project Repo from the database defined in GitLab - must have been removed! + if !found { + // Add to our list + response = append(response, gitLabDBProject) + } + } + + return response +} diff --git a/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go b/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go new file mode 100644 index 000000000..2300ca676 --- /dev/null +++ b/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go @@ -0,0 +1,22 @@ +// +build aws_lambda + +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package handler + +import ( + "github.com/aws/aws-lambda-go/lambda" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/sirupsen/logrus" +) + +// RunHandler starts the lambda main handler routine +func RunHandler() { + f := logrus.Fields{ + "functionName": "cmd.gitlab_repository_check.handler.RunHandler", + } + log.WithFields(f).Info("lambda server starting...") + lambda.Start(Handler) + log.WithFields(f).Infof("Lambda shutting down...") +} diff --git a/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go b/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go new file mode 100644 index 000000000..1141d2942 --- /dev/null +++ b/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go @@ -0,0 +1,25 @@ +// +build !aws_lambda + +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package handler + +import ( + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" +) + +// RunHandler starts the lambda in local testing model by invoking the handler directly +func RunHandler() { + f := logrus.Fields{ + "functionName": "cmd.gitlab_repository_check.handler.RunHandler", + } + log.WithFields(f).Debug("creating a new handler") + err := Handler(utils.NewContext()) + if err != nil { + log.WithFields(f).WithError(err).Warn("error returned from handler") + } + log.Infof("handler completed") +} diff --git a/cla-backend-go/cmd/gitlab_repository_check/main.go b/cla-backend-go/cmd/gitlab_repository_check/main.go new file mode 100644 index 000000000..5b2723e97 --- /dev/null +++ b/cla-backend-go/cmd/gitlab_repository_check/main.go @@ -0,0 +1,11 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import "github.com/communitybridge/easycla/cla-backend-go/cmd/gitlab_repository_check/handler" + +func main() { + handler.Init() + handler.RunHandler() +} diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 653cc6644..6ef709283 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -48,6 +48,12 @@ type RepositoryDisabledEventData struct { RepositoryExternalID int64 } +// RepositoryDeletedEventData event data model +type RepositoryDeletedEventData struct { + RepositoryName string + RepositoryExternalID int64 +} + // RepositoryRenamedEventData event data model type RepositoryRenamedEventData struct { NewRepositoryName string @@ -505,6 +511,29 @@ func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) return data, true } +// GetEventDetailsString returns the details string for this event +func (ed *RepositoryDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := "The GitHub repository " // nolint + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if ed.RepositoryName != "" { + data = data + fmt.Sprintf(" with repository name %s", ed.RepositoryName) + } + if ed.RepositoryExternalID > 0 { + data = data + fmt.Sprintf(" with repository external ID %d", ed.RepositoryExternalID) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectSFID) + } + data = data + " was deleted" + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventDetailsString returns the details string for this event func (ed *RepositoryRenamedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository renamed from %s to %s for the project %s", ed.OldRepositoryName, ed.NewRepositoryName, args.ProjectName) @@ -1503,6 +1532,32 @@ func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) return data, true } +// GetEventSummaryString returns the summary string for this event +func (ed *RepositoryDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := "The GitHub repository " // nolint + if args.CLAGroupName != "" { + data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) + } + if ed.RepositoryName != "" { + data = data + fmt.Sprintf(" with repository name %s", ed.RepositoryName) + } + if ed.RepositoryExternalID > 0 { + data = data + fmt.Sprintf(" with repository external ID %d", ed.RepositoryExternalID) + } + if args.ProjectSFID != "" { + data = data + fmt.Sprintf(" for the project %s", args.ProjectSFID) + } + data = data + " was deleted" + if args.CompanyName != "" { + data = data + fmt.Sprintf(" for the company %s", args.CompanyName) + } + if args.UserName != "" { + data = data + fmt.Sprintf(" by the user %s", args.UserName) + } + data = data + "." + return data, true +} + // GetEventSummaryString returns the summary string for this event func (ed *RepositoryRenamedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { data := fmt.Sprintf("The GitHub repository was renamed from %s to %s", ed.OldRepositoryName, ed.NewRepositoryName) diff --git a/cla-backend-go/events/event_types.go b/cla-backend-go/events/event_types.go index 85cffeea7..9ad8537f7 100644 --- a/cla-backend-go/events/event_types.go +++ b/cla-backend-go/events/event_types.go @@ -35,6 +35,7 @@ const ( RepositoryRenamed = "repository.renamed" RepositoryTransferred = "repository.transferred" RepositoryDisabled = "repository.disabled" + RepositoryDeleted = "repository.deleted" RepositoryUpdated = "repository.updated" RepositoryBranchProtectionAdded = "repository.branchprotection.updated" RepositoryBranchProtectionDisabled = "repository.branchprotection.updated" diff --git a/cla-backend-go/utils/context.go b/cla-backend-go/utils/context.go index 49e612a72..f9e092015 100644 --- a/cla-backend-go/utils/context.go +++ b/cla-backend-go/utils/context.go @@ -42,6 +42,20 @@ func NewContextWithUser(authUser *auth.User) context.Context { return context.WithValue(context.WithValue(context.Background(), XREQUESTID, requestID), CtxAuthUser, authUser) // nolint } +// NewContextFromParent returns a new context object with a new request ID based on the parent +func NewContextFromParent(ctx context.Context) context.Context { + f := logrus.Fields{ + "functionName": "utils.NewContext", + } + requestID, err := uuid.NewV4() + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to generate a UUID for x-request-id") + return context.Background() + } + + return context.WithValue(ctx, XREQUESTID, requestID.String()) // nolint +} + // ContextWithRequestAndUser returns a new context with the specified request ID and user func ContextWithRequestAndUser(ctx context.Context, reqID string, authUser *auth.User) context.Context { return context.WithValue(context.WithValue(ctx, XREQUESTID, reqID), CtxAuthUser, authUser) // nolint diff --git a/cla-backend-go/utils/lambda.go b/cla-backend-go/utils/lambda.go new file mode 100644 index 000000000..c449c9a1a --- /dev/null +++ b/cla-backend-go/utils/lambda.go @@ -0,0 +1,26 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "context" + "fmt" + + "github.com/aws/aws-lambda-go/events" +) + +//GetHTTPOKResponse : return Get HTTP Success Response +func GetHTTPOKResponse(ctx context.Context) events.APIGatewayProxyResponse { + resp := events.APIGatewayProxyResponse{ + StatusCode: 200, + IsBase64Encoded: false, + Body: "", + Headers: map[string]string{ + "Content-Type": "application/json", + XREQUESTID: fmt.Sprintf("%+v", ctx.Value(XREQUESTID)), + "X-MyCompany-Func-Reply": "hello-handler", + }, + } + return resp +} diff --git a/cla-backend-go/utils/properties.go b/cla-backend-go/utils/properties.go new file mode 100644 index 000000000..410759e69 --- /dev/null +++ b/cla-backend-go/utils/properties.go @@ -0,0 +1,28 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package utils + +import ( + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +// GetProperty is a common routine to bind and return the specified environment variable +func GetProperty(property string) string { + f := logrus.Fields{ + "functionName": "utils.properties.GetProperty", + } + err := viper.BindEnv(property) + if err != nil { + log.WithFields(f).WithError(err).Fatalf("unable to load property: %s - value not defined or empty", property) + } + + value := viper.GetString(property) + if value == "" { + log.WithFields(f).WithError(err).Fatalf("property: %s cannot be empty", property) + } + + return value +} diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 7d147b366..ebb38e675 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -67,7 +67,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseForbidden(reqID, msg)) } - result, err := service.GetGitLabOrganizations(ctx, params.ProjectSFID) + result, err := service.GetGitLabOrganizationsByProjectSFID(ctx, params.ProjectSFID) if err != nil { if strings.ContainsAny(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("Gitlab organization with project SFID not found: %s", params.ProjectSFID) @@ -304,7 +304,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic }, }) - results, err := service.GetGitLabOrganizations(ctx, params.ProjectSFID) + results, err := service.GetGitLabOrganizationsByProjectSFID(ctx, params.ProjectSFID) if err != nil { if strings.ContainsAny(err.Error(), "getProjectNotFound") { msg := fmt.Sprintf("Gitlab organization with project SFID not found: %s", params.ProjectSFID) diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index b12b893ef..d8ca8abf8 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -43,7 +43,10 @@ const ( // RepositoryInterface is interface for gitlab org data model type RepositoryInterface interface { AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) (*v2Models.GitlabOrganization, error) - GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) + GetGitLabOrganizations(ctx context.Context) (*v2Models.GitlabOrganizations, error) + GetGitLabOrganizationsEnabled(ctx context.Context) (*v2Models.GitlabOrganizations, error) + GetGitLabOrganizationsEnabledWithAutoEnabled(ctx context.Context) (*v2Models.GitlabOrganizations, error) + GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) @@ -196,10 +199,31 @@ func (repo *Repository) AddGitLabOrganization(ctx context.Context, input *common return common.ToModel(gitlabOrg), nil } -// GetGitLabOrganizations get GitLab organizations based on the project SFID or parent project SFID -func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) { +// GetGitLabOrganizations returns the complete list of GitLab groups/organizations +func (repo *Repository) GetGitLabOrganizations(ctx context.Context) (*v2Models.GitlabOrganizations, error) { + // No filter, return all + return repo.getScanResults(ctx, nil) +} + +// GetGitLabOrganizationsEnabled returns the list of GitLab groups/organizations that are enabled +func (repo *Repository) GetGitLabOrganizationsEnabled(ctx context.Context) (*v2Models.GitlabOrganizations, error) { + // Build the scan/query expression + filter := expression.Name(GitLabOrganizationsEnabledColumn).Equal(expression.Value(true)) + return repo.getScanResults(ctx, &filter) +} + +// GetGitLabOrganizationsEnabledWithAutoEnabled returns the list of GitLab groups/organizations that are enabled with the auto enabled flag set to true +func (repo *Repository) GetGitLabOrganizationsEnabledWithAutoEnabled(ctx context.Context) (*v2Models.GitlabOrganizations, error) { + // Build the scan/query expression + filter := expression.Name(GitLabOrganizationsEnabledColumn).Equal(expression.Value(true)). + And(expression.Name(GitLabOrganizationsAutoEnabledColumn).Equal(expression.Value(true))) + return repo.getScanResults(ctx, &filter) +} + +// GetGitLabOrganizationsByProjectSFID get GitLab organizations based on the project SFID or parent project SFID +func (repo *Repository) GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.repository.GetGitLabOrganizations", + "functionName": "v2.gitlab_organizations.repository.GetGitLabOrganizationsByProjectSFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -214,7 +238,7 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID // Search the project SFID column go func(ctx context.Context, projectSFID string) { condition := expression.Key(GitLabOrganizationsProjectSFIDColumn).Equal(expression.Value(projectSFID)) - filter := expression.Name("enabled").Equal(expression.Value(true)) + filter := expression.Name(GitLabOrganizationsEnabledColumn).Equal(expression.Value(true)) response, err := repo.getOrganizationsWithConditionFilter(ctx, condition, filter, GitLabOrgProjectSFIDIndex) responseChannel <- &getResponseChannelModel{ Response: response, @@ -225,7 +249,7 @@ func (repo *Repository) GetGitLabOrganizations(ctx context.Context, projectSFID // Search the organization SFID (parent sfid) column go func(ctx context.Context, projectSFID string) { condition := expression.Key(GitLabOrganizationsOrganizationSFIDColumn).Equal(expression.Value(projectSFID)) - filter := expression.Name("enabled").Equal(expression.Value(true)) + filter := expression.Name(GitLabOrganizationsEnabledColumn).Equal(expression.Value(true)) response, err := repo.getOrganizationsWithConditionFilter(ctx, condition, filter, GitLabOrgOrganizationSFIDIndex) responseChannel <- &getResponseChannelModel{ Response: response, @@ -792,3 +816,56 @@ func updateResponse(fullResponse, response *v2Models.GitlabOrganizations) { } } } + +func (repo *Repository) getScanResults(ctx context.Context, filter *expression.ConditionBuilder) (*v2Models.GitlabOrganizations, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.repository.GetGitLabOrganizations", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + builder := expression.NewBuilder() + + // Add the filter if provided + if filter != nil { + builder.WithFilter(*filter) + } + + // Build the scan/query expression + expr, builderErr := builder.Build() + if builderErr != nil { + log.WithFields(f).Warnf("error building expression for %s scan, error: %v", repo.gitlabOrgTableName, builderErr) + return nil, builderErr + } + + scanInput := &dynamodb.ScanInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + FilterExpression: expr.Filter(), + ProjectionExpression: expr.Projection(), + TableName: aws.String(repo.gitlabOrgTableName), + } + + var resultList []map[string]*dynamodb.AttributeValue + for { + results, scanErr := repo.dynamoDBClient.Scan(scanInput) //nolint + if scanErr != nil { + log.WithFields(f).Warnf("error retrieving scan results from table %s, error: %v", repo.gitlabOrgTableName, scanErr) + return nil, scanErr + } + resultList = append(resultList, results.Items...) + if len(results.LastEvaluatedKey) != 0 { + scanInput.ExclusiveStartKey = results.LastEvaluatedKey + } else { + break + } + } + + var gitlabOrganizations *v2Models.GitlabOrganizations + unmarshalErr := dynamodbattribute.UnmarshalListOfMaps(resultList, &gitlabOrganizations) + if unmarshalErr != nil { + log.Warnf("error unmarshalling %s from database. error: %v", repo.gitlabOrgTableName, unmarshalErr) + return nil, unmarshalErr + } + + return gitlabOrganizations, nil +} diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index ee35c1fdb..cbd1e3031 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -22,7 +22,7 @@ import ( projectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" - "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" + v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -33,14 +33,17 @@ import ( // ServiceInterface contains functions of GitlabOrganizations service type ServiceInterface interface { - AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) (*models.GitlabProjectOrganizations, error) - GetGitLabOrganization(ctx context.Context, gitLabOrganizationID string) (*models.GitlabOrganization, error) + AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) (*v2Models.GitlabProjectOrganizations, error) + GetGitLabOrganization(ctx context.Context, gitLabOrganizationID string) (*v2Models.GitlabOrganization, error) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) - GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) - GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*models.GitlabOrganization, error) - GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*models.GitlabOrganization, error) - GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) - GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) + GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*v2Models.GitlabOrganization, error) + GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*v2Models.GitlabOrganization, error) + GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*v2Models.GitlabOrganization, error) + GetGitLabOrganizations(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) + GetGitLabOrganizationsEnabled(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) + GetGitLabOrganizationsEnabledWithAutoEnabled(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) + GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabProjectOrganizations, error) + GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*v2Models.GitlabOrganization, error) UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) error UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlabApi.OauthSuccessResponse) error DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID string, gitlabOrgFullPath string) error @@ -55,7 +58,7 @@ type Service struct { } // NewService creates a new gitlab organization service -func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceInterface, claGroupRepository projects_cla_groups.Repository) *Service { +func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceInterface, claGroupRepository projects_cla_groups.Repository) ServiceInterface { return &Service{ repo: repo, v2GitRepoService: v2GitRepoService, @@ -65,7 +68,7 @@ func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceI } // AddGitLabOrganization adds the specified GitLab organization -func (s *Service) AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) (*models.GitlabProjectOrganizations, error) { +func (s *Service) AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) (*v2Models.GitlabProjectOrganizations, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.AddGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -77,7 +80,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, input *common.GitLa "groupFullPath": input.OrganizationFullPath, } - var existingModel *models.GitlabOrganization + var existingModel *v2Models.GitlabOrganization var getErr error if input.OrganizationFullPath != "" { existingModel, getErr = s.GetGitLabOrganizationByFullPath(ctx, input.OrganizationFullPath) @@ -132,7 +135,7 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, input *common.GitLa log.WithFields(f).WithError(updateErr).Warnf("problem updating GitLab group/organization, error: %+v", updateErr) return nil, getErr } - return s.GetGitLabOrganizations(ctx, input.ProjectSFID) + return s.GetGitLabOrganizationsByProjectSFID(ctx, input.ProjectSFID) } log.WithFields(f).Debug("adding GitLab organization...") @@ -143,11 +146,11 @@ func (s *Service) AddGitLabOrganization(ctx context.Context, input *common.GitLa } log.WithFields(f).Debugf("created GitLab organization with ID: %s", resp.OrganizationID) - return s.GetGitLabOrganizations(ctx, input.ProjectSFID) + return s.GetGitLabOrganizationsByProjectSFID(ctx, input.ProjectSFID) } // GetGitLabOrganization returns the GitLab organization based on the specified GitLab Organization ID -func (s *Service) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*v2Models.GitlabOrganization, error) { dbModel, err := s.GetGitLabOrganizationByID(ctx, gitlabOrganizationID) if err != nil { return nil, err @@ -178,7 +181,7 @@ func (s *Service) GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizat } // GetGitLabOrganizationByName returns the gitlab organization based on the Group/Org name -func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByName", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -195,7 +198,7 @@ func (s *Service) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganiz } // GetGitLabOrganizationByFullPath returns the GitLab group/organization using the specified full path -func (s *Service) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByFullPath", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -215,7 +218,7 @@ func (s *Service) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrg } // GetGitLabOrganizationByGroupID returns the GitLab group/organization using the specified group ID -func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByGroupID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -235,10 +238,55 @@ func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGrou return common.ToModel(dbModel), nil } -// GetGitLabOrganizations returns a collection of GitLab organizations based on the specified project SFID value -func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string) (*models.GitlabProjectOrganizations, error) { +// GetGitLabOrganizations returns the complete list of GitLab groups/organizations +func (s *Service) GetGitLabOrganizations(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) { + gitLabOrganizations, err := s.repo.GetGitLabOrganizations(ctx) + if err != nil { + return nil, err + } + + // Our response model + out := &v2Models.GitlabProjectOrganizations{ + List: s.toGitLabProjectOrganizationList(ctx, gitLabOrganizations), + } + + return out, nil +} + +// GetGitLabOrganizationsEnabled returns the list of GitLab groups/organizations that are enabled +func (s *Service) GetGitLabOrganizationsEnabled(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) { + gitLabOrganizations, err := s.repo.GetGitLabOrganizationsEnabled(ctx) + if err != nil { + return nil, err + } + + // Our response model + out := &v2Models.GitlabProjectOrganizations{ + List: s.toGitLabProjectOrganizationList(ctx, gitLabOrganizations), + } + + return out, nil +} + +// GetGitLabOrganizationsEnabledWithAutoEnabled returns the list of GitLab groups/organizations that are enabled with the auto enabled flag set to true +func (s *Service) GetGitLabOrganizationsEnabledWithAutoEnabled(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) { + gitLabOrganizations, err := s.repo.GetGitLabOrganizationsEnabledWithAutoEnabled(ctx) + if err != nil { + return nil, err + } + + // Our response model + out := &v2Models.GitlabProjectOrganizations{ + List: s.toGitLabProjectOrganizationList(ctx, gitLabOrganizations), + } + + return out, nil +} + +// GetGitLabOrganizationsByProjectSFID returns a collection of GitLab organizations based on the specified project SFID value +func (s *Service) GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabProjectOrganizations, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizations", + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationsByProjectSFID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "projectSFID": projectSFID, } @@ -262,7 +310,7 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string // Load the GitLab Organization and Repository details - result will be missing CLA Group info and ProjectSFID details log.WithFields(f).Debugf("loading Gitlab organizations for projectSFID: %s", projectSFID) - orgList, err := s.repo.GetGitLabOrganizations(ctx, projectSFID) + orgList, err := s.repo.GetGitLabOrganizationsByProjectSFID(ctx, projectSFID) if err != nil { log.WithFields(f).WithError(err).Warn("problem loading gitlab organizations from the project service") return nil, err @@ -270,12 +318,23 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string log.WithFields(f).Debugf("loaded %d Gitlab organizations for projectSFID: %s", len(orgList.List), projectSFID) // Our response model - out := &models.GitlabProjectOrganizations{ - List: make([]*models.GitlabProjectOrganization, 0), + out := &v2Models.GitlabProjectOrganizations{ + List: s.toGitLabProjectOrganizationList(ctx, orgList), } - orgMap := make(map[string]*models.GitlabProjectOrganization) - for _, org := range orgList.List { + return out, nil +} + +func (s *Service) toGitLabProjectOrganizationList(ctx context.Context, dbModels *v2Models.GitlabOrganizations) []*v2Models.GitlabProjectOrganization { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.toGitLabProjectOrganizationList", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + var response []*v2Models.GitlabProjectOrganization + + orgMap := make(map[string]*v2Models.GitlabProjectOrganization) + for _, org := range dbModels.List { autoEnabledCLAGroupName := "" if org.AutoEnabledClaGroupID != "" { log.WithFields(f).Debugf("loading CLA Group by ID: %s to obtain the name for GitLab auth enabled CLA Group response", org.AutoEnabledClaGroupID) @@ -298,14 +357,14 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string log.WithFields(f).Debugf("filtering repositories based on group path: %s", org.OrganizationFullPath) repoList, repoErr := s.v2GitRepoService.GitLabGetRepositoriesByNamePrefix(ctx, fmt.Sprintf("%s/", org.OrganizationFullPath)) if repoErr != nil { - if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { - log.WithFields(f).WithError(repoErr).Debugf("no GitLab repositories onboarded for project : %s", projectSFID) + if _, ok := repoErr.(*utils.GitLabRepositoryNotFound); ok { + log.WithFields(f).WithError(repoErr).Debugf("no GitLab repositories onboarded for group/organization : %s", org.OrganizationFullPath) } else { - log.WithFields(f).WithError(repoErr).Debugf("unexpected error while fetching GitLab group repositories for project: %s, error type: %T, error: %v", projectSFID, repoErr, repoErr) + log.WithFields(f).WithError(repoErr).Debugf("unexpected error while fetching GitLab group repositories for group/organization path: %s, error type: %T, error: %v", org.OrganizationFullPath, repoErr, repoErr) } } - rorg := &models.GitlabProjectOrganization{ + rorg := &v2Models.GitlabProjectOrganization{ AutoEnabled: org.AutoEnabled, AutoEnableClaGroupID: org.AutoEnabledClaGroupID, AutoEnabledClaGroupName: strings.TrimSpace(autoEnabledCLAGroupName), @@ -317,17 +376,17 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string OrganizationExternalID: org.OrganizationExternalID, InstallationURL: buildInstallationURL(org.OrganizationID, orgDetailed.AuthState), BranchProtectionEnabled: org.BranchProtectionEnabled, - ConnectionStatus: "", // updated below - Repositories: []*models.GitlabProjectRepository{}, // updated below + ConnectionStatus: "", // updated below + Repositories: []*v2Models.GitlabProjectRepository{}, // updated below } if orgDetailed.AuthInfo == "" { rorg.ConnectionStatus = utils.NoConnection } else { - if err != nil { - log.WithFields(f).Warnf("initializing gitlab client for gitlab org : %s failed : %v", org.OrganizationID, err) + if repoErr != nil { + log.WithFields(f).Warnf("initializing gitlab client for gitlab org: %s failed : %v", org.OrganizationID, repoErr) rorg.ConnectionStatus = utils.ConnectionFailure - rorg.ConnectionStatusMessage = err.Error() + rorg.ConnectionStatusMessage = repoErr.Error() } else { // We've been authenticated by the user - great, see if we can determine the list of repos... glClient, clientErr := gitlabApi.NewGitlabOauthClient(orgDetailed.AuthInfo, s.gitLabApp) @@ -352,24 +411,24 @@ func (s *Service) GetGitLabOrganizations(ctx context.Context, projectSFID string } orgMap[org.OrganizationName] = rorg - out.List = append(out.List, rorg) + response = append(response, rorg) } // Sort everything nicely - sort.Slice(out.List, func(i, j int) bool { - return strings.ToLower(out.List[i].OrganizationName) < strings.ToLower(out.List[j].OrganizationName) + sort.Slice(response, func(i, j int) bool { + return strings.ToLower(response[i].OrganizationName) < strings.ToLower(response[j].OrganizationName) }) - for _, projectOrganization := range out.List { + for _, projectOrganization := range response { sort.Slice(projectOrganization.Repositories, func(i, j int) bool { return strings.ToLower(projectOrganization.Repositories[i].RepositoryName) < strings.ToLower(projectOrganization.Repositories[j].RepositoryName) }) } - return out, nil + return response } // GetGitLabOrganizationByState returns the GitLab organization by the auth state -func (s *Service) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*models.GitlabOrganization, error) { +func (s *Service) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.GetGitLabOrganization", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -532,23 +591,23 @@ func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI return &installationURL } -func toGitLabProjectResponse(gitLabListRepos *models.GitlabRepositoriesList) []*models.GitlabProjectRepository { +func toGitLabProjectResponse(gitLabListRepos *v2Models.GitlabRepositoriesList) []*v2Models.GitlabProjectRepository { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.toGitLabProjectResponse", } if gitLabListRepos == nil { - return []*models.GitlabProjectRepository{} + return []*v2Models.GitlabProjectRepository{} } - var repoList []*models.GitlabProjectRepository + var repoList []*v2Models.GitlabProjectRepository for _, repo := range gitLabListRepos.List { parentProjectSFID, err := projectService.GetClient().GetParentProject(repo.RepositoryProjectSfid) if err != nil { log.WithFields(f).Warnf("unable to lookup project parent SFID using SFID: %s", repo.RepositoryProjectSfid) } - repoList = append(repoList, &models.GitlabProjectRepository{ + repoList = append(repoList, &v2Models.GitlabProjectRepository{ ClaGroupID: repo.RepositoryClaGroupID, //ConnectionStatus: "", // set via another function Enabled: repo.Enabled, @@ -566,13 +625,13 @@ func toGitLabProjectResponse(gitLabListRepos *models.GitlabRepositoriesList) []* } // updateRepositoryStatus is a helper function to set/add the repository connection status -func (s *Service) updateRepositoryStatus(glClient *goGitLab.Client, gitLabProjectRepos []*models.GitlabProjectRepository) []*models.GitlabProjectRepository { +func (s *Service) updateRepositoryStatus(glClient *goGitLab.Client, gitLabProjectRepos []*v2Models.GitlabProjectRepository) []*v2Models.GitlabProjectRepository { f := logrus.Fields{ "functionName": "v2.gitlab_organizations.service.updateRepositoryStatus", } if gitLabProjectRepos == nil { - return []*models.GitlabProjectRepository{} + return []*v2Models.GitlabProjectRepository{} } type responseChannelModel struct { @@ -586,7 +645,7 @@ func (s *Service) updateRepositoryStatus(glClient *goGitLab.Client, gitLabProjec opts := &goGitLab.GetProjectOptions{} for _, repo := range gitLabProjectRepos { // Create a go routine to this concurrently - go func(glClient *goGitLab.Client, repo *models.GitlabProjectRepository) { + go func(glClient *goGitLab.Client, repo *v2Models.GitlabProjectRepository) { projectModel, resp, lookupErr := glClient.Projects.GetProject(int(repo.RepositoryGitlabID), opts) // OK to convert int64 to int as it is the ID and probably should be typed as a int anyway if lookupErr != nil { log.WithFields(f).WithError(lookupErr).Warnf("problem loading GitLab project by external ID: %d, error: %v", repo.RepositoryGitlabID, lookupErr) diff --git a/cla-backend-go/v2/repositories/gitlab_services.go b/cla-backend-go/v2/repositories/gitlab_services.go index 9eaaf2378..dfaebce45 100644 --- a/cla-backend-go/v2/repositories/gitlab_services.go +++ b/cla-backend-go/v2/repositories/gitlab_services.go @@ -17,7 +17,7 @@ import ( v2GitLabOrg "github.com/communitybridge/easycla/cla-backend-go/v2/common" - gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + gitLabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/sirupsen/logrus" @@ -26,8 +26,13 @@ import ( repoModels "github.com/communitybridge/easycla/cla-backend-go/repositories" ) -// GitLabAddRepositories service function +// GitLabAddRepositories add a lst of GitLab repositories to the collection - default is not enabled/used/active by a CLA Group func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, input *GitLabAddRepoModel) (*v2Models.GitlabRepositoriesList, error) { + return s.GitLabAddRepositoriesWithEnabledFlag(ctx, projectSFID, input, false) +} + +// GitLabAddRepositoriesWithEnabledFlag add a lst of GitLab repositories to the collection +func (s *Service) GitLabAddRepositoriesWithEnabledFlag(ctx context.Context, projectSFID string, input *GitLabAddRepoModel, enabled bool) (*v2Models.GitlabRepositoriesList, error) { f := logrus.Fields{ "functionName": "v2.repositories.gitlab_services.GitLabAddRepositories", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -65,7 +70,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, log.WithFields(f).Debugf("successfully loaded GitLab group/organization") // Get the client - gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) + gitLabClient, err := gitLabApi.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) if err != nil { return nil, fmt.Errorf("initializing GitLab client : %v", err) } @@ -81,7 +86,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, for _, gitLabProjectID := range input.ProjectIDList { go func(gitLabProjectID int) { log.WithFields(f).Debugf("loading GitLab project from GitLab using projectID: %d...", gitLabProjectID) - project, getProjectErr := gitlab_api.GetProjectByID(ctx, gitLabClient, gitLabProjectID) + project, getProjectErr := gitLabApi.GetProjectByID(ctx, gitLabClient, gitLabProjectID) if getProjectErr != nil { newErr := fmt.Errorf("unable to load GitLab project using ID: %d, error: %v", gitLabProjectID, getProjectErr) log.WithFields(f).WithError(newErr) @@ -105,7 +110,7 @@ func (s *Service) GitLabAddRepositories(ctx context.Context, projectSFID string, RepositoryOrganizationName: input.GroupName, RepositoryCLAGroupID: input.ClaGroupID, RepositoryType: utils.GitLabLower, // should always be gitlab - Enabled: false, // we don't enable by default + Enabled: enabled, } repoModel, addErr := s.gitV2Repository.GitLabAddRepository(ctx, projectSFID, inputDBModel) @@ -172,7 +177,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel } // Get the client - gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) + gitLabClient, err := gitLabApi.NewGitlabOauthClient(gitLabOrgModel.AuthInfo, s.gitLabApp) if err != nil { return nil, fmt.Errorf("initializing gitlab client : %v", err) } @@ -184,7 +189,7 @@ func (s *Service) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel } // Query the project list by organization name - projectList, projectListErr := gitlab_api.GetGroupProjectListByGroupID(ctx, gitLabClient, gitLabOrgModel.ExternalGroupID) + projectList, projectListErr := gitLabApi.GetGroupProjectListByGroupID(ctx, gitLabClient, gitLabOrgModel.ExternalGroupID) if projectListErr != nil { return nil, projectListErr } @@ -399,6 +404,40 @@ func (s *Service) GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath return s.gitV2Repository.GitLabDeleteRepositories(ctx, gitLabGroupPath) } +// GitLabDeleteRepositoryByExternalID deletes the specified repository +func (s *Service) GitLabDeleteRepositoryByExternalID(ctx context.Context, gitLabExternalID int64) error { + // Load the record - needed for the event log after we delete + record, getErr := s.gitV2Repository.GitLabGetRepositoryByExternalID(ctx, gitLabExternalID) + if getErr != nil { + return getErr + } + + // Delete the record + err := s.gitV2Repository.GitLabDeleteRepositoryByExternalID(ctx, gitLabExternalID) + if err != nil { + return err + } + + // Convert the external ID value + repositoryExternalID, parseIntErr := strconv.ParseInt(record.RepositoryExternalID, 10, 64) + if err == nil { + return parseIntErr + } + + // Log the event + s.eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryDeleted, + ProjectSFID: record.ProjectSFID, + CLAGroupID: record.RepositoryCLAGroupID, + LfUsername: utils.GetUserNameFromContext(ctx), + EventData: &events.RepositoryDeletedEventData{ + RepositoryName: record.RepositoryFullPath, // give the full path/name + RepositoryExternalID: repositoryExternalID, + }, + }) + return err +} + // dbModelToGitLabRepository converts the database model to a v2 response model func dbModelToGitLabRepository(dbModel *repoModels.RepositoryDBModel) (*v2Models.GitlabRepository, error) { @@ -413,6 +452,7 @@ func dbModelToGitLabRepository(dbModel *repoModels.RepositoryDBModel) (*v2Models RepositoryClaGroupID: dbModel.RepositoryCLAGroupID, // CLA Group ID RepositoryExternalID: gitLabExternalID, // GitLab unique gitV1Repository ID RepositoryName: dbModel.RepositoryName, // Short repository name + RepositoryFullPath: dbModel.RepositoryFullPath, // Repository full path RepositoryOrganizationName: dbModel.RepositoryOrganizationName, // Group/Organization name RepositoryURL: dbModel.RepositoryURL, // full url RepositoryType: dbModel.RepositoryType, // gitlab diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index 4bc6f3848..27fa02048 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -38,6 +38,7 @@ type RepositoryInterface interface { GitLabEnrollRepositoryByID(ctx context.Context, claGroupID string, repositoryID int64, enrollValue bool) error GitLabEnableCLAGroupRepositories(ctx context.Context, claGroupID string, enrollValue bool) error GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error + GitLabDeleteRepositoryByExternalID(ctx context.Context, gitLabExternalID int64) error } // Repository object/struct @@ -457,6 +458,38 @@ func (r *Repository) GitLabDeleteRepositories(ctx context.Context, gitLabGroupPa return lastErr } +// GitLabDeleteRepositoryByExternalID deletes the specified repository +func (r *Repository) GitLabDeleteRepositoryByExternalID(ctx context.Context, gitLabExternalID int64) error { + f := logrus.Fields{ + "functionName": "v2.repositories.repository.GitLabDeleteRepositoryByExternalID", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabExternalID": gitLabExternalID, + } + + repositoryRecord, err := r.GitLabGetRepositoryByExternalID(ctx, gitLabExternalID) + if err != nil { + // If nothing to delete... + if _, ok := err.(*utils.GitLabRepositoryNotFound); ok { + return nil + } + log.WithFields(f).WithError(err).Warnf("problem loading existing repository by external ID: %d", gitLabExternalID) + return err + } + if repositoryRecord == nil { + return nil + } + + _, deleteErr := r.dynamoDBClient.DeleteItem(&dynamodb.DeleteItemInput{ + Key: map[string]*dynamodb.AttributeValue{ + repoModels.RepositoryIDColumn: {S: aws.String(repositoryRecord.RepositoryID)}, + }, + TableName: aws.String(r.repositoryTableName), + }) + + // Return the error + return deleteErr +} + // getRepositoryWithConditionFilter fetches the repository entry based on the specified condition and filter criteria using the provided index func (r *Repository) getRepositoryWithConditionFilter(ctx context.Context, condition expression.KeyConditionBuilder, filter expression.ConditionBuilder, indexName string) (*repoModels.RepositoryDBModel, error) { f := logrus.Fields{ diff --git a/cla-backend-go/v2/repositories/service.go b/cla-backend-go/v2/repositories/service.go index e2411433d..b19e3ac8d 100644 --- a/cla-backend-go/v2/repositories/service.go +++ b/cla-backend-go/v2/repositories/service.go @@ -15,7 +15,7 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/v2/common" "github.com/communitybridge/easycla/cla-backend-go/config" - gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + gitLabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/github/branch_protection" @@ -61,17 +61,19 @@ type ServiceInterface interface { GitLabGetRepositoriesByOrganizationName(ctx context.Context, orgName string) (*v2Models.GitlabRepositoriesList, error) GitLabGetRepositoriesByNamePrefix(ctx context.Context, repositoryNamePrefix string) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositories(ctx context.Context, projectSFID string, input *GitLabAddRepoModel) (*v2Models.GitlabRepositoriesList, error) + GitLabAddRepositoriesWithEnabledFlag(ctx context.Context, projectSFID string, input *GitLabAddRepoModel, enabled bool) (*v2Models.GitlabRepositoriesList, error) GitLabAddRepositoriesByApp(ctx context.Context, gitLabOrgModel *common.GitLabOrganization) ([]*v2Models.GitlabRepository, error) GitLabEnrollRepositories(ctx context.Context, claGroupID string, repositoryIDList []int64, enrollValue bool) error GitLabEnrollRepository(ctx context.Context, claGroupID string, repositoryExternalID int64, enrollValue bool) error GitLabEnrollCLAGroupRepositories(ctx context.Context, claGroupID string, enrollValue bool) error GitLabDeleteRepositories(ctx context.Context, gitLabGroupPath string) error + GitLabDeleteRepositoryByExternalID(ctx context.Context, gitLabExternalID int64) error } // GitLabOrgRepo redefine the interface here to avoid circular dependency issues type GitLabOrgRepo interface { AddGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) (*v2Models.GitlabOrganization, error) - GetGitLabOrganizations(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) + GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabOrganizations, error) GetGitLabOrganization(ctx context.Context, gitlabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) @@ -88,7 +90,7 @@ type Service struct { projectsClaGroupsRepo projects_cla_groups.Repository ghOrgRepo github_organizations.RepositoryInterface glOrgRepo GitLabOrgRepo - gitLabApp *gitlab_api.App + gitLabApp *gitLabApi.App eventService events.Service } @@ -107,7 +109,7 @@ func NewService(gitV1Repository *v1Repositories.Repository, gitV2Repository Repo ghOrgRepo: ghOrgRepo, glOrgRepo: glOrgRepo, eventService: eventService, - gitLabApp: gitlab_api.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), + gitLabApp: gitLabApi.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), } } diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index f3b6e1438..84ef282a0 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -15,6 +15,7 @@ package: - ./dynamo-events-lambda - ./zipbuilder-scheduler-lambda - ./zipbuilder-lambda + - ./gitlab-repository-check-lambda - ./functional-tests - dev.sh - docs/** @@ -534,6 +535,23 @@ functions: include: - ./zipbuilder-lambda + gitlab-repository-check-lambda: + handler: gitlab-repository-check-lambda + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-gitlab-repository-check-lambda + description: "routine to periodically check the GitLab repository list for auto-enabled GitLab Groups" + runtime: go1.x + timeout: 900 # maximum time allowed + memorySize: 1024 + events: + - schedule: + description: 'periodically check the GitLab repository list for auto-enabled GitLab Groups' + rate: rate(15 minutes) + enabled: true + package: + individually: true + include: + - ./gitlab-repository-check-lambda + apiv1: handler: wsgi_handler.handler description: "EasyCLA Python API handler for the /v1 endpoints" From 7cab2f8a1407c0560c7bc466ee08b7cf560ef8ba Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 27 Aug 2021 16:43:00 -0700 Subject: [PATCH 0470/1276] =?UTF-8?q?Resolved=20Build=20and=20Deployment?= =?UTF-8?q?=20Issues=20for=20GitLab=20Repository=20Checker=20La=E2=80=A6?= =?UTF-8?q?=20(#3210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cla-backend-go/Makefile | 2 +- .../handler/handler.go | 82 ++++++++++++++----- cla-backend-go/github/init.go | 2 +- cla-backend-go/gitlab_api/client_groups.go | 4 + cla-backend-go/v2/common/models.go | 1 + .../v2/gitlab_organizations/repository.go | 8 +- 6 files changed, 73 insertions(+), 26 deletions(-) diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 439414290..700d174d9 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -301,7 +301,7 @@ build-zipbuilder-lambda-mac: deps build-gitlab-repository-check-lambda-linux: deps @echo "Building a statically linked Linux OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(GITLAB_REPO_CHECK_BIN) cmd/gitlab_repository_check/main.go + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(GITLAB_REPO_CHECK_BIN) cmd/gitlab_repository_check/main.go @chmod +x $(GITLAB_REPO_CHECK_BIN) build-gitlab-repository-check-lambda-mac: deps diff --git a/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go b/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go index ca8aabdab..718c8341d 100644 --- a/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go +++ b/cla-backend-go/cmd/gitlab_repository_check/handler/handler.go @@ -5,12 +5,16 @@ package handler import ( "context" + "os" "strconv" + "github.com/communitybridge/easycla/cla-backend-go/config" + + "github.com/aws/aws-sdk-go/aws/session" + v1Company "github.com/communitybridge/easycla/cla-backend-go/company" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gerrits" - "github.com/communitybridge/easycla/cla-backend-go/github" "github.com/communitybridge/easycla/cla-backend-go/github_organizations" gitLabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" @@ -30,6 +34,13 @@ import ( goGitLab "github.com/xanzy/go-gitlab" ) +var ( + awsSession *session.Session + stage string + configFile config.Config + gitLabApp *gitLabApi.App +) + // Init initializes the handler func Init() { f := logrus.Fields{ @@ -41,7 +52,43 @@ func Init() { // General initialization ini.Init() - ini.GetConfig() + + var awsErr error + awsSession, awsErr = ini.GetAWSSession() + if awsErr != nil { + log.WithFields(f).WithError(awsErr).Panic("unable to load AWS session") + } + + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage = os.Getenv("STAGE") + if stage == "" { + log.WithFields(f).Panic("unable to determine STAGE - please set in the environment variable: 'STAGE' - expected one of [DEV, STAGING, PROD]") + } + + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + log.WithFields(f).Panic("unable to determine DYNAMODB_AWS_REGION - please set in the environment variable: 'DYNAMODB_AWS_REGION'") + } + + var configErr error + configFile, configErr = config.LoadConfig("", awsSession, stage) + if configErr != nil { + log.WithFields(f).WithError(configErr).Panicf("Unable to load config - Error: %v", configErr) + } + + if configFile.Gitlab.AppClientID == "" { + log.WithFields(f).Panic("unable to determine configFile.Gitlab.AppClientID value - please set the configuration") + } + if configFile.Gitlab.AppClientSecret == "" { + log.WithFields(f).Panic("unable to determine configFile.Gitlab.AppClientSecret value - please set the configuration") + } + if configFile.Gitlab.AppPrivateKey == "" { + log.WithFields(f).Panic("unable to determine configFile.Gitlab.AppPrivateKey value - please set the configuration") + } + + // Create a new GitLab App client instance + gitLabApp = gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) + } // Handler is invoked each time the lambda is triggered - https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html @@ -53,20 +100,6 @@ func Handler(ctx context.Context) error { // Add the x-request-id to the context ctx = utils.NewContextFromParent(ctx) f[utils.XREQUESTID] = ctx.Value(utils.XREQUESTID) - stage := strings.ToLower(utils.GetProperty("STAGE")) - f["stage"] = stage - - awsSession, err := ini.GetAWSSession() - if err != nil { - log.WithFields(f).WithError(err).Panic("Unable to load AWS session") - } - - configFile := ini.GetConfig() - - // initialize GitHub - github.Init(configFile.GitHub.AppID, configFile.GitHub.AppPrivateKey, configFile.GitHub.AccessToken) - // initialize GitLab - gitLabApp := gitlab.Init(configFile.Gitlab.AppClientID, configFile.Gitlab.AppClientSecret, configFile.Gitlab.AppPrivateKey) // Repository Layer usersRepo := users.NewRepository(awsSession, stage) @@ -118,20 +151,27 @@ func Handler(ctx context.Context) error { log.WithFields(f).Debugf("start - checking %d GitLab projects for add/delete events", len(gitLabGroups.List)) for _, gitLabGroup := range gitLabGroups.List { + log.WithFields(f).Debugf("start - processing GitLab group/organization: %s with group ID: %d associated with project SFID: %s", gitLabGroup.OrganizationURL, gitLabGroup.OrganizationExternalID, gitLabGroup.ProjectSfid) + + if gitLabGroup.AuthInfo == "" { + log.WithFields(f).Debugf("GitLab group/organization: %s not fully onboarded - missing authentication info - skipping", gitLabGroup.OrganizationURL) + continue + } + gitLabClient, gitLabClientErr := gitLabApi.NewGitlabOauthClient(gitLabGroup.AuthInfo, gitLabApp) if gitLabClientErr != nil { - log.WithFields(f).WithError(gitLabClientErr).Warnf("problem loading GitLab client for group/organization: %s", gitLabGroup.OrganizationURL) - return gitLabClientErr + log.WithFields(f).WithError(gitLabClientErr).Warnf("problem loading GitLab client for group/organization: %s - skipping", gitLabGroup.OrganizationURL) + continue } gitLabProjects, getGitLabAPIError := gitLabApi.GetGroupProjectListByGroupID(ctx, gitLabClient, int(gitLabGroup.OrganizationExternalID)) if getGitLabAPIError != nil { - log.WithFields(f).WithError(getGitLabAPIError).Warnf("problem loading GitLab projects for group/organization: %s using the groupID: %d - skipping GitLab Group/Organziation", gitLabGroup.OrganizationFullPath, gitLabGroup.OrganizationExternalID) + log.WithFields(f).WithError(getGitLabAPIError).Warnf("problem loading GitLab projects for group/organization: %s using the groupID: %d - skipping GitLab Group/Organziation - skipping", gitLabGroup.OrganizationFullPath, gitLabGroup.OrganizationExternalID) continue } gitLabDBProjects, getProjectListDBErr := gitV2Repository.GitLabGetRepositoriesByNamePrefix(ctx, gitLabGroup.OrganizationFullPath) if getProjectListDBErr != nil { - log.WithFields(f).WithError(getProjectListDBErr).Warnf("problem loading GitLab projects for group/organization: %s from the database - skipping GitLab Group/Organziation", gitLabGroup.OrganizationFullPath) + log.WithFields(f).WithError(getProjectListDBErr).Warnf("problem loading GitLab projects for group/organization: %s from the database - skipping GitLab Group/Organziation - skipping", gitLabGroup.OrganizationFullPath) continue } @@ -177,6 +217,8 @@ func Handler(ctx context.Context) error { } } } + + log.WithFields(f).Debugf("done - processed GitLab group/organization: %s with group ID: %d associated with project SFID: %s", gitLabGroup.OrganizationURL, gitLabGroup.OrganizationExternalID, gitLabGroup.ProjectSfid) } log.WithFields(f).Debugf("done - checked %d GitLab projects for add/delete events", len(gitLabGroups.List)) diff --git a/cla-backend-go/github/init.go b/cla-backend-go/github/init.go index 4dd5a5d0a..3e59e4c9a 100644 --- a/cla-backend-go/github/init.go +++ b/cla-backend-go/github/init.go @@ -7,7 +7,7 @@ var githubAppPrivateKey string var githubAppID int var secretAccessToken string -// Init initializes the required github variables +// Init initializes the required GitHub variables func Init(ghAppID int, ghAppPrivateKey string, secAccessToken string) { githubAppPrivateKey = ghAppPrivateKey githubAppID = ghAppID diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go index 196b95531..533c29edc 100644 --- a/cla-backend-go/gitlab_api/client_groups.go +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -152,6 +152,10 @@ func GetGroupProjectListByGroupID(ctx context.Context, client *goGitLab.Client, "functionName": "gitlab_api.client_groups.GetGroupProjectListByGroupID", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), } + if groupID == 0 { + return nil, errors.New("invalid groupID value - 0") + } + opts := &goGitLab.ListGroupProjectsOptions{ ListOptions: goGitLab.ListOptions{ Page: 1, // starts with one: https://docs.gitlab.com/ee/api/#offset-based-pagination diff --git a/cla-backend-go/v2/common/models.go b/cla-backend-go/v2/common/models.go index b2fe363e7..d7cec44ab 100644 --- a/cla-backend-go/v2/common/models.go +++ b/cla-backend-go/v2/common/models.go @@ -32,6 +32,7 @@ type GitLabOrganization struct { // ToModel converts to models.GitlabOrganization func ToModel(in *GitLabOrganization) *models2.GitlabOrganization { return &models2.GitlabOrganization{ + AuthInfo: in.AuthInfo, OrganizationID: in.OrganizationID, DateCreated: in.DateCreated, DateModified: in.DateModified, diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index d8ca8abf8..ca2aa9749 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -827,7 +827,7 @@ func (repo *Repository) getScanResults(ctx context.Context, filter *expression.C // Add the filter if provided if filter != nil { - builder.WithFilter(*filter) + builder = builder.WithFilter(*filter) } // Build the scan/query expression @@ -860,12 +860,12 @@ func (repo *Repository) getScanResults(ctx context.Context, filter *expression.C } } - var gitlabOrganizations *v2Models.GitlabOrganizations - unmarshalErr := dynamodbattribute.UnmarshalListOfMaps(resultList, &gitlabOrganizations) + var resultOutput []*common.GitLabOrganization + unmarshalErr := dynamodbattribute.UnmarshalListOfMaps(resultList, &resultOutput) if unmarshalErr != nil { log.Warnf("error unmarshalling %s from database. error: %v", repo.gitlabOrgTableName, unmarshalErr) return nil, unmarshalErr } - return gitlabOrganizations, nil + return &v2Models.GitlabOrganizations{List: common.ToModels(resultOutput)}, nil } From 89b79f38638865de9b53583d7715ab9124ac2c5d Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 30 Aug 2021 13:12:12 +0300 Subject: [PATCH 0471/1276] Bug/Individual Signature - Resolved issue raised by gitlab flow for individual signature Signed-off-by: Harold Wanyama --- cla-backend/cla/controllers/signing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index 78a163239..8dc40dc17 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -36,12 +36,12 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur signing_service = get_signing_service() if return_url_type is not None and return_url_type.lower() == "gerrit": return signing_service.request_individual_signature_gerrit(str(project_id), str(user_id), return_url) - elif return_url_type is not None and (return_url_type.lower() == "github" or return_url_type.lower == "gitlab"): + elif return_url_type is not None and (return_url_type.lower() == "github" or return_url_type.lower() == "gitlab"): if return_url_type == "github": # fetching the primary for the account github = get_repository_service("github") primary_user_email = github.get_primary_user_email(request) - elif return_url_type == "gitlab": + elif return_url_type.lower() == "gitlab": try: cla.log.debug(f"Fetching user details for: {user_id}") user = User() From 58161b816c9184bf71241b63fa0bc79dad8c0c8e Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 30 Aug 2021 09:09:48 -0700 Subject: [PATCH 0472/1276] Bug/Gitlab ICLA Sign (#3214) --- cla-backend/cla/models/docusign_models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 5194b1be9..04ee14fcf 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -252,9 +252,9 @@ def request_individual_signature(self, project_id, user_id, return_url=None, ret signature_callback_url=callback_url) # Set signature ACL - if return_url_type == "github": + if return_url_type.lower() == "github": acl = user.get_user_github_id() - elif return_url_type == "gitlab": + elif return_url_type.lower() == "gitlab": acl = user.get_user_github_id() cla.log.debug('Individual Signature - setting ACL using user {} id: {}'.format(return_url_type, acl)) signature.set_signature_acl('github:{}'.format(acl)) @@ -705,9 +705,9 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url cla.log.info(f'{fn} - created new signature document for: {request_info} - signature: {new_signature}') # Set signature ACL - if return_url_type == "github": + if return_url_type.lower() == "github": acl_value = f'github:{user.get_user_github_id()}' - elif return_url_type == "gitlab": + elif return_url_type.lower() == "gitlab": acl_value = f'gitlab:{user.get_user_gitlab_id()}' cla.log.info(f'{fn} - assigning signature acl with value: {acl_value} for: {request_info}') new_signature.set_signature_acl(acl_value) From 472d3fb32471f647236369f212ce13f9b1d53a95 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 30 Aug 2021 16:14:37 -0700 Subject: [PATCH 0473/1276] Bug/GitLab ICLA (#3216) --- cla-backend/cla/models/docusign_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 04ee14fcf..38a40605f 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -180,9 +180,9 @@ def request_individual_signature(self, project_id, user_id, return_url=None, ret cla.log.debug('Individual Signature - get active signature metadata: {}'.format(signature_metadata)) cla.log.debug('Individual Signature - get individual signature callback url') - if return_url_type == "github": + if return_url_type.lower() == "github": callback_url = cla.utils.get_individual_signature_callback_url(user_id, signature_metadata) - elif return_url_type == "gitlab": + elif return_url_type.lower() == "gitlab": callback_url = cla.utils.get_individual_signature_callback_url_gitlab(user_id, signature_metadata) cla.log.debug('Individual Signature - get individual signature callback url: {}'.format(callback_url)) From 0d6ad209855d49f080deffb7b6f28174ea5a3fa1 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 31 Aug 2021 08:51:18 -0700 Subject: [PATCH 0474/1276] Bug/Gitlab-ICLA Sign (#3218) --- cla-backend-go/tests/github_v4_test.go | 74 ++--- .../v2/gitlab-activity/service_test.go | 304 +++++++++--------- cla-backend-go/v2/gitlab_sign/service.go | 1 + cla-backend/cla/models/docusign_models.py | 2 +- cla-backend/cla/models/dynamo_models.py | 6 + cla-backend/cla/utils.py | 5 +- 6 files changed, 204 insertions(+), 188 deletions(-) diff --git a/cla-backend-go/tests/github_v4_test.go b/cla-backend-go/tests/github_v4_test.go index 29eba9348..217475a66 100644 --- a/cla-backend-go/tests/github_v4_test.go +++ b/cla-backend-go/tests/github_v4_test.go @@ -20,42 +20,44 @@ import ( ) func TestGetRepositoryIDFromName(t *testing.T) { - - ctx := utils.NewContext() - - // Need to initialize the system to load the configuration which contains a number of SSM parameters - stage := os.Getenv("STAGE") - if stage == "" { - assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") - } - dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") - if dynamodbRegion == "" { - assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") - } - - viper.Set("STAGE", stage) - viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) - ini.Init() - _, err := ini.GetAWSSession() - if err != nil { - assert.Fail(t, "unable to load AWS session", err) - } - ini.ConfigVariable() - config := ini.GetConfig() - github.Init(config.GitHub.AppID, config.GitHub.AppPrivateKey, config.GitHub.AccessToken) - installationID, int64Err := strconv.ParseInt(config.GitHub.TestOrganizationInstallationID, 10, 64) - if int64Err != nil { - assert.Fail(t, fmt.Sprintf("unable to convert installation ID to string: %s", config.GitHub.TestOrganizationInstallationID), int64Err) + if enabled { + ctx := utils.NewContext() + + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + _, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + github.Init(config.GitHub.AppID, config.GitHub.AppPrivateKey, config.GitHub.AccessToken) + installationID, int64Err := strconv.ParseInt(config.GitHub.TestOrganizationInstallationID, 10, 64) + if int64Err != nil { + assert.Fail(t, fmt.Sprintf("unable to convert installation ID to string: %s", config.GitHub.TestOrganizationInstallationID), int64Err) + } + + branchProtectionRepoV4, err := branch_protection.NewBranchProtectionRepositoryV4(installationID) + if err != nil { + assert.Fail(t, fmt.Sprintf("initializing branch protection v4 repo failed : %v", err)) + } + expectedValue := config.GitHub.TestRepositoryID + actualValue, err := branchProtectionRepoV4.GetRepositoryIDFromName(ctx, config.GitHub.TestOrganization, config.GitHub.TestRepository) + if err != nil { + assert.Fail(t, fmt.Sprintf("unable to create GitHub v4 client from installation ID: %d", installationID), err) + } + assert.Equal(t, expectedValue, actualValue, "CombinedRepository ID Lookup") } - branchProtectionRepoV4, err := branch_protection.NewBranchProtectionRepositoryV4(installationID) - if err != nil { - assert.Fail(t, fmt.Sprintf("initializing branch protection v4 repo failed : %v", err)) - } - expectedValue := config.GitHub.TestRepositoryID - actualValue, err := branchProtectionRepoV4.GetRepositoryIDFromName(ctx, config.GitHub.TestOrganization, config.GitHub.TestRepository) - if err != nil { - assert.Fail(t, fmt.Sprintf("unable to create GitHub v4 client from installation ID: %d", installationID), err) - } - assert.Equal(t, expectedValue, actualValue, "CombinedRepository ID Lookup") } diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index 14c98dfef..02e37bfe1 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -14,182 +14,188 @@ import ( "github.com/xanzy/go-gitlab" ) -func TestIsUserApprovedForSignature(t *testing.T) { - userModel := &models.User{ - Emails: []string{ - "one@example.com", - "two@bar.com", - }, - } - gitlabUser := &gitlab.User{ - Username: "one", - } +const enabled = false //nolint - testCases := []struct { - name string - signature *models.Signature - expected bool - }{ - { - name: "nothing matched", - signature: &models.Signature{}, - }, - { - name: "email approval list non empty no match", - signature: &models.Signature{ - EmailApprovalList: []string{"three@example.com"}, +func TestIsUserApprovedForSignature(t *testing.T) { + if enabled { + userModel := &models.User{ + Emails: []string{ + "one@example.com", + "two@bar.com", }, - }, - { - name: "email approval list match", - signature: &models.Signature{ - EmailApprovalList: []string{"one@example.com"}, + } + gitlabUser := &gitlab.User{ + Username: "one", + } + + testCases := []struct { + name string + signature *models.Signature + expected bool + }{ + { + name: "nothing matched", + signature: &models.Signature{}, }, - expected: true, - }, - { - name: "domain approval list match no match", - signature: &models.Signature{ - DomainApprovalList: []string{"*.foo.com"}, + { + name: "email approval list non empty no match", + signature: &models.Signature{ + EmailApprovalList: []string{"three@example.com"}, + }, }, - expected: false, - }, - { - name: "domain approval list match domain star", - signature: &models.Signature{ - DomainApprovalList: []string{"*.example.com"}, + { + name: "email approval list match", + signature: &models.Signature{ + EmailApprovalList: []string{"one@example.com"}, + }, + expected: true, }, - expected: true, - }, - { - name: "domain approval list match domain star globbing", - signature: &models.Signature{ - DomainApprovalList: []string{"*example.com"}, + { + name: "domain approval list match no match", + signature: &models.Signature{ + DomainApprovalList: []string{"*.foo.com"}, + }, + expected: false, }, - expected: true, - }, - { - name: "domain approval list match domain star dot", - signature: &models.Signature{ - DomainApprovalList: []string{".example.com"}, + { + name: "domain approval list match domain star", + signature: &models.Signature{ + DomainApprovalList: []string{"*.example.com"}, + }, + expected: true, }, - expected: true, - }, - { - name: "gitlab username approval list no match", - signature: &models.Signature{ - GitlabUsernameApprovalList: []string{"two"}, + { + name: "domain approval list match domain star globbing", + signature: &models.Signature{ + DomainApprovalList: []string{"*example.com"}, + }, + expected: true, }, - expected: false, - }, - { - name: "gitlab username approval list match", - signature: &models.Signature{ - GitlabUsernameApprovalList: []string{"one"}, + { + name: "domain approval list match domain star dot", + signature: &models.Signature{ + DomainApprovalList: []string{".example.com"}, + }, + expected: true, }, - expected: true, - }, - } + { + name: "gitlab username approval list no match", + signature: &models.Signature{ + GitlabUsernameApprovalList: []string{"two"}, + }, + expected: false, + }, + { + name: "gitlab username approval list match", + signature: &models.Signature{ + GitlabUsernameApprovalList: []string{"one"}, + }, + expected: true, + }, + } - for _, tc := range testCases { - t.Run(tc.name, func(tt *testing.T) { - result := IsUserApprovedForSignature(logrus.Fields{}, tc.signature, userModel, gitlabUser) - if tc.expected { - assert.True(tt, result) + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + result := IsUserApprovedForSignature(logrus.Fields{}, tc.signature, userModel, gitlabUser) + if tc.expected { + assert.True(tt, result) - } else { - assert.False(tt, result) - } - }) + } else { + assert.False(tt, result) + } + }) + } } } func TestPrepareMrCommentContent(t *testing.T) { - - signedContains := ":white_check_mark: %s" - missingUserContains := ":x: The commit associated with %s is missing the User's ID" - missingAffiliationContains := "%s is authorized, but they must confirm their affiliation" - missingApprovalContains := "%s's commit is not authorized under a signed CLA" - - testCases := []struct { - name string - signed []*gitlab.User - missing []*gatedGitlabUser - expectedMsgs []string - expectedBadge string - }{ - { - name: "all signed", - signed: []*gitlab.User{ - {ID: 1, Username: "neo"}, - {ID: 2, Username: "oracle"}, - }, - expectedMsgs: []string{signedContains, signedContains}, - expectedBadge: "cla-signed.svg", - }, - { - name: "missing id", - signed: []*gitlab.User{ - {ID: 1, Username: "neo"}, - }, - missing: []*gatedGitlabUser{ - {err: missingID, User: &gitlab.User{ID: 3, Username: "missing"}}, - }, - expectedMsgs: []string{signedContains, missingUserContains}, - expectedBadge: "cla-missing-id.svg", - }, - { - name: "missing affiliation", - signed: []*gitlab.User{ - {ID: 1, Username: "neo"}, + if enabled { + signedContains := ":white_check_mark: %s" + missingUserContains := ":x: The commit associated with %s is missing the User's ID" + missingAffiliationContains := "%s is authorized, but they must confirm their affiliation" + missingApprovalContains := "%s's commit is not authorized under a signed CLA" + + testCases := []struct { + name string + signed []*gitlab.User + missing []*gatedGitlabUser + expectedMsgs []string + expectedBadge string + }{ + { + name: "all signed", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + {ID: 2, Username: "oracle"}, + }, + expectedMsgs: []string{signedContains, signedContains}, + expectedBadge: "cla-signed.svg", }, - missing: []*gatedGitlabUser{ - {err: missingCompanyAffiliation, User: &gitlab.User{ID: 4, Username: "affiliationUser"}}, + { + name: "missing id", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + }, + missing: []*gatedGitlabUser{ + {err: missingID, User: &gitlab.User{ID: 3, Username: "missing"}}, + }, + expectedMsgs: []string{signedContains, missingUserContains}, + expectedBadge: "cla-missing-id.svg", }, - expectedMsgs: []string{signedContains, missingAffiliationContains}, - expectedBadge: "cla-confirmation-needed.svg", - }, - { - name: "missing approval", - signed: []*gitlab.User{ - {ID: 1, Username: "neo"}, + { + name: "missing affiliation", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + }, + missing: []*gatedGitlabUser{ + {err: missingCompanyAffiliation, User: &gitlab.User{ID: 4, Username: "affiliationUser"}}, + }, + expectedMsgs: []string{signedContains, missingAffiliationContains}, + expectedBadge: "cla-confirmation-needed.svg", }, - missing: []*gatedGitlabUser{ - {err: missingCompanyApproval, User: &gitlab.User{ID: 5, Username: "approvalUser"}}, + { + name: "missing approval", + signed: []*gitlab.User{ + {ID: 1, Username: "neo"}, + }, + missing: []*gatedGitlabUser{ + {err: missingCompanyApproval, User: &gitlab.User{ID: 5, Username: "approvalUser"}}, + }, + expectedMsgs: []string{signedContains, missingApprovalContains}, + expectedBadge: "cla-not-signed.svg", }, - expectedMsgs: []string{signedContains, missingApprovalContains}, - expectedBadge: "cla-not-signed.svg", - }, - } + } - for _, tc := range testCases { - t.Run(tc.name, func(tt *testing.T) { - result := PrepareMrCommentContent(tc.missing, tc.signed, "https://sign.com") - tt.Logf("the result is : %s", result) - parts := strings.Split(result, "
  • ") - assert.Len(tt, parts, len(tc.expectedMsgs)+1) + for _, tc := range testCases { + t.Run(tc.name, func(tt *testing.T) { + result := PrepareMrCommentContent(tc.missing, tc.signed, "https://sign.com") + tt.Logf("the result is : %s", result) + parts := strings.Split(result, "
  • ") + assert.Len(tt, parts, len(tc.expectedMsgs)+1) - var allUsers []*gitlab.User + var allUsers []*gitlab.User - if len(tc.signed) > 0 { - for _, s := range tc.signed { - allUsers = append(allUsers, s) + if len(tc.signed) > 0 { + for _, s := range tc.signed { + allUsers = append(allUsers, s) + } } - } - if len(tc.missing) > 0 { - for _, m := range tc.missing { - allUsers = append(allUsers, m.User) + if len(tc.missing) > 0 { + for _, m := range tc.missing { + allUsers = append(allUsers, m.User) + } } - } - for i, p := range parts[1:] { - expected := fmt.Sprintf(tc.expectedMsgs[i], getAuthorInfo(allUsers[i])) - assert.Contains(tt, p, expected) - } + for i, p := range parts[1:] { + expected := fmt.Sprintf(tc.expectedMsgs[i], getAuthorInfo(allUsers[i])) + assert.Contains(tt, p, expected) + } - assert.Contains(tt, result, tc.expectedBadge) - }) + assert.Contains(tt, result, tc.expectedBadge) + }) + } } + } diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go index 61d79e34b..cb6835108 100644 --- a/cla-backend-go/v2/gitlab_sign/service.go +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -195,6 +195,7 @@ func (s service) getOrCreateUser(ctx context.Context, gitlabClient *gitlab.Clien GitlabID: fmt.Sprintf("%d", gitlabUser.ID), GitlabUsername: gitlabUser.Username, Emails: []string{gitlabUser.Email}, + Username: gitlabUser.Name, } claUser, userErr := s.userService.CreateUser(user, nil) if err != nil { diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 38a40605f..4197c702a 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -245,7 +245,7 @@ def request_individual_signature(self, project_id, user_id, return_url=None, ret signature_reference_type='user', signature_reference_name=user.get_user_name(), signature_type='cla', - signature_return_url_type='Github', + signature_return_url_type=return_url_type, signature_signed=False, signature_approved=True, signature_return_url=return_url, diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 21d5a69e1..b1aaf9223 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -3973,6 +3973,12 @@ def get_organization_by_sfid(self, sfid) -> List: org.model = org_model organizations.append(org) return organizations + + def search_organization_by_lower_name(self, organization_name): + organizations = list(filter(lambda org: org.get_organization_name_lower() == organization_name, self.all())) + if organizations: + return organizations[0] + raise cla.models.DoesNotExist("Gitlab Org does not exist") def get_organization_by_lower_name(self, organization_name): organization_name = organization_name.lower() diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 8b4fd5074..fe15b6610 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1246,10 +1246,11 @@ def get_organization_id_from_gitlab_repository(gitlab_repository_id): except DoesNotExist: return None # Get GitLabGroup from this repository - gitLabOrg = GitlabOrg() + gitLabOrg = None try: - gitLabOrg.load(repository.get_repository_organization_name()) + gitLabOrg = GitlabOrg().search_organization_by_lower_name(repository.get_repository_organization_name().lower()) except DoesNotExist: + cla.log.debug(f"unable to get gitlab org by name: {repository.get_repository_organization_name()}") return None #return GitLab organization ID From 787bbb001e84992c192e25a30a6596480b17394c Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:28:45 -0700 Subject: [PATCH 0475/1276] Bug/ POST Gitlab (#3219) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index ebb38e675..12759ada2 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -111,7 +111,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic // Load the project parent parentProjectModel, err := psc.GetParentProjectModel(params.ProjectSFID) - if err != nil || parentProjectModel == nil { + if err != nil || (parentProjectModel == nil && !utils.IsProjectHasRootParent(projectModel)) { return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate parent project from project with ID: %s", params.ProjectSFID))) } @@ -178,7 +178,10 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // If the parent is TLF, then use the same project SFID value for the parent SFID value - parentProjectSFID := parentProjectModel.ID + parentProjectSFID := "" + if parentProjectModel != nil { + parentProjectSFID = parentProjectModel.ID + } if utils.IsProjectHasRootParent(projectModel) { parentProjectSFID = params.ProjectSFID } From 7a21fdfdedc0ebaf54a1246f35238ec7aaf5bec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:56:44 -0700 Subject: [PATCH 0476/1276] Bump tar from 4.4.15 to 4.4.19 in /cla-frontend-contributor-console (#3220) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 3a75259b0..d122325ef 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -1481,7 +1481,7 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.0.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -2791,7 +2791,7 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.5: +fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== @@ -4260,7 +4260,7 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: +minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -4275,7 +4275,7 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.2.1: +minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== @@ -4306,7 +4306,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5416,7 +5416,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6124,17 +6124,17 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^4.0.2: - version "4.4.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" - integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" tar@^6.0.5: version "6.0.5" @@ -6672,7 +6672,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From e229702fc505b616e6c6f4312c9b502c3fe3b0bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 11:22:56 -0700 Subject: [PATCH 0477/1276] Bump tar from 6.1.3 to 6.1.11 in /cla-backend (#3222) Bumps [tar](https://github.com/npm/node-tar) from 6.1.3 to 6.1.11. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.3...v6.1.11) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index ea5cc2a06..c7e1bab72 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -5864,9 +5864,9 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.1.0: - version "6.1.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.3.tgz#e44b97ee7d6cc7a4c574e8b01174614538291825" - integrity sha512-3rUqwucgVZXTeyJyL2jqtUau8/8r54SioM1xj3AmTX3HnWQdj2AydfJ2qYYayPyIIznSplcvU9mhBb7dR2XF3w== + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From 511d4d4f16e491fe68fe2247766c6fd88f440afc Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 31 Aug 2021 12:27:34 -0700 Subject: [PATCH 0478/1276] Added go 1.17 support (#3224) - updated CI/CD docker build image - updated tags to support go 1.17 - go mod tidy for go 1.17 Signed-off-by: David Deal --- .circleci/config.yml | 6 +- .../handler/server_aws_lambda.go | 1 + .../handler/server_standalone.go | 1 + cla-backend-go/cmd/server_aws_lambda.go | 1 + cla-backend-go/cmd/server_standalone.go | 1 + cla-backend-go/go.mod | 49 +++++- cla-backend-go/go.sum | 149 ------------------ .../userSubscribeLambda/cmd/serve_lambda.go | 1 + .../userSubscribeLambda/cmd/serve_local.go | 1 + .../utils/utils_user_auth_lambda.go | 1 + .../utils/utils_user_auth_standalone.go | 1 + 11 files changed, 58 insertions(+), 154 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 72af5e1cf..b82fd1992 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -198,7 +198,7 @@ jobs: buildGoBackend: &buildGoBackendAnchor docker: - - image: circleci/golang:1.16.5 + - image: circleci/golang:1.17 working_directory: /go/src/github.com/communitybridge/easycla/ steps: - checkout @@ -217,7 +217,7 @@ jobs: sudo apt-get update sudo apt install -y software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa -y - sudo apt-get install -y python3.7 python3-pip + sudo apt-get install -y python3 python3-pip echo "Upgrading pip..." python3 -m pip install --upgrade pip echo "Python 3 version:" @@ -879,7 +879,7 @@ jobs: functionalTestsGo: &functionalTestsGo docker: - - image: circleci/golang:1.16.5 + - image: circleci/golang:1.17 steps: - attach_workspace: at: ~/ diff --git a/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go b/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go index 2300ca676..01eb964bb 100644 --- a/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go +++ b/cla-backend-go/cmd/gitlab_repository_check/handler/server_aws_lambda.go @@ -1,3 +1,4 @@ +//go:build aws_lambda // +build aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go b/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go index 1141d2942..4cf227191 100644 --- a/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go +++ b/cla-backend-go/cmd/gitlab_repository_check/handler/server_standalone.go @@ -1,3 +1,4 @@ +//go:build !aws_lambda // +build !aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/cmd/server_aws_lambda.go b/cla-backend-go/cmd/server_aws_lambda.go index 65a0dea80..2e473f0c2 100644 --- a/cla-backend-go/cmd/server_aws_lambda.go +++ b/cla-backend-go/cmd/server_aws_lambda.go @@ -1,3 +1,4 @@ +//go:build aws_lambda // +build aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/cmd/server_standalone.go b/cla-backend-go/cmd/server_standalone.go index fe2e193a1..6d7840781 100644 --- a/cla-backend-go/cmd/server_standalone.go +++ b/cla-backend-go/cmd/server_standalone.go @@ -1,3 +1,4 @@ +//go:build !aws_lambda // +build !aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index c737d1c57..3f438b245 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT module github.com/communitybridge/easycla/cla-backend-go -go 1.16 +go 1.17 replace github.com/awslabs/aws-lambda-go-api-proxy => github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 @@ -56,7 +56,6 @@ require ( github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 - github.com/ugorji/go v1.2.6 // indirect github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a github.com/xanzy/go-gitlab v0.50.1 go.uber.org/ratelimit v0.1.0 @@ -69,3 +68,49 @@ require ( golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/protobuf v1.27.1 // indirect ) + +require ( + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/analysis v0.19.10 // indirect + github.com/go-openapi/jsonpointer v0.19.3 // indirect + github.com/go-openapi/jsonreference v0.19.3 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-github/v29 v29.0.2 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.1 // indirect + github.com/hashicorp/go-retryablehttp v0.6.8 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 // indirect + github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mailru/easyjson v0.7.1 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/ugorji/go/codec v1.2.6 // indirect + go.mongodb.org/mongo-driver v1.3.4 // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/appengine v1.6.7 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 944fa3c75..7a0dc864a 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -17,38 +17,28 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -56,23 +46,17 @@ github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJ github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -86,46 +70,32 @@ github.com/aws/aws-sdk-go v1.36.27 h1:wc3xLJJHog2SwiqlLnrLUuct/n+dBjB45QhuZw2psV github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -137,17 +107,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -156,14 +122,10 @@ github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2 github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -244,55 +206,38 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -322,10 +267,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -346,12 +289,9 @@ github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3 github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -363,11 +303,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -376,61 +313,41 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -438,7 +355,6 @@ github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= @@ -448,7 +364,6 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -456,14 +371,12 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -471,35 +384,25 @@ github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2h github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= -github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -514,12 +417,9 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -528,17 +428,11 @@ github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1y github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -551,21 +445,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= @@ -577,37 +467,27 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= -github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -634,7 +514,6 @@ github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -656,33 +535,23 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -695,15 +564,12 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= -go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -729,7 +595,6 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -748,10 +613,8 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -761,7 +624,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -890,7 +752,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -965,7 +826,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -993,7 +853,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1044,7 +903,6 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1065,7 +923,6 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1085,9 +942,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1110,11 +965,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cla-backend-go/userSubscribeLambda/cmd/serve_lambda.go b/cla-backend-go/userSubscribeLambda/cmd/serve_lambda.go index 31c44a848..e5a61ff21 100644 --- a/cla-backend-go/userSubscribeLambda/cmd/serve_lambda.go +++ b/cla-backend-go/userSubscribeLambda/cmd/serve_lambda.go @@ -1,3 +1,4 @@ +//go:build aws_lambda // +build aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/userSubscribeLambda/cmd/serve_local.go b/cla-backend-go/userSubscribeLambda/cmd/serve_local.go index 8d15e4e64..aae00f394 100644 --- a/cla-backend-go/userSubscribeLambda/cmd/serve_local.go +++ b/cla-backend-go/userSubscribeLambda/cmd/serve_local.go @@ -1,3 +1,4 @@ +//go:build !aws_lambda // +build !aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/utils/utils_user_auth_lambda.go b/cla-backend-go/utils/utils_user_auth_lambda.go index 0e9bfbd88..39c0a3a66 100644 --- a/cla-backend-go/utils/utils_user_auth_lambda.go +++ b/cla-backend-go/utils/utils_user_auth_lambda.go @@ -1,3 +1,4 @@ +//go:build aws_lambda // +build aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index 85a61676f..ab3696b40 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -1,3 +1,4 @@ +//go:build !aws_lambda // +build !aws_lambda // Copyright The Linux Foundation and each contributor to CommunityBridge. From a063b05d1771d2b141d9cf7ff36b4bf3123cf3fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:50:10 -0700 Subject: [PATCH 0479/1276] Bump tar from 4.4.15 to 4.4.19 in /cla-frontend-corporate-console (#3221) Bumps [tar](https://github.com/npm/node-tar) from 4.4.15 to 4.4.19. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v4.4.15...v4.4.19) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index fe72ed2b5..0b2ca65f1 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -1468,7 +1468,7 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.0.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -2778,7 +2778,7 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.5: +fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== @@ -4228,7 +4228,7 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: +minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -4243,7 +4243,7 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.2.1: +minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== @@ -4274,7 +4274,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5379,7 +5379,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6087,17 +6087,17 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^4.0.2: - version "4.4.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" - integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" tar@^6.0.5: version "6.0.5" @@ -6625,7 +6625,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From ec74f0aba20504538f839254566bce8c577aabe8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:50:28 -0700 Subject: [PATCH 0480/1276] Bump tar from 6.1.4 to 6.1.11 in /cla-backend-go (#3223) Bumps [tar](https://github.com/npm/node-tar) from 6.1.4 to 6.1.11. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.4...v6.1.11) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend-go/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index f44d4608b..eb3ade544 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -4753,9 +4753,9 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.4.tgz#9f0722b772a5e00dba7d52e1923b37a7ec3799b3" - integrity sha512-kcPWrO8S5ABjuZ/v1xQHP8xCEvj1dQ1d9iAb6Qs4jLYzaAIYWwST2IQpz7Ud8VNYRI+fGhFjrnzRKmRggKWg3g== + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From ac67eaae78057ccd7a833afee8b840bb7e39ca83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:20:14 -0700 Subject: [PATCH 0481/1276] Bump tar from 6.1.4 to 6.1.11 (#3225) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1e449494c..fd1445b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4555,9 +4555,9 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.0.5: - version "6.1.4" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.4.tgz#9f0722b772a5e00dba7d52e1923b37a7ec3799b3" - integrity sha512-kcPWrO8S5ABjuZ/v1xQHP8xCEvj1dQ1d9iAb6Qs4jLYzaAIYWwST2IQpz7Ud8VNYRI+fGhFjrnzRKmRggKWg3g== + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From 829fa8fa251457cdc03bc890e11cfe90c9ea1f34 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 31 Aug 2021 18:43:40 -0700 Subject: [PATCH 0482/1276] Housekeeping - Moved User Service Events Lambda (#3226) --- .circleci/config.yml | 83 +- cla-backend-go/.gitignore | 14 +- cla-backend-go/Makefile | 152 +-- .../cmd/serve_lambda.go | 0 .../user-subscribe-lambda}/cmd/serve_local.go | 0 .../user-subscribe-lambda}/main.go | 3 +- cla-backend-go/package.json | 2 +- cla-backend-go/serverless.yml | 4 +- cla-backend-go/yarn.lock | 1017 +++++++++-------- cla-backend/serverless.yml | 37 +- 10 files changed, 670 insertions(+), 642 deletions(-) rename cla-backend-go/{userSubscribeLambda => cmd/user-subscribe-lambda}/cmd/serve_lambda.go (100%) rename cla-backend-go/{userSubscribeLambda => cmd/user-subscribe-lambda}/cmd/serve_local.go (100%) rename cla-backend-go/{userSubscribeLambda => cmd/user-subscribe-lambda}/main.go (99%) diff --git a/.circleci/config.yml b/.circleci/config.yml index b82fd1992..fe8f8026e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -271,15 +271,7 @@ jobs: - persist_to_workspace: root: ~/ paths: - - cla-backend-go/backend-aws-lambda - - cla-backend-go/user-subscribe-lambda - - cla-backend-go/metrics-aws-lambda - - cla-backend-go/metrics-report-lambda - - cla-backend-go/dynamo-events-lambda - - cla-backend-go/zipbuilder-scheduler-lambda - - cla-backend-go/zipbuilder-lambda - - cla-backend-go/gitlab-repository-check-lambda - - cla-backend-go/functional-tests + - cla-backend-go/bin/* buildGoBackendCommon: <<: *buildGoBackendAnchor @@ -291,26 +283,6 @@ jobs: AWS_REGION: us-east-1 DYNAMODB_AWS_REGION: us-east-1 -# buildGoBackendStaging: -# <<: *buildGoBackendAnchor -# environment: -# STAGE: staging -# AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING -# AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING -# AWS_PROFILE: lf-cla -# AWS_REGION: us-east-1 -# DYNAMODB_AWS_REGION: us-east-1 -# -# buildGoBackendProd: -# <<: *buildGoBackendAnchor -# environment: -# STAGE: prod -# AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD -# AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD -# AWS_PROFILE: lf-cla -# AWS_REGION: us-east-1 -# DYNAMODB_AWS_REGION: us-east-1 - # Deploys deployBackend: &deployBackendAnchor docker: @@ -347,28 +319,29 @@ jobs: # -------------------------------------------------------------- # Copy over the go backend binary to the common cla-backend folder (they share a single serverless.yml config) - cp ~/cla-backend-go/backend-aws-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/user-subscribe-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/metrics-aws-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/metrics-report-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/dynamo-events-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/zipbuilder-scheduler-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/zipbuilder-lambda ~/project/cla-backend/ - cp ~/cla-backend-go/gitlab-repository-check-lambda ~/project/cla-backend/ + mkdir -p ~/project/cla-backend/bin + cp ~/cla-backend-go/bin/backend-aws-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/user-subscribe-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/metrics-aws-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/metrics-report-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/dynamo-events-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/zipbuilder-scheduler-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/zipbuilder-lambda ~/project/cla-backend/bin/ + cp ~/cla-backend-go/bin/gitlab-repository-check-lambda ~/project/cla-backend/bin/ ls -alF ~/project/cla-backend/ pushd ~/project/cla-backend echo "Directory: $(pwd)" yarn install - if [[ ! -f backend-aws-lambda ]]; then echo "Missing backend-aws-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f user-subscribe-lambda ]]; then echo "Missing user-subscribe-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f metrics-aws-lambda ]]; then echo "Missing metrics-aws-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f metrics-report-lambda ]]; then echo "Missing metrics-report-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f dynamo-events-lambda ]]; then echo "Missing dynamo-events-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f zipbuilder-lambda ]]; then echo "Missing zipbuilder-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f zipbuilder-scheduler-lambda ]]; then echo "Missing zipbuilder-scheduler-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f gitlab-repository-check-lambda ]]; then echo "Missing gitlab-repository-check-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/backend-aws-lambda ]]; then echo "Missing bin/backend-aws-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/user-subscribe-lambda ]]; then echo "Missing bin/user-subscribe-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/metrics-aws-lambda ]]; then echo "Missing bin/metrics-aws-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/metrics-report-lambda ]]; then echo "Missing bin/metrics-report-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/dynamo-events-lambda ]]; then echo "Missing bin/dynamo-events-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/zipbuilder-lambda ]]; then echo "Missing bin/zipbuilder-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/zipbuilder-scheduler-lambda ]]; then echo "Missing bin/zipbuilder-scheduler-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/gitlab-repository-check-lambda ]]; then echo "Missing bin/gitlab-repository-check-lambda binary file. Exiting..."; exit 1; fi if [[ ! -f serverless.yml ]]; then echo "Missing serverless.yml file. Exiting..."; exit 1; fi if [[ ! -f serverless-authorizer.yml ]]; then echo "Missing serverless-authorizer.yml file. Exiting..."; exit 1; fi yarn sls deploy --force --stage ${STAGE} --region us-east-1 @@ -387,14 +360,15 @@ jobs: ## End Debug # -------------------------------------------------------------- - cp ~/cla-backend-go/backend-aws-lambda ~/project/cla-backend-go/ - cp ~/cla-backend-go/user-subscribe-lambda ~/project/cla-backend-go/ - echo "Directory: ~/project/cla-backend-go/" - ls -alF ~/project/cla-backend-go/ + mkdir -p ~/project/cla-backend-go/bin + cp ~/cla-backend-go/bin/backend-aws-lambda ~/project/cla-backend-go/bin/ + cp ~/cla-backend-go/bin/user-subscribe-lambda ~/project/cla-backend-go/bin/ + echo "Directory: ~/project/cla-backend-go/bin/" + ls -alF ~/project/cla-backend-go/bin/ pushd ~/project/cla-backend-go echo "Directory: $(pwd)" - if [[ ! -f backend-aws-lambda ]]; then echo "Missing backend-aws-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f user-subscribe-lambda ]]; then echo "Missing user-subscribe-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/backend-aws-lambda ]]; then echo "Missing bin/backend-aws-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/user-subscribe-lambda ]]; then echo "Missing bin/user-subscribe-lambda binary file. Exiting..."; exit 1; fi yarn install # Deploy to us-east-2 @@ -619,13 +593,6 @@ jobs: popd - *install-node-12 - *install_aws_cli -# - run: -# name: Publish Console to S3 -# command: | -# pushd ${PROJECT_DIR}/src -# aws s3 rm s3://${BUCKET_NAME} --recursive -# aws s3 sync www/ s3://${BUCKET_NAME}/ --acl public-read -# popd - run: name: Deploy Cloudfront and LambdaEdge command: | diff --git a/cla-backend-go/.gitignore b/cla-backend-go/.gitignore index 19cb1e7ba..6e9b5b2a2 100644 --- a/cla-backend-go/.gitignore +++ b/cla-backend-go/.gitignore @@ -1,19 +1,7 @@ # Copyright The Linux Foundation and each contributor to CommunityBridge. # SPDX-License-Identifier: MIT # Project specific ignores -cla-backend-go -cla -cla-mac* -backend-aws-lambda* -user-subscribe-lambda* -build-user-subscribe-lambda* -metrics-aws-lambda* -metrics-report-lambda* -functional-tests* -dynamo-events-lambda* -zipbuilder-lambda* -zipbuilder-scheduler-lambda* -gitlab-repository-check-lambda* +bin/ *env.json db/schema.sql diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 700d174d9..11249b9f9 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT SERVICE = cla SHELL = bash +BIN_DIR = bin LAMBDA_BIN = backend-aws-lambda METRICS_BIN = metrics-aws-lambda METRICS_REPORT_BIN = metrics-report-lambda @@ -30,7 +31,7 @@ GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') all: all-mac all-mac: clean swagger deps fmt build-mac build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac build-gitlab-repository-check-lambda-mac test lint all-linux: clean swagger deps fmt build-linux build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux build-gitlab-repository-check-lambda-linux test lint -lambdas-mac: build-aws-lambda-mac +lambdas-mac: build-lambdas-mac build-lambdas-mac: build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-metrics-report-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac build-gitlab-repository-check-lambda-mac lambdas: build-lambdas-linux build-lambdas-linux: build-aws-lambda-linux build-user-subscribe-lambda-linux build-metrics-lambda-linux build-metrics-report-lambda-linux build-dynamo-events-lambda-linux build-zipbuilder-scheduler-lambda-linux build-zipbuilder-lambda-linux build-gitlab-repository-check-lambda-linux @@ -65,14 +66,16 @@ setup_deploy: setup-deploy setup-deploy: @yarn add serverless && yarn install -clean: - @rm -rf cla cla-mac* cla-linux \ - ./v2/project-service/client ./v2/project-service/models \ +clean: clean-models clean-lambdas + @rm -rf cla cla-mac* cla-linux + +clean-models: + @rm -rf ./v2/project-service/client ./v2/project-service/models \ ./v2/organization-service/client ./v2/organization-service/models \ - ./v2/user-service/client ./v2/user-service/models \ - backend-aws-lambda* dynamo-events-lambda* \ - functional-tests* metrics-aws-lambda* metrics-report-lambda* \ - user-subscribe-lambda* zipbuild-lambda* zipbuilder-scheduler-lambda* + ./v2/user-service/client ./v2/user-service/models + +clean-lambdas: + @rm -rf $(BIN_DIR) swagger-clean: clean-swagger clean-swagger: @@ -196,129 +199,128 @@ mock: @cd $(MAKEFILE_DIR) && mockgen -copyright_file=copyright-header.txt -package=branch_protection -destination=github/branch_protection/mock.go -self_package=github.com/communitybridge/easycla/cla-backend-go/github/branch_protection github.com/communitybridge/easycla/cla-backend-go/github/branch_protection CombinedRepository -run: - go run main.go - deps: go env -w GOPRIVATE=github.com/LF-Engineering/* go mod download -x +build-prep: + mkdir -p $(BIN_DIR) + build: build-linux -build-linux: deps +build-linux: deps build-prep @echo "Building Linux amd64 binary..." - env GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(SERVICE) main.go - @chmod +x $(SERVICE) + env GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(SERVICE) main.go + @chmod +x $(BIN_DIR)/$(SERVICE) -build-mac: deps +build-mac: deps build-prep @echo "Building Mac OSX amd64 binary..." - env GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(SERVICE)-mac main.go - @chmod +x $(SERVICE)-mac + env GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(SERVICE)-mac main.go + @chmod +x $(BIN_DIR)/$(SERVICE)-mac -build-mac-arm64: deps +build-mac-arm64: deps build-prep @echo "Building Mac OSX Arm64 binary..." - env GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(SERVICE)-mac-arm64 main.go - @chmod +x $(SERVICE)-mac-arm64 + env GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(BIN_DIR)/$(SERVICE)-mac-arm64 main.go + @chmod +x $(BIN_DIR)/$(SERVICE)-mac-arm64 rebuild-mac: clean fmt build-mac lint - ./$(SERVICE)-mac build-aws-lambda: build-aws-lambda-linux -build-aws-lambda-linux: deps +build-aws-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(LAMBDA_BIN) main.go - @chmod +x $(LAMBDA_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(BIN_DIR)/$(LAMBDA_BIN) main.go + @chmod +x $(BIN_DIR)/$(LAMBDA_BIN) -build-aws-lambda-mac: deps +build-aws-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(LAMBDA_BIN)-mac main.go - @chmod +x $(LAMBDA_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(BIN_DIR)/$(LAMBDA_BIN)-mac main.go + @chmod +x $(BIN_DIR)/$(LAMBDA_BIN)-mac build-user-subscribe-lambda: build-user-subscribe-lambda-linux -build-user-subscribe-lambda-linux: deps +build-user-subscribe-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(USER_SUBSCRIBE_BIN) userSubscribeLambda/main.go - @chmod +x $(USER_SUBSCRIBE_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(BIN_DIR)/$(USER_SUBSCRIBE_BIN) cmd/user-subscribe-lambda/main.go + @chmod +x $(BIN_DIR)/$(USER_SUBSCRIBE_BIN) -build-user-subscribe-lambda-mac: deps +build-user-subscribe-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(USER_SUBSCRIBE_BIN)-mac userSubscribeLambda/main.go - @chmod +x $(USER_SUBSCRIBE_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(BIN_DIR)/$(USER_SUBSCRIBE_BIN)-mac cmd/user-subscribe-lambda/main.go + @chmod +x $(BIN_DIR)/$(USER_SUBSCRIBE_BIN)-mac build-metrics-lambda: build-metrics-lambda-linux -build-metrics-lambda-linux: deps +build-metrics-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(METRICS_BIN) cmd/metrics_lambda/main.go - @chmod +x $(METRICS_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(METRICS_BIN) cmd/metrics_lambda/main.go + @chmod +x $(BIN_DIR)/$(METRICS_BIN) -build-metrics-lambda-mac: deps +build-metrics-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(METRICS_BIN)-mac cmd/metrics_lambda/main.go - @chmod +x $(METRICS_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(METRICS_BIN)-mac cmd/metrics_lambda/main.go + @chmod +x $(BIN_DIR)/$(METRICS_BIN)-mac build-metrics-report-lambda: build-metrics-report-lambda-linux -build-metrics-report-lambda-linux: deps +build-metrics-report-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(METRICS_REPORT_BIN) cmd/metrics_report_lambda/main.go - @chmod +x $(METRICS_REPORT_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(METRICS_REPORT_BIN) cmd/metrics_report_lambda/main.go + @chmod +x $(BIN_DIR)/$(METRICS_REPORT_BIN) -build-metrics-report-lambda-mac: deps +build-metrics-report-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(METRICS_REPORT_BIN)-mac cmd/metrics_report_lambda/main.go - @chmod +x $(METRICS_REPORT_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(METRICS_REPORT_BIN)-mac cmd/metrics_report_lambda/main.go + @chmod +x $(BIN_DIR)/$(METRICS_REPORT_BIN)-mac build-dynamo-events-lambda: build-dynamo-events-lambda-linux -build-dynamo-events-lambda-linux: deps +build-dynamo-events-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(DYNAMO_EVENTS_BIN) cmd/dynamo_events_lambda/main.go - @chmod +x $(DYNAMO_EVENTS_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(DYNAMO_EVENTS_BIN) cmd/dynamo_events_lambda/main.go + @chmod +x $(BIN_DIR)/$(DYNAMO_EVENTS_BIN) -build-dynamo-events-lambda-mac: deps +build-dynamo-events-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(DYNAMO_EVENTS_BIN)-mac cmd/dynamo_events_lambda/main.go - @chmod +x $(DYNAMO_EVENTS_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(DYNAMO_EVENTS_BIN)-mac cmd/dynamo_events_lambda/main.go + @chmod +x $(BIN_DIR)/$(DYNAMO_EVENTS_BIN)-mac build-zipbuilder-scheduler-lambda: build-zipbuilder-scheduler-lambda-linux -build-zipbuilder-scheduler-lambda-linux: deps +build-zipbuilder-scheduler-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(ZIPBUILDER_SCHEDULER_BIN) cmd/zipbuilder_scheduler_lambda/main.go - @chmod +x $(ZIPBUILDER_SCHEDULER_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(ZIPBUILDER_SCHEDULER_BIN) cmd/zipbuilder_scheduler_lambda/main.go + @chmod +x $(BIN_DIR)/$(ZIPBUILDER_SCHEDULER_BIN) -build-zipbuilder-scheduler-lambda-mac: deps +build-zipbuilder-scheduler-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(ZIPBUILDER_SCHEDULER_BIN)-mac cmd/zipbuilder_scheduler_lambda/main.go - @chmod +x $(ZIPBUILDER_SCHEDULER_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(ZIPBUILDER_SCHEDULER_BIN)-mac cmd/zipbuilder_scheduler_lambda/main.go + @chmod +x $(BIN_DIR)/$(ZIPBUILDER_SCHEDULER_BIN)-mac build-zipbuilder-lambda: build-zipbuilder-lambda-linux -build-zipbuilder-lambda-linux: deps +build-zipbuilder-lambda-linux: deps build-prep @echo "Building a statically linked Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(ZIPBUILDER_BIN) cmd/zipbuilder_lambda/main.go - @chmod +x $(ZIPBUILDER_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(ZIPBUILDER_BIN) cmd/zipbuilder_lambda/main.go + @chmod +x $(BIN_DIR)/$(ZIPBUILDER_BIN) -build-zipbuilder-lambda-mac: deps +build-zipbuilder-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(ZIPBUILDER_BIN)-mac cmd/zipbuilder_lambda/main.go - @chmod +x $(ZIPBUILDER_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(ZIPBUILDER_BIN)-mac cmd/zipbuilder_lambda/main.go + @chmod +x $(BIN_DIR)/$(ZIPBUILDER_BIN)-mac -build-gitlab-repository-check-lambda-linux: deps +build-gitlab-repository-check-lambda-linux: deps build-prep @echo "Building a statically linked Linux OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(GITLAB_REPO_CHECK_BIN) cmd/gitlab_repository_check/main.go - @chmod +x $(GITLAB_REPO_CHECK_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) $(BUILD_TAGS) -o $(BIN_DIR)/$(GITLAB_REPO_CHECK_BIN) cmd/gitlab_repository_check/main.go + @chmod +x $(BIN_DIR)/$(GITLAB_REPO_CHECK_BIN) -build-gitlab-repository-check-lambda-mac: deps +build-gitlab-repository-check-lambda-mac: deps build-prep @echo "Building a statically linked Mac OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(GITLAB_REPO_CHECK_BIN)-mac cmd/gitlab_repository_check/main.go - @chmod +x $(GITLAB_REPO_CHECK_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(GITLAB_REPO_CHECK_BIN)-mac cmd/gitlab_repository_check/main.go + @chmod +x $(BIN_DIR)/$(GITLAB_REPO_CHECK_BIN)-mac build-functional-tests: build-functional-tests-linux -build-functional-tests-linux: deps +build-functional-tests-linux: deps build-prep @echo "Building Functional Tests for Linux amd64 binary..." - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(FUNCTIONAL_TESTS_BIN) cmd/functional_tests/main.go - @chmod +x $(FUNCTIONAL_TESTS_BIN) + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(FUNCTIONAL_TESTS_BIN) cmd/functional_tests/main.go + @chmod +x $(BIN_DIR)/$(FUNCTIONAL_TESTS_BIN) -build-functional-tests-mac: deps +build-functional-tests-mac: deps build-prep @echo "Building Functional Tests for OSX amd64 binary..." - env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(FUNCTIONAL_TESTS_BIN)-mac cmd/functional_tests/main.go - @chmod +x $(FUNCTIONAL_TESTS_BIN)-mac + env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(FUNCTIONAL_TESTS_BIN)-mac cmd/functional_tests/main.go + @chmod +x $(BIN_DIR)/$(FUNCTIONAL_TESTS_BIN)-mac lint: @cd $(MAKEFILE_DIR) && echo "Running lint..." && $(LINT_TOOL) --version && $(LINT_TOOL) run --exclude="this method will not auto-escape HTML. Verify data is well formed" --allow-parallel-runners --config=.golangci.yaml ./... && echo "Lint check passed." diff --git a/cla-backend-go/userSubscribeLambda/cmd/serve_lambda.go b/cla-backend-go/cmd/user-subscribe-lambda/cmd/serve_lambda.go similarity index 100% rename from cla-backend-go/userSubscribeLambda/cmd/serve_lambda.go rename to cla-backend-go/cmd/user-subscribe-lambda/cmd/serve_lambda.go diff --git a/cla-backend-go/userSubscribeLambda/cmd/serve_local.go b/cla-backend-go/cmd/user-subscribe-lambda/cmd/serve_local.go similarity index 100% rename from cla-backend-go/userSubscribeLambda/cmd/serve_local.go rename to cla-backend-go/cmd/user-subscribe-lambda/cmd/serve_local.go diff --git a/cla-backend-go/userSubscribeLambda/main.go b/cla-backend-go/cmd/user-subscribe-lambda/main.go similarity index 99% rename from cla-backend-go/userSubscribeLambda/main.go rename to cla-backend-go/cmd/user-subscribe-lambda/main.go index 959026f76..08dec8174 100644 --- a/cla-backend-go/userSubscribeLambda/main.go +++ b/cla-backend-go/cmd/user-subscribe-lambda/main.go @@ -9,6 +9,8 @@ import ( "os" "runtime" + "github.com/communitybridge/easycla/cla-backend-go/cmd/user-subscribe-lambda/cmd" + "github.com/go-openapi/strfmt" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -23,7 +25,6 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/token" - "github.com/communitybridge/easycla/cla-backend-go/userSubscribeLambda/cmd" "github.com/communitybridge/easycla/cla-backend-go/users" user_service "github.com/communitybridge/easycla/cla-backend-go/v2/user-service" "github.com/mitchellh/mapstructure" diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index 53989b91e..2d94ad13f 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -19,7 +19,7 @@ "install": "^0.13.0", "node.extend": "^2.0.2", "request": "^2.88.0", - "serverless": "^2.19.0", + "serverless": "^2.57.0", "serverless-finch": "^2.3.2", "serverless-layers": "^1.4.3", "serverless-plugin-tracing": "^2.0.0", diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index c8f8b0b3c..575f8c09d 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -8,7 +8,7 @@ package: # Exclude all first - selectively add in lambda functions exclude: - auth/** - - ./backend-aws-lambda + - bin/ - dev.sh - docs/** - helpers/** @@ -309,7 +309,7 @@ functions: package: individually: true include: - - ./backend-aws-lambda + - ./bin/backend-aws-lambda # easyClaUserSubscribe: # name: easy-cla-user-subscribe diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index eb3ade544..9e645ab1d 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -138,39 +138,38 @@ node-fetch "^2.6.0" shortid "^2.2.14" -"@serverless/components@^3.4.7": - version "3.4.7" - resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.4.7.tgz#9e5d9a58951000d9b5bcea78cad56f62d7dd5633" - integrity sha512-jY3+K3juQAa1HpFbvc1kztyDi4SFqG1+1GzUwh/kpRTlz2A01GnekWm8mf47l9HKxRzMxqVveg37wyyIQpw4xg== - dependencies: - "@serverless/platform-client" "^3.1.5" - "@serverless/platform-client-china" "^2.0.9" - "@serverless/platform-sdk" "^2.3.2" - "@serverless/utils" "^2.2.0" - adm-zip "^0.4.16" +"@serverless/components@^3.16.0": + version "3.16.0" + resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.16.0.tgz#a161481f73ffa9e1d62c447b3d8b6bacbf701ba6" + integrity sha512-vgsfR0V4dierB97GKgtfFp/s5XN3zmEQlhRfshWLHjoXvJe2HvWOZXy5Bqyzb1WMr6S0dp/wXQzcnVStbiyBaw== + dependencies: + "@serverless/platform-client" "^4.2.2" + "@serverless/platform-client-china" "^2.2.0" + "@serverless/utils" "^4.0.0" + adm-zip "^0.5.4" ansi-escapes "^4.3.1" - aws4 "^1.11.0" chalk "^4.1.0" child-process-ext "^2.1.1" - chokidar "^3.5.0" + chokidar "^3.5.1" + ci-info "^3.2.0" + dayjs "^1.10.4" dotenv "^8.2.0" + fastest-levenshtein "^1.0.12" figures "^3.2.0" - fs-extra "^9.0.1" - globby "^11.0.2" - got "^11.8.1" + fs-extra "^9.1.0" + got "^11.8.2" graphlib "^2.1.8" https-proxy-agent "^5.0.0" - ini "^1.3.8" inquirer-autocomplete-prompt "^1.3.0" js-yaml "^3.14.1" memoizee "^0.4.14" minimist "^1.2.5" - moment "^2.29.1" open "^7.3.1" prettyoutput "^1.2.0" ramda "^0.27.1" semver "^7.3.4" strip-ansi "^6.0.0" + tencent-serverless-http "^1.3.1" traverse "^0.6.6" uuid "^8.3.2" @@ -185,30 +184,30 @@ ramda "^0.26.1" semver "^6.1.1" -"@serverless/enterprise-plugin@^4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@serverless/enterprise-plugin/-/enterprise-plugin-4.4.2.tgz#ec635a2099e63ecd6a82a005272cbfad8cbdfac6" - integrity sha512-w5xD8R8tFK6B7QiLvWI5jqVHTtH1LdTyGp5eRcjkdJBa10/D2IZFpJimMAGsBxk9U1JGKO4j0miVnRHIW8ppeg== +"@serverless/dashboard-plugin@^5.4.4": + version "5.4.4" + resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.4.4.tgz#787d0214175f520776c46ae0ca38bf7a981f8d54" + integrity sha512-1lLChYK/zwrF5SEAubVr9Oz/xGnq1Yjbw36X1iz0j/+jwbtpt1AeumksArA3UVAgDSecVOfaksxUta2cc10pRA== dependencies: "@serverless/event-mocks" "^1.1.1" - "@serverless/platform-client" "^3.1.5" - "@serverless/platform-sdk" "^2.3.2" - chalk "^4.1.0" + "@serverless/platform-client" "^4.3.0" + "@serverless/utils" "^5.7.0" + chalk "^4.1.2" child-process-ext "^2.1.1" - chokidar "^3.5.0" + chokidar "^3.5.2" cli-color "^2.0.0" flat "^5.0.2" - fs-extra "^9.0.1" - js-yaml "^3.14.1" - jszip "^3.5.0" - lodash "^4.17.20" - memoizee "^0.4.14" - ncjsm "^4.1.0" + fs-extra "^9.1.0" + js-yaml "^4.1.0" + jszip "^3.7.1" + lodash "^4.17.21" + memoizee "^0.4.15" + ncjsm "^4.2.0" node-dir "^0.1.17" node-fetch "^2.6.1" - open "^7.3.0" - semver "^7.3.4" - simple-git "^2.31.0" + open "^7.4.2" + semver "^7.3.5" + simple-git "^2.44.0" uuid "^8.3.2" yamljs "^0.3.0" @@ -220,63 +219,47 @@ "@types/lodash" "^4.14.123" lodash "^4.17.11" -"@serverless/platform-client-china@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@serverless/platform-client-china/-/platform-client-china-2.0.9.tgz#473b9413781bec62c61c57b9d6ce00eb691f6f7d" - integrity sha512-qec3a5lVaMH0nccgjVgvcEF8M+M95BXZbbYDGypVHEieJQxrKqj057+VVKsiHBeHYXzr4B3v6pIyQHst40vpIw== +"@serverless/platform-client-china@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@serverless/platform-client-china/-/platform-client-china-2.2.3.tgz#686bece20a3b4271766a591ee4e5331bd1090f6a" + integrity sha512-MoDxI0d3HMesHwdyk7gzwLNU0wuJk/ovL9+XD/gAZgnrdZpFFq9kwnJAm2midL7jdhpnEFu/aoGJ0eE+xtQJ7w== dependencies: - "@serverless/utils-china" "^1.0.11" + "@serverless/utils-china" "^1.1.4" + adm-zip "^0.5.1" archiver "^5.0.2" + axios "^0.21.1" dotenv "^8.2.0" + fast-glob "^3.2.4" fs-extra "^9.0.1" https-proxy-agent "^5.0.0" js-yaml "^3.14.0" minimatch "^3.0.4" querystring "^0.2.0" + run-parallel-limit "^1.0.6" traverse "^0.6.6" urlencode "^1.1.0" ws "^7.3.1" -"@serverless/platform-client@^3.1.5": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-3.4.0.tgz#8c6c94bcbf8e22a06c07b1009c500aef238024d7" - integrity sha512-iOMsluUqf7rQDalDwTRA+fuAHxk8WXCPXnMFDuTf/34q/1uRCx/xJhBNIvEUIbzZnSjiykfTIXUAcJ6kKbh6qA== +"@serverless/platform-client@^4.2.2", "@serverless/platform-client@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-4.3.0.tgz#5eda7b7eb8bdd0da93f2ecf2130b4131349806a1" + integrity sha512-q2CMqCkKeBaKA/UwfJAZLkdUsbghSbiYPvAX4rl9rsR5APm4KWtjKQP9CTOtVO5JRMWYoysK6jF0d5VJOABRzQ== dependencies: - adm-zip "^0.4.13" - archiver "^5.0.0" + adm-zip "^0.5.5" + archiver "^5.3.0" axios "^0.21.1" - fast-glob "^3.2.4" + fast-glob "^3.2.7" https-proxy-agent "^5.0.0" ignore "^5.1.8" isomorphic-ws "^4.0.1" - js-yaml "^3.13.1" + js-yaml "^3.14.1" jwt-decode "^2.2.0" minimatch "^3.0.4" - querystring "^0.2.0" - run-parallel-limit "^1.0.6" + querystring "^0.2.1" + run-parallel-limit "^1.1.0" throat "^5.0.0" traverse "^0.6.6" - ws "^7.2.1" - -"@serverless/platform-sdk@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@serverless/platform-sdk/-/platform-sdk-2.3.2.tgz#d53e37c910e66687e0cc398c3b83fde9d7357806" - integrity sha512-JSX0/EphGVvnb4RAgZYewtBXPuVsU2TFCuXh6EEZ4jxK3WgUwNYeYdwB8EuVLrm1/dYqu/UWUC0rPKb+ZDycJg== - dependencies: - chalk "^2.4.2" - https-proxy-agent "^4.0.0" - is-docker "^1.1.0" - jwt-decode "^2.2.0" - node-fetch "^2.6.1" - opn "^5.5.0" - querystring "^0.2.0" - ramda "^0.25.0" - rc "^1.2.8" - regenerator-runtime "^0.13.7" - source-map-support "^0.5.19" - uuid "^3.4.0" - write-file-atomic "^2.4.3" - ws "<7.0.0" + ws "^7.5.3" "@serverless/template@^1.1.3": version "1.1.3" @@ -288,16 +271,17 @@ graphlib "^2.1.7" traverse "^0.6.6" -"@serverless/utils-china@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@serverless/utils-china/-/utils-china-1.0.11.tgz#368003260ccd1df55f7477da50d0b606f157e58b" - integrity sha512-raOPIoPSTrkWKBDuozkYWvLXP2W65K9Uk4ud+lPcbhhBSamO3uVW40nuAkC19MdIoAsFi5oTGYpcc9UDx8b+lg== +"@serverless/utils-china@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@serverless/utils-china/-/utils-china-1.1.4.tgz#5a76c90d5d0f33c144fe0e82c799251a46415adb" + integrity sha512-8s73M1k+mST7Z/Rp8wgmZh50tjpwX+fqsbYYRuFGgyuWTvgqAlUflDOWAeQuDx4pEndWEqjbG09ZrZNqlHuZqQ== dependencies: - "@tencent-sdk/capi" "^1.1.2" + "@tencent-sdk/capi" "^1.1.8" dijkstrajs "^1.0.1" dot-qs "0.2.0" duplexify "^4.1.1" end-of-stream "^1.4.4" + got "^11.8.2" https-proxy-agent "^5.0.0" kafka-node "^5.0.0" protobufjs "^6.9.0" @@ -317,18 +301,45 @@ uuid "^3.4.0" write-file-atomic "^2.4.3" -"@serverless/utils@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-2.2.0.tgz#80dba2a98307f9987e8c8e399381a9302dd4a39f" - integrity sha512-0TqmLwH9r2GAewvz9mhZ+TSyQBoE9ANuB4nNhn6lJvVUgzlzji3aqeFbAuDt+Z60ZkaIDNipU/J5Vf2Lo/QTQQ== +"@serverless/utils@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-4.1.0.tgz#99574f446509eac8935f58013399d7af89945b4b" + integrity sha512-cl5uPaGg72z0sCUpF0zsOhwYYUV72Gxc1FwFfxltO8hSvMeFDvwD7JrNE4kHcIcKRjwPGbSH0fdVPUpErZ8Mog== dependencies: chalk "^4.1.0" + ci-info "^3.1.1" inquirer "^7.3.3" - js-yaml "^4.0.0" - lodash "^4.17.20" + js-yaml "^4.1.0" + jwt-decode "^3.1.2" + lodash "^4.17.21" ncjsm "^4.1.0" - rc "^1.2.8" - type "^2.1.0" + type "^2.5.0" + uuid "^8.3.2" + write-file-atomic "^3.0.3" + +"@serverless/utils@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.7.0.tgz#eeef23bc85c16501716c107c262b80d127bf3562" + integrity sha512-4/9lTag4NNMtgoK7qRSoP//VplnKUTqgKMJ5pjvuXHFTBNoGYbdi5Cr1UmbHwnG8FfYBUy95jNUHjSEeUXDqgg== + dependencies: + archive-type "^4.0.0" + chalk "^4.1.2" + ci-info "^3.2.0" + content-disposition "^0.5.3" + decompress "^4.2.1" + ext-name "^5.0.0" + file-type "^16.5.3" + filenamify "^4.3.0" + get-stream "^6.0.1" + got "^11.8.2" + inquirer "^7.3.3" + js-yaml "^4.1.0" + jwt-decode "^3.1.2" + lodash "^4.17.21" + make-dir "^3.1.0" + ncjsm "^4.2.0" + p-event "^4.2.0" + type "^2.5.0" uuid "^8.3.2" write-file-atomic "^3.0.3" @@ -337,11 +348,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - "@sindresorhus/is@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" @@ -361,16 +367,21 @@ dependencies: defer-to-connect "^2.0.0" -"@tencent-sdk/capi@^1.1.2": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@tencent-sdk/capi/-/capi-1.1.5.tgz#ba2932e292deb659d3e9968b70d9a6ec54d47c66" - integrity sha512-cHkoMY/1L5VxeiKv51uKxbFK8lZ7pZbY3CukzOHro8YKT6dETKYzTGO/F8jDhH7r8vKWxuA+ZcALzxYuVlmwsg== +"@tencent-sdk/capi@^1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@tencent-sdk/capi/-/capi-1.1.8.tgz#955130f9c7da88a599c05b3eae01b2f0e15a9beb" + integrity sha512-AmyMQndtxMsM59eDeA0gGiw8T2LzNvDhx/xl+ygFXXrsw+yb/mit73ndHkiHKcRA1EpNHTyD1PN9ATxghzplfg== dependencies: "@types/request" "^2.48.3" "@types/request-promise-native" "^1.0.17" request "^2.88.0" request-promise-native "^1.0.8" +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@types/cacheable-request@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" @@ -447,21 +458,16 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== -adm-zip@^0.4.13, adm-zip@^0.4.16: - version "0.4.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" - integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== +adm-zip@^0.5.1, adm-zip@^0.5.4, adm-zip@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.5.tgz#b6549dbea741e4050309f1bb4d47c47397ce2c4f" + integrity sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w== after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - agent-base@6: version "6.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" @@ -765,6 +771,14 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -806,7 +820,7 @@ archiver@^3.0.0: tar-stream "^2.1.0" zip-stream "^2.1.2" -archiver@^5.0.0, archiver@^5.0.2: +archiver@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.0.2.tgz#b2c435823499b1f46eb07aa18e7bcb332f6ca3fc" integrity sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg== @@ -819,18 +833,18 @@ archiver@^5.0.0, archiver@^5.0.2: tar-stream "^2.1.4" zip-stream "^4.0.0" -archiver@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.2.0.tgz#25aa1b3d9febf7aec5b0f296e77e69960c26db94" - integrity sha512-QEAKlgQuAtUxKeZB9w5/ggKXh21bZS+dzzuQ0RPBC20qtDCbTyzqmisoeJP46MP39fg4B4IcyvR+yeyEBdblsQ== +archiver@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" + integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== dependencies: archiver-utils "^2.1.0" async "^3.2.0" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" readdir-glob "^1.0.0" - tar-stream "^2.1.4" - zip-stream "^4.0.4" + tar-stream "^2.2.0" + zip-stream "^4.1.0" are-we-there-yet@~1.1.2: version "1.1.5" @@ -908,10 +922,10 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sdk@^2.828.0: - version "2.828.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.828.0.tgz#6aa599c3582f219568f41fb287eb65753e4a9234" - integrity sha512-JoDujGdncSIF9ka+XFZjop/7G+fNGucwPwYj7OHYMmFIOV5p7YmqomdbVmH/vIzd988YZz8oLOinWc4jM6vvhg== +aws-sdk@^2.979.0: + version "2.980.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" + integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== dependencies: buffer "4.9.2" events "1.1.1" @@ -928,11 +942,6 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - aws4@^1.8.0: version "1.10.1" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" @@ -1027,10 +1036,10 @@ bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -boxen@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.0.tgz#64fe9b16066af815f51057adcc800c3730120854" - integrity sha512-5bvsqw+hhgUi3oYGK0Vf4WpIkyemp60WBInn7+WNfoISzAqk/HX4L7WNROq38E6UR/y3YADpv6pEm4BfkeEAdA== +boxen@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" + integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== dependencies: ansi-align "^3.0.0" camelcase "^6.2.0" @@ -1079,11 +1088,6 @@ buffer-fill@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - buffer@4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -1118,24 +1122,16 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== +builtin-modules@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + cacheable-lookup@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3" integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w== -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -1201,6 +1197,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -1241,20 +1245,20 @@ chokidar@^3.4.1: optionalDependencies: fsevents "~2.1.2" -chokidar@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== +chokidar@^3.5.1, chokidar@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" chownr@^1.0.1: version "1.1.4" @@ -1266,11 +1270,28 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +ci-info@^3.1.1, ci-info@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-color@^1.4: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" + integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== + dependencies: + ansi-regex "^2.1.1" + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.5" + cli-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" @@ -1297,6 +1318,17 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-progress-footer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz#8aa2292f5cca4f6639e3e0107ef04d897d620563" + integrity sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg== + dependencies: + cli-color "^1.4" + d "1" + es5-ext "^0.10.47" + process-utils "^2.0.1" + timers-ext "^0.1.7" + cli-width@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" @@ -1326,7 +1358,7 @@ clone-deep@^4.0.0: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@1.0.2, clone-response@^1.0.2: +clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= @@ -1466,13 +1498,13 @@ compress-commons@^4.0.0: normalize-path "^3.0.0" readable-stream "^3.6.0" -compress-commons@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.0.2.tgz#d6896be386e52f37610cef9e6fa5defc58c31bd7" - integrity sha512-qhd32a9xgzmpfoga1VQEiLEwdKZ6Plnpx5UCgIsf89FSolyJ7WnifY4Gtjgv5WR6hWAyRaHxC5MiEhU/38U70A== +compress-commons@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== dependencies: buffer-crc32 "^0.2.13" - crc32-stream "^4.0.1" + crc32-stream "^4.0.2" normalize-path "^3.0.0" readable-stream "^3.6.0" @@ -1486,7 +1518,7 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-disposition@^0.5.2: +content-disposition@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -1532,10 +1564,10 @@ crc32-stream@^4.0.0: crc "^3.4.4" readable-stream "^3.4.0" -crc32-stream@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.1.tgz#0f047d74041737f8a55e86837a1b826bd8ab0067" - integrity sha512-FN5V+weeO/8JaXsamelVYO1PHyeCsuL3HcG4cqsj0ceARcocxalaShCsohZMSAF+db7UYFwBy1rARK/0oFItUw== +crc32-stream@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== dependencies: crc-32 "^1.2.0" readable-stream "^3.4.0" @@ -1578,10 +1610,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.3.tgz#cf3357c8e7f508432826371672ebf376cb7d619b" - integrity sha512-/2fdLN987N8Ki7Id8BUN2nhuiRyxTLumQnSQf9CNncFCyqFsSKb9TNhzRYcC8K8eJSJOKvbvkImo/MKKhNi4iw== +dayjs@^1.10.4, dayjs@^1.10.6: + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: version "4.3.1" @@ -1772,28 +1804,21 @@ dot-qs@0.2.0: resolved "https://registry.yarnpkg.com/dot-qs/-/dot-qs-0.2.0.tgz#d36517fe24b7cda61fce7a5026a0024afaf5a439" integrity sha1-02UX/iS3zaYfznpQJqACSvr1pDk= +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -download@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/download/-/download-8.0.0.tgz#afc0b309730811731aae9f5371c9f46be73e51b1" - integrity sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA== - dependencies: - archive-type "^4.0.0" - content-disposition "^0.5.2" - decompress "^4.2.1" - ext-name "^5.0.0" - file-type "^11.1.0" - filenamify "^3.0.0" - get-stream "^4.1.0" - got "^8.3.1" - make-dir "^2.1.0" - p-event "^2.1.0" - pify "^4.0.1" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -2008,6 +2033,13 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" +ext@^1.1.0, ext@^1.4.0, ext@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" + integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== + dependencies: + type "^2.5.0" + ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -2063,6 +2095,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.4: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2111,10 +2154,14 @@ figures@^3.0.0, figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" -file-type@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8" - integrity sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g== +file-type@^16.5.3: + version "16.5.3" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06" + integrity sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" file-type@^3.8.0: version "3.9.0" @@ -2146,19 +2193,19 @@ filename-reserved-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= -filenamify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-3.0.0.tgz#9603eb688179f8c5d40d828626dcbb92c3a4672c" - integrity sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g== +filenamify@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" + integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== dependencies: filename-reserved-regex "^2.0.0" - strip-outer "^1.0.0" + strip-outer "^1.0.1" trim-repeated "^1.0.0" -filesize@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" - integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== +filesize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.0.tgz#532db71cb8a04df7d403da054a28de1b648534e0" + integrity sha512-sb690gQx3y/5KZIztgWAKM/r4Hf1V3R8mkAE0OhasMw2FDYduFTYCji8YN9BVpsGoMxrHPFvia1BMxwfLHX+fQ== fill-range@^7.0.1: version "7.0.1" @@ -2230,14 +2277,6 @@ formidable@^1.2.0: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== -from2@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -2269,6 +2308,16 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -2294,15 +2343,28 @@ fs2@^0.3.8: memoizee "^0.4.14" type "^2.0.0" +fs2@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/fs2/-/fs2-0.3.9.tgz#3869e5b2ec7e0622eaa5f4373df540d3d427a9fb" + integrity sha512-WsOqncODWRlkjwll+73bAxVW3JPChDgaPX3DT4iTTm73UmG4VgALa7LaFblP232/DN60itkOrPZ8kaP1feksGQ== + dependencies: + d "^1.0.1" + deferred "^0.7.11" + es5-ext "^0.10.53" + event-emitter "^0.3.5" + ignore "^5.1.8" + memoizee "^0.4.14" + type "^2.1.0" + fsevents@~2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== -fsevents@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f" - integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" @@ -2328,11 +2390,6 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -2355,6 +2412,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2367,7 +2429,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2386,10 +2448,10 @@ glob@^7.0.5, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -globby@^11.0.2: - version "11.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" - integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== +globby@^11.0.4: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -2398,10 +2460,10 @@ globby@^11.0.2: merge2 "^1.3.0" slash "^3.0.0" -got@^11.8.1: - version "11.8.1" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" - integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q== +got@^11.8.2: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" @@ -2415,29 +2477,6 @@ got@^11.8.1: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^8.3.1: - version "8.3.2" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -2455,11 +2494,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + graphlib@^2.1.7, graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -2502,18 +2546,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2526,11 +2558,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -2553,14 +2580,6 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" @@ -2581,6 +2600,11 @@ ieee754@1.1.13, ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" @@ -2614,12 +2638,12 @@ info-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/info-symbol/-/info-symbol-0.1.0.tgz#27841d72867ddb4242cd612d79c10633881c6a78" integrity sha1-J4QdcoZ920JCzWEtecEGM4gcang= -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.7, ini@^1.3.8, ini@~1.3.0: +ini@^1.3.7, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== @@ -2678,14 +2702,6 @@ install@^0.13.0: resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776" integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA== -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -2749,16 +2765,16 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-docker@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-1.1.0.tgz#f04374d4eee5310e9a8e113bf1495411e46176a1" - integrity sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE= - -is-docker@^2.0.0, is-docker@^2.1.1: +is-docker@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== +is-docker@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -2815,11 +2831,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= - is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -2837,11 +2848,6 @@ is-promise@^2.1, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -2857,11 +2863,6 @@ is-windows@^1.0.1: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -2909,14 +2910,6 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - jmespath@0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" @@ -2938,10 +2931,10 @@ js-yaml@^3.14.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" - integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -3020,7 +3013,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.5.0: +jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -3035,6 +3028,11 @@ jwt-decode@^2.2.0: resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk= +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + kafka-node@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/kafka-node/-/kafka-node-5.0.0.tgz#4b6f65cc1d77ebe565859dfb8f9575ed15d543c0" @@ -3056,13 +3054,6 @@ kafka-node@^5.0.0: optionalDependencies: snappy "^6.0.1" -keyv@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" - integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== - dependencies: - json-buffer "3.0.0" - keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -3152,7 +3143,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: +lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3211,11 +3202,6 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -3247,13 +3233,12 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: - pify "^4.0.1" - semver "^5.6.0" + semver "^6.0.0" map-visit@^1.0.0: version "1.0.0" @@ -3262,6 +3247,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + memoizee@^0.4.14: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" @@ -3308,11 +3298,24 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +mime-db@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== + mime-db@^1.28.0: version "1.45.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" @@ -3325,6 +3328,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.44.0" +mime-types@~2.1.24: + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== + dependencies: + mime-db "1.49.0" + mime@^1.2.11, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -3397,11 +3407,6 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3455,6 +3460,19 @@ ncjsm@^4.1.0: fs2 "^0.3.8" type "^2.0.0" +ncjsm@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ncjsm/-/ncjsm-4.2.0.tgz#7b2d752c3a42db5f6a2c5ff6934cf66fb1bb5e38" + integrity sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw== + dependencies: + builtin-modules "^3.2.0" + deferred "^0.7.11" + es5-ext "^0.10.53" + es6-set "^0.1.5" + find-requires "^1.0.0" + fs2 "^0.3.9" + type "^2.5.0" + nested-error-stacks@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" @@ -3512,7 +3530,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: +normalize-url@^4.1.0, normalize-url@^4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== @@ -3551,10 +3569,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" - integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== object-visit@^1.0.0: version "1.0.1" @@ -3589,14 +3607,6 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -open@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" - integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - open@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/open/-/open-7.3.1.tgz#111119cb919ca1acd988f49685c4fdd0f4755356" @@ -3605,12 +3615,13 @@ open@^7.3.1: is-docker "^2.0.0" is-wsl "^2.1.1" -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: - is-wsl "^1.1.0" + is-docker "^2.0.0" + is-wsl "^2.1.1" optional@^0.1.3: version "0.1.4" @@ -3627,11 +3638,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -3642,34 +3648,22 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-event@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6" - integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== +p-event@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" + integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== dependencies: - p-timeout "^2.0.1" + p-timeout "^3.1.0" p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= - -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== dependencies: p-finally "^1.0.0" @@ -3721,6 +3715,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path2@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/path2/-/path2-0.1.0.tgz#639828942cdbda44a41a45b074ae8873483b4efa" + integrity sha1-Y5golCzb2kSkGkWwdK6Ic0g7Tvo= + +peek-readable@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.0.1.tgz#9a045f291db254111c3412c1ce4fec27ddd4d202" + integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -3736,6 +3740,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -3746,11 +3755,6 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -3819,6 +3823,24 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-utils@^2.0.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-2.6.0.tgz#0f0d3b2633ec1f4656def20d36425f92ec60cf23" + integrity sha512-2zKFADQDvHiUDyJQTsBTdu1+Q2D/WtReBotZwXmD9oUueb0kNv4rXulK/78hMM+nclBNFZ/ZlHOJtobt8oHpqQ== + dependencies: + ext "^1.1.0" + type "^2.0.0" + +process-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-4.0.0.tgz#3e5b204e1d38e62fe39ef3144664a1fe94097b9e" + integrity sha512-fMyMQbKCxX51YxR7YGCzPjLsU3yDzXFkP4oi1/Mt5Ixnk7GO/7uUTj8mrCHUwuvozWzI+V7QSJR9cZYnwNOZPg== + dependencies: + ext "^1.4.0" + fs2 "^0.3.9" + memoizee "^0.4.14" + type "^2.1.0" + promise-queue@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4" @@ -3967,6 +3989,16 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystring@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -3981,11 +4013,6 @@ radio-symbol@^2.0.0: ansi-green "^0.1.1" is-windows "^1.0.1" -ramda@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9" - integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ== - ramda@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" @@ -4028,6 +4055,13 @@ readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -4042,6 +4076,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + readline-ui@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/readline-ui/-/readline-ui-2.2.3.tgz#9e873a7668bbd8ca8a5573ce810a6bafb70a5089" @@ -4067,7 +4108,7 @@ readline-utils@^2.2.1, readline-utils@^2.2.3: strip-color "^0.1.0" window-size "^1.1.0" -regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== @@ -4138,7 +4179,7 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== -responselike@1.0.2, responselike@^1.0.2: +responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -4188,6 +4229,13 @@ run-parallel-limit@^1.0.6: resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.6.tgz#0982a893d825b050cbaff1a35414832b195541b6" integrity sha512-yFFs4Q2kECi5mWXyyZj3UlAZ5OFq5E07opABC+EmhZdjEkrxXaUwFqOaaNF4tbayMnBxrsbujpeCYTVjGufZGQ== +run-parallel-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" + integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw== + dependencies: + queue-microtask "^1.2.2" + run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -4232,12 +4280,12 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" -semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +semver@^5.4.1, semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.1.1, semver@^6.2.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -4249,6 +4297,13 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + serverless-finch@^2.3.2: version "2.6.0" resolved "https://registry.yarnpkg.com/serverless-finch/-/serverless-finch-2.6.0.tgz#c74e7492dbfae52aa6383d4a21bac9138bcd9383" @@ -4287,61 +4342,66 @@ serverless-pseudo-parameters@^2.5.0: resolved "https://registry.yarnpkg.com/serverless-pseudo-parameters/-/serverless-pseudo-parameters-2.5.0.tgz#f30bf34db166e4b8b22144a8e65aca71b90dd1e6" integrity sha512-A/O49AR8LL6jlnPSmnOTYgL1KqVgskeRla4sVDeS/r5dHFJlwOU5MgFilc7aaQP8NWAwRJANaIS9oiSE3I+VUA== -serverless@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.19.0.tgz#c464f0abac97f1a2da9d009cac17541e8b78050d" - integrity sha512-JvNB+llJXIfsMk6weTh5/aCMEvTGnizQ/ZHfyyXhLuBHm0cAa9h6bpyBagnC5CTtV++jwCR2WKu2a0SQQEmEvA== +serverless@^2.57.0: + version "2.57.0" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.57.0.tgz#74b5347f50969d81869762f53c92221d92e9999a" + integrity sha512-/gLzaqdisNlymMiQJMkFoACy7NXkOd7E67sbcOrENXuVb48QAS4PTT8VPkMNu6eQonlphGagbKhpqVmKzHJDmQ== dependencies: "@serverless/cli" "^1.5.2" - "@serverless/components" "^3.4.7" - "@serverless/enterprise-plugin" "^4.4.2" - "@serverless/utils" "^2.2.0" + "@serverless/components" "^3.16.0" + "@serverless/dashboard-plugin" "^5.4.4" + "@serverless/platform-client" "^4.3.0" + "@serverless/utils" "^5.7.0" ajv "^6.12.6" ajv-keywords "^3.5.2" - archiver "^5.2.0" - aws-sdk "^2.828.0" + archiver "^5.3.0" + aws-sdk "^2.979.0" bluebird "^3.7.2" - boxen "^5.0.0" + boxen "^5.0.1" cachedir "^2.3.0" - chalk "^4.1.0" + chalk "^4.1.2" child-process-ext "^2.1.1" + ci-info "^3.2.0" + cli-progress-footer "^1.1.1" d "^1.0.1" - dayjs "^1.10.3" + dayjs "^1.10.6" decompress "^4.2.1" - dotenv "^8.2.0" - download "^8.0.0" + dotenv "^10.0.0" + dotenv-expand "^5.1.0" essentials "^1.1.1" + ext "^1.5.0" fastest-levenshtein "^1.0.12" - filesize "^6.1.0" - fs-extra "^9.0.1" + filesize "^8.0.0" + fs-extra "^9.1.0" get-stdin "^8.0.0" - globby "^11.0.2" - got "^11.8.1" - graceful-fs "^4.2.4" + globby "^11.0.4" + got "^11.8.2" + graceful-fs "^4.2.8" https-proxy-agent "^5.0.0" - is-docker "^2.1.1" + is-docker "^2.2.1" is-wsl "^2.2.0" - js-yaml "^4.0.0" + js-yaml "^4.1.0" json-cycle "^1.3.0" json-refs "^3.0.15" - lodash "^4.17.20" + lodash "^4.17.21" memoizee "^0.4.15" - micromatch "^4.0.2" - ncjsm "^4.1.0" + micromatch "^4.0.4" + ncjsm "^4.2.0" node-fetch "^2.6.1" - object-hash "^2.1.1" - p-limit "^3.1.0" + object-hash "^2.2.0" + path2 "^0.1.0" + process-utils "^4.0.0" promise-queue "^2.2.5" replaceall "^0.1.6" - semver "^7.3.4" + semver "^7.3.5" + signal-exit "^3.0.3" tabtab "^3.0.2" - tar "^6.1.0" + tar "^6.1.11" timers-ext "^0.1.7" - type "^2.1.0" + type "^2.5.0" untildify "^4.0.0" uuid "^8.3.2" yaml-ast-parser "0.0.43" - yargs-parser "^20.2.4" set-blocking@~2.0.0: version "2.0.0" @@ -4402,7 +4462,7 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -4421,10 +4481,10 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.31.0: - version "2.31.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.31.0.tgz#3e5954c1e36c76fb382c08eaa2749a206db9f613" - integrity sha512-/+rmE7dYZMbRAfEmn8EUIOwlM2G7UdzpkC60KF86YAfXGnmGtsPrKsym0hKvLBdFLLW019C+aZld1+6iIVy5xA== +simple-git@^2.44.0: + version "2.45.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.45.0.tgz#4d53b146fc23496099ebfc7af5b0d605d0e3e504" + integrity sha512-wu/Ujs9IXn0HuyYm4HyRvne+EKsjJSWKEMkB3wQa3gNHSMHt7y3oeNX9zRQ3UBPk7bRRMLLHAdIZCZfHT9ehPg== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -4491,19 +4551,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -source-map-support@^0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - split2@^3.1.1: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -4664,13 +4711,21 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strip-outer@^1.0.0: +strip-outer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== dependencies: escape-string-regexp "^1.0.2" +strtok3@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.2.4.tgz#302aea64c0fa25d12a0385069ba66253fdc38a81" + integrity sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.0.1" + success-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897" @@ -4752,7 +4807,18 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^6.1.0: +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.1.11: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -4764,6 +4830,13 @@ tar@^6.1.0: mkdirp "^1.0.3" yallist "^4.0.0" +tencent-serverless-http@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/tencent-serverless-http/-/tencent-serverless-http-1.3.2.tgz#76fb76278bfb08c1d15d8350e723267a76b48314" + integrity sha512-HgIu9HuBdY0lx3jLKuicOSOrjmieklPh55x8ZmtuTnrZ5v1buAPUfLKBhTeBSz6e90ggyW+dPr5PWdz179kUkw== + dependencies: + type-is "^1.6.16" + terminal-paginator@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/terminal-paginator/-/terminal-paginator-2.0.2.tgz#967e66056f28fe8f55ba7c1eebfb7c3ef371c1d3" @@ -4793,11 +4866,6 @@ time-stamp@^1.0.1: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - timers-ext@^0.1.5, timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" @@ -4849,6 +4917,14 @@ toggle-array@^1.0.1: dependencies: isobject "^3.0.0" +token-types@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.1.1.tgz#ef9e8c8e2e0ded9f1b3f8dbaa46a3228b113ba1a" + integrity sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -4906,6 +4982,14 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-is@^1.6.16: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + type@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" @@ -4916,6 +5000,11 @@ type@^2.0.0, type@^2.1.0: resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== +type@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -4941,6 +5030,11 @@ universalify@^1.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + untildify@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" @@ -4965,11 +5059,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= - url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" @@ -5114,7 +5203,7 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: +ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== @@ -5160,11 +5249,6 @@ yamljs@^0.3.0: argparse "^1.0.7" glob "^7.0.5" -yargs-parser@^20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" @@ -5178,11 +5262,6 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - zames@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/zames/-/zames-2.0.1.tgz#f52633e193699b707672e32aeb6d51a09b6c8b36" @@ -5209,11 +5288,11 @@ zip-stream@^4.0.0: compress-commons "^4.0.0" readable-stream "^3.6.0" -zip-stream@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.0.4.tgz#3a8f100b73afaa7d1ae9338d910b321dec77ff3a" - integrity sha512-a65wQ3h5gcQ/nQGWV1mSZCEzCML6EK/vyVPcrPNynySP1j3VBbQKh3nhC8CbORb+jfl2vXvh56Ul5odP1bAHqw== +zip-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== dependencies: archiver-utils "^2.1.0" - compress-commons "^4.0.2" + compress-commons "^4.1.0" readable-stream "^3.6.0" diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 84ef282a0..142c850c5 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -8,15 +8,7 @@ package: # Exclude all first - selectively add in lambda functions exclude: - auth/** - - ./backend-aws-lambda - - ./user-subscribe-lambda - - ./metrics-aws-lambda - - ./metrics-report-lambda - - ./dynamo-events-lambda - - ./zipbuilder-scheduler-lambda - - ./zipbuilder-lambda - - ./gitlab-repository-check-lambda - - ./functional-tests + - bin/ - dev.sh - docs/** - helpers/** @@ -414,7 +406,7 @@ functions: package: individually: true include: - - ./backend-aws-lambda + - ./bin/backend-aws-lambda dynamo-projects-events-lambda: handler: dynamo-events-lambda @@ -424,7 +416,7 @@ functions: package: individually: true include: - - ./dynamo-events-lambda + - ./bin/dynamo-events-lambda dynamo-signatures-events-lambda: handler: dynamo-events-lambda @@ -434,7 +426,7 @@ functions: package: individually: true include: - - ./dynamo-events-lambda + - ./bin/dynamo-events-lambda dynamo-events-events-lambda: handler: dynamo-events-lambda @@ -444,7 +436,7 @@ functions: package: individually: true include: - - ./dynamo-events-lambda + - ./bin/dynamo-events-lambda dynamo-repositories-events-lambda: handler: dynamo-events-lambda @@ -454,7 +446,7 @@ functions: package: individually: true include: - - ./dynamo-events-lambda + - ./bin/dynamo-events-lambda dynamo-projects-cla-groups-events-lambda: handler: dynamo-events-lambda @@ -464,7 +456,7 @@ functions: package: individually: true include: - - ./dynamo-events-lambda + - ./bin/dynamo-events-lambda dynamo-github-orgs-events-lambda: handler: dynamo-events-lambda @@ -474,7 +466,7 @@ functions: package: individually: true include: - - ./dynamo-events-lambda + - ./bin/dynamo-events-lambda saveMetrics: description: "EasyCLA Save Metrics API handler" @@ -489,7 +481,7 @@ functions: package: individually: true include: - - ./metrics-aws-lambda + - ./bin/metrics-aws-lambda reportMetrics: description: "EasyCLA Report Metrics API handler" @@ -504,8 +496,7 @@ functions: package: individually: true include: - - ./metrics-report-lambda - + - ./bin/metrics-report-lambda zipbuilder-scheduler-lambda: handler: zipbuilder-scheduler-lambda @@ -521,7 +512,7 @@ functions: package: individually: true include: - - ./zipbuilder-scheduler-lambda + - ./bin/zipbuilder-scheduler-lambda zipbuilder-lambda: handler: zipbuilder-lambda @@ -533,7 +524,7 @@ functions: package: individually: true include: - - ./zipbuilder-lambda + - ./bin/zipbuilder-lambda gitlab-repository-check-lambda: handler: gitlab-repository-check-lambda @@ -550,7 +541,7 @@ functions: package: individually: true include: - - ./gitlab-repository-check-lambda + - ./bin/gitlab-repository-check-lambda apiv1: handler: wsgi_handler.handler @@ -615,7 +606,7 @@ functions: package: individually: true include: - - ./user-subscribe-lambda + - ./bin/user-subscribe-lambda reservedConcurrency: 5 events: - sns: From f3371cbee38f23d77617d2d4bc26c740da52b7cb Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 31 Aug 2021 20:13:53 -0700 Subject: [PATCH 0483/1276] Updated Serverless (#3227) - Updated serverless for go, python and landing page - Updated serverless to 2.57.0 - Updated serverless.yml - removed/updated deprecated constructs Signed-off-by: David Deal --- cla-backend-go/serverless.yml | 395 +++++----- cla-backend/.gitignore | 13 +- cla-backend/package.json | 2 +- cla-backend/serverless.yml | 454 ++++++------ cla-backend/yarn.lock | 1005 +++++++++++++------------ cla-landing-page/package.json | 14 +- cla-landing-page/project-vars.yml | 3 + cla-landing-page/serverless.yml | 35 +- cla-landing-page/yarn.lock | 1133 +++++++++++++++++------------ 9 files changed, 1666 insertions(+), 1388 deletions(-) create mode 100644 cla-landing-page/project-vars.yml diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index 575f8c09d..5e23e2e1e 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -2,27 +2,29 @@ # SPDX-License-Identifier: MIT service: cla-backend-go -frameworkVersion: '^2.11.0' +frameworkVersion: '^2.57.0' package: - # Exclude all first - selectively add in lambda functions - exclude: - - auth/** - - bin/ - - dev.sh - - docs/** - - helpers/** - - Makefile - - .env/** - - .venv/** - - .git* - - .git/** - - .vscode/** - - .serverless-wsgi - - .pylintrc - - node_modules/** - - package-lock.json - - yarn.lock + # Exclude all first - selectively add in lambda functions, + # Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead + # More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS + patterns: + - '!auth/**' + - '!bin/*' + - '!dev.sh' + - '!docs/**' + - '!helpers/**' + - '!Makefile' + - '!.env/**' + - '!.venv/**' + - '!.git*' + - '!.git/**' + - '!.vscode/**' + - '!.serverless-wsgi' + - '!.pylintrc' + - '!node_modules/**' + - '!package-lock.json' + - '!yarn.lock' custom: allowed_origins: ${file(./env.json):cla-allowed-origins-${opt:stage}, ssm:/cla-allowed-origins-${opt:stage}} @@ -34,7 +36,6 @@ custom: prune: automatic: true number: 3 - #userEventsSNSTopicARN: arn:aws:sns:${self:provider.region}:#{AWS::AccountId}:userservice-triggers-${self:provider.stage}-user-sns-topic ses_from_email: dev: admin@dev.lfcla.com staging: admin@staging.lfcla.com @@ -42,167 +43,169 @@ custom: provider: name: aws - provider: go1.x + runtime: go1.x stage: ${opt:stage} # EasyCLA v2 is deployed in us-east-2 to support Platform API GW and ACS region: us-east-2 timeout: 300 # optional, in seconds, default is 6 logRetentionInDays: 14 + lambdaHashingVersion: '20201221' # Resolution of lambda version hashes was improved with better algorithm, which will be used in next major release. Switch to it now by setting "provider.lambdaHashingVersion" to "20201221" tracing: - lambda: true + lambda: true # optional, enables tracing for all functions (can be true (true equals 'Active') 'Active' or 'PassThrough') - # Alongside provider.iamRoleStatements managed policies can also be added to this service-wide Role - # These will also be merged into the generated IAM Role - iamManagedPolicies: - - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - - "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" - - iamRoleStatements: - - Effect: Allow - Action: - - cloudwatch:* - Resource: "*" - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: "*" - - Effect: Allow - Action: - - s3:GetObject - - s3:PutObject - - s3:DeleteObject - - s3:PutObjectAcl - Resource: - - "arn:aws:s3:::cla-signature-files-${self:provider.stage}/*" - - "arn:aws:s3:::cla-project-logo-${self:provider.stage}/*" - - Effect: Allow - Action: - - s3:ListBucket - Resource: - - "arn:aws:s3:::cla-signature-files-${self:provider.stage}" - - "arn:aws:s3:::cla-project-logo-${self:provider.stage}" - - Effect: Allow - Action: - - ssm:GetParameter - Resource: - - "arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/cla-*" - - "arn:aws:ssm:${self:custom.dynamodb.region}:#{AWS::AccountId}:parameter/cla-*" - - Effect: Allow - Action: - - ses:SendEmail - - ses:SendRawEmail - Resource: - - "*" - Condition: - StringEquals: - ses:FromAddress: ${self:custom.ses_from_email.${opt:stage}} - - Effect: Allow - Action: - - sns:Publish - Resource: - - "*" - - Effect: Allow - Action: - - dynamodb:Query - - dynamodb:DeleteItem - - dynamodb:UpdateItem - - dynamodb:PutItem - - dynamodb:GetItem - - dynamodb:Scan - - dynamodb:DescribeTable - - dynamodb:BatchGetItem - - dynamodb:GetRecords - - dynamodb:GetShardIterator - - dynamodb:DescribeStream - - dynamodb:ListStreams - Resource: - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-session-store" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-store" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-user-permissions" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" - - Effect: Allow - Action: - - dynamodb:Query - Resource: - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/external-company-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/external-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + iam: + role: + # Alongside provider.iam.role.statements managed policies can also be added to this service-wide Role + # These will also be merged into the generated IAM Role + managedPolicies: + - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + - "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + statements: + - Effect: Allow + Action: + - cloudwatch:* + Resource: "*" + - Effect: Allow + Action: + - xray:PutTraceSegments + - xray:PutTelemetryRecords + Resource: "*" + - Effect: Allow + Action: + - s3:GetObject + - s3:PutObject + - s3:DeleteObject + - s3:PutObjectAcl + Resource: + - "arn:aws:s3:::cla-signature-files-${self:provider.stage}/*" + - "arn:aws:s3:::cla-project-logo-${self:provider.stage}/*" + - Effect: Allow + Action: + - s3:ListBucket + Resource: + - "arn:aws:s3:::cla-signature-files-${self:provider.stage}" + - "arn:aws:s3:::cla-project-logo-${self:provider.stage}" + - Effect: Allow + Action: + - ssm:GetParameter + Resource: + - "arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/cla-*" + - "arn:aws:ssm:${self:custom.dynamodb.region}:#{AWS::AccountId}:parameter/cla-*" + - Effect: Allow + Action: + - ses:SendEmail + - ses:SendRawEmail + Resource: + - "*" + Condition: + StringEquals: + ses:FromAddress: ${self:custom.ses_from_email.${opt:stage}} + - Effect: Allow + Action: + - sns:Publish + Resource: + - "*" + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:DeleteItem + - dynamodb:UpdateItem + - dynamodb:PutItem + - dynamodb:GetItem + - dynamodb:Scan + - dynamodb:DescribeTable + - dynamodb:BatchGetItem + - dynamodb:GetRecords + - dynamodb:GetShardIterator + - dynamodb:DescribeStream + - dynamodb:ListStreams + Resource: + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-session-store" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-store" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-user-permissions" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" + - Effect: Allow + Action: + - dynamodb:Query + Resource: + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/external-company-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/external-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: STAGE: ${self:provider.stage} @@ -210,21 +213,21 @@ provider: REGION: us-east-2 # Currently, we use DynamoDB in the us-east-1 region DYNAMODB_AWS_REGION: us-east-1 - GH_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}~true} - GH_APP_ID: ${file(./env.json):gh-app-id, ssm:/cla-gh-app-id-${opt:stage}~true} - GH_OAUTH_CLIENT_ID: ${file(./env.json):gh-oauth-client-id, ssm:/cla-gh-oauth-client-id-${opt:stage}~true} - GH_OAUTH_SECRET: ${file(./env.json):gh-oauth-secret, ssm:/cla-gh-oauth-secret-${opt:stage}~true} - GITHUB_OAUTH_TOKEN: ${file(./env.json):gh-access-token, ssm:/cla-gh-access-token-${opt:stage}~true} + GH_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}} + GH_APP_ID: ${file(./env.json):gh-app-id, ssm:/cla-gh-app-id-${opt:stage}} + GH_OAUTH_CLIENT_ID: ${file(./env.json):gh-oauth-client-id, ssm:/cla-gh-oauth-client-id-${opt:stage}} + GH_OAUTH_SECRET: ${file(./env.json):gh-oauth-secret, ssm:/cla-gh-oauth-secret-${opt:stage}} + GITHUB_OAUTH_TOKEN: ${file(./env.json):gh-access-token, ssm:/cla-gh-access-token-${opt:stage}} GH_STATUS_CTX_NAME: "EasyCLA" - AUTH0_DOMAIN: ${file(./env.json):auth0-domain, ssm:/cla-auth0-domain-${opt:stage}~true} - AUTH0_CLIENT_ID: ${file(./env.json):auth0-clientId, ssm:/cla-auth0-clientId-${opt:stage}~true} + AUTH0_DOMAIN: ${file(./env.json):auth0-domain, ssm:/cla-auth0-domain-${opt:stage}} + AUTH0_CLIENT_ID: ${file(./env.json):auth0-clientId, ssm:/cla-auth0-clientId-${opt:stage}} AUTH0_USERNAME_CLAIM: ${file(./env.json):auth0-username-claim, ssm:/cla-auth0-username-claim-${opt:stage}} AUTH0_ALGORITHM: ${file(./env.json):auth0-algorithm, ssm:/cla-auth0-algorithm-${opt:stage}} - SF_INSTANCE_URL: ${file(./env.json):sf-instance-url, ssm:/cla-sf-instance-url-${opt:stage}~true} - SF_CLIENT_ID: ${file(./env.json):sf-client-id, ssm:/cla-sf-consumer-key-${opt:stage}~true} - SF_CLIENT_SECRET: ${file(./env.json):sf-client-secret, ssm:/cla-sf-consumer-secret-${opt:stage}~true} - SF_USERNAME: ${file(./env.json):sf-username, ssm:/cla-sf-username-${opt:stage}~true} - SF_PASSWORD: ${file(./env.json):sf-password, ssm:/cla-sf-password-${opt:stage}~true} + SF_INSTANCE_URL: ${file(./env.json):sf-instance-url, ssm:/cla-sf-instance-url-${opt:stage}} + SF_CLIENT_ID: ${file(./env.json):sf-client-id, ssm:/cla-sf-consumer-key-${opt:stage}} + SF_CLIENT_SECRET: ${file(./env.json):sf-client-secret, ssm:/cla-sf-consumer-secret-${opt:stage}} + SF_USERNAME: ${file(./env.json):sf-username, ssm:/cla-sf-username-${opt:stage}} + SF_PASSWORD: ${file(./env.json):sf-password, ssm:/cla-sf-password-${opt:stage}} DOCRAPTOR_API_KEY: ${file(./env.json):doc-raptor-api-key, ssm:/cla-doc-raptor-api-key-${opt:stage}} DOCUSIGN_ROOT_URL: ${file(./env.json):docusign-root-url, ssm:/cla-docusign-root-url-${opt:stage}} DOCUSIGN_USERNAME: ${file(./env.json):docusign-username, ssm:/cla-docusign-username-${opt:stage}} @@ -235,8 +238,8 @@ provider: CLA_CONTRIBUTOR_V2_BASE: ${file(./env.json):cla-contributor-v2-base, ssm:/cla-contributor-v2-base-${opt:stage}} CLA_CORPORATE_BASE: ${file(./env.json):cla-corporate-base, ssm:/cla-corporate-base-${opt:stage}} CLA_LANDING_PAGE: ${file(./env.json):cla-landing-page, ssm:/cla-landing-page-${opt:stage}} - CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${opt:stage}~true} - CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${opt:stage}~true} + CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${opt:stage}} + CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${opt:stage}} SES_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-ses-sender-email-address, ssm:/cla-ses-sender-email-address-${opt:stage}} LF_GROUP_CLIENT_ID: ${file(./env.json):lf-group-client-id, ssm:/cla-lf-group-client-id-${opt:stage}} LF_GROUP_CLIENT_SECRET: ${file(./env.json):lf-group-client-secret, ssm:/cla-lf-group-client-secret-${opt:stage}} @@ -308,19 +311,5 @@ functions: handler: backend-aws-lambda package: individually: true - include: - - ./bin/backend-aws-lambda - - # easyClaUserSubscribe: - # name: easy-cla-user-subscribe - # runtime: go1.x - # description: Update easycla user data to user object in dynamodb - # handler: user-subscribe-lambda - # package: - # individually: true - # include: - # - ./user-subscribe-lambda - # reservedConcurrency: 5 - # events: - # - sns: - # arn: ${self:custom.userEventsSNSTopicARN} + patterns: + - './bin/backend-aws-lambda' diff --git a/cla-backend/.gitignore b/cla-backend/.gitignore index d03236b63..359233f88 100644 --- a/cla-backend/.gitignore +++ b/cla-backend/.gitignore @@ -1,5 +1,6 @@ # Copyright The Linux Foundation and each contributor to CommunityBridge. # SPDX-License-Identifier: MIT +bin/ wsgi.py serverless_wsgi.py wsgi_handler.py @@ -14,15 +15,3 @@ _env.json .venv .vscode/ -# Golang binaries that may have been copied over for local deployment -backend-aws-lambda -dynamo-events-lambda -functional-tests -metrics-aws-lambda -user-subscribe-lambda -zipbuilder-lambda -zipbuilder-scheduler-lambda -zipbuilder-lambda-mac -zipbuilder-scheduler-lambda-mac - - diff --git a/cla-backend/package.json b/cla-backend/package.json index 2a7ca59bf..8b3e21772 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -33,7 +33,7 @@ "install": "^0.13.0", "node.extend": "^2.0.2", "request": "^2.88.0", - "serverless": "^2.19.0", + "serverless": "^2.57.0", "serverless-domain-manager": "^5.1.0", "serverless-finch": "^2.3.2", "serverless-layers": "^1.4.3", diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 142c850c5..6fc652b2e 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -2,27 +2,29 @@ # SPDX-License-Identifier: MIT service: cla-backend -frameworkVersion: '^2.11.0' +frameworkVersion: '^2.57.0' package: # Exclude all first - selectively add in lambda functions - exclude: - - auth/** - - bin/ - - dev.sh - - docs/** - - helpers/** - - Makefile - - .env/** - - .venv/** - - .git* - - .git/** - - .vscode/** - - .serverless-wsgi - - .pylintrc - - node_modules/** - - package-lock.json - - yarn.lock + # Support for "package.include" and "package.exclude" will be removed with next major release. Please use "package.patterns" instead + # More Info: https://www.serverless.com/framework/docs/deprecations/#NEW_PACKAGE_PATTERNS + patterns: + - '!auth/**' + - '!bin/*' + - '!dev.sh' + - '!docs/**' + - '!helpers/**' + - '!Makefile' + - '!.env/**' + - '!.venv/**' + - '!.git*' + - '!.git/**' + - '!.vscode/**' + - '!.serverless-wsgi' + - '!.pylintrc' + - '!node_modules/**' + - '!package-lock.json' + - '!yarn.lock' custom: allowed_origins: ${file(./env.json):cla-allowed-origins-${opt:stage}, ssm:/cla-allowed-origins-${opt:stage}} @@ -103,12 +105,13 @@ provider: region: us-east-1 timeout: 60 # optional, in seconds, default is 6 logRetentionInDays: 14 + lambdaHashingVersion: '20201221' # Resolution of lambda version hashes was improved with better algorithm, which will be used in next major release. Switch to it now by setting "provider.lambdaHashingVersion" to "20201221" + apiGateway: # https://www.serverless.com/framework/docs/deprecations/#AWS_API_GATEWAY_NAME_STARTING_WITH_SERVICE shouldStartNameWithService: true # Configuring API Gateway to return binary media can be done via the binaryMediaTypes config: binaryMediaTypes: - #- '*/*' - 'image/*' - 'application/pdf' - 'application/zip' @@ -124,191 +127,192 @@ provider: tracing: apiGateway: true - lambda: true - - # Alongside provider.iamRoleStatements managed policies can also be added to this service-wide Role - # These will also be merged into the generated IAM Role - iamManagedPolicies: - - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - - "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" - - iamRoleStatements: - - Effect: Allow - Action: - - cloudwatch:* - Resource: "*" - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: "*" - - Effect: Allow - Action: - - s3:GetObject - - s3:PutObject - - s3:DeleteObject - - s3:PutObjectAcl - Resource: - - "arn:aws:s3:::cla-signature-files-${self:provider.stage}/*" - - "arn:aws:s3:::cla-project-logo-${self:provider.stage}/*" - - Effect: Allow - Action: - - s3:ListBucket - Resource: - - "arn:aws:s3:::cla-signature-files-${self:provider.stage}" - - "arn:aws:s3:::cla-project-logo-${self:provider.stage}" - - Effect: Allow - Action: - - lambda:InvokeFunction - Resource: - - "arn:aws:lambda:${self:provider.region}:#{AWS::AccountId}:function:cla-backend-${opt:stage}-zipbuilder-lambda" - - Effect: Allow - Action: - - ssm:GetParameter - Resource: - - "arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/cla-*" - - Effect: Allow - Action: - - ses:SendEmail - - ses:SendRawEmail - Resource: - - "*" - Condition: - StringEquals: - ses:FromAddress: ${self:custom.ses_from_email.${opt:stage}} - - Effect: Allow - Action: - - sns:Publish - Resource: - - "*" - - Effect: Allow - Action: - - sqs:SendMessage - Resource: - - "*" - - Effect: Allow - Action: - - dynamodb:Query - - dynamodb:DeleteItem - - dynamodb:UpdateItem - - dynamodb:PutItem - - dynamodb:GetItem - - dynamodb:Scan - - dynamodb:DescribeTable - - dynamodb:BatchGetItem - - dynamodb:GetRecords - - dynamodb:GetShardIterator - - dynamodb:DescribeStream - - dynamodb:ListStreams - Resource: - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-session-store" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-store" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-user-permissions" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" - - - Effect: Allow - Action: - - dynamodb:Query - Resource: - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/external-company-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/external-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + lambda: true # optional, enables tracing for all functions (can be true (true equals 'Active') 'Active' or 'PassThrough') + + iam: + role: + # Alongside provider.iam.role.statements managed policies can also be added to this service-wide Role + # These will also be merged into the generated IAM Role + managedPolicies: + - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + - "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + statements: + - Effect: Allow + Action: + - cloudwatch:* + Resource: "*" + - Effect: Allow + Action: + - xray:PutTraceSegments + - xray:PutTelemetryRecords + Resource: "*" + - Effect: Allow + Action: + - s3:GetObject + - s3:PutObject + - s3:DeleteObject + - s3:PutObjectAcl + Resource: + - "arn:aws:s3:::cla-signature-files-${self:provider.stage}/*" + - "arn:aws:s3:::cla-project-logo-${self:provider.stage}/*" + - Effect: Allow + Action: + - s3:ListBucket + Resource: + - "arn:aws:s3:::cla-signature-files-${self:provider.stage}" + - "arn:aws:s3:::cla-project-logo-${self:provider.stage}" + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: + - "arn:aws:lambda:${self:provider.region}:#{AWS::AccountId}:function:cla-backend-${opt:stage}-zipbuilder-lambda" + - Effect: Allow + Action: + - ssm:GetParameter + Resource: + - "arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/cla-*" + - Effect: Allow + Action: + - ses:SendEmail + - ses:SendRawEmail + Resource: + - "*" + Condition: + StringEquals: + ses:FromAddress: ${self:custom.ses_from_email.${opt:stage}} + - Effect: Allow + Action: + - sns:Publish + Resource: + - "*" + - Effect: Allow + Action: + - sqs:SendMessage + Resource: + - "*" + - Effect: Allow + Action: + - dynamodb:Query + - dynamodb:DeleteItem + - dynamodb:UpdateItem + - dynamodb:PutItem + - dynamodb:GetItem + - dynamodb:Scan + - dynamodb:DescribeTable + - dynamodb:BatchGetItem + - dynamodb:GetRecords + - dynamodb:GetShardIterator + - dynamodb:DescribeStream + - dynamodb:ListStreams + Resource: + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-session-store" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-store" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-user-permissions" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" + + - Effect: Allow + Action: + - dynamodb:Query + Resource: + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/external-company-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/external-project-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-type-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: STAGE: ${self:provider.stage} HOME: /tmp REGION: us-east-1 DYNAMODB_AWS_REGION: us-east-1 - GH_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}~true} - GH_APP_ID: ${file(./env.json):gh-app-id, ssm:/cla-gh-app-id-${opt:stage}~true} - GH_OAUTH_CLIENT_ID: ${file(./env.json):gh-oauth-client-id, ssm:/cla-gh-oauth-client-id-${opt:stage}~true} - GH_OAUTH_SECRET: ${file(./env.json):gh-oauth-secret, ssm:/cla-gh-oauth-secret-${opt:stage}~true} - GITHUB_OAUTH_TOKEN: ${file(./env.json):gh-access-token, ssm:/cla-gh-access-token-${opt:stage}~true} - GITHUB_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}~true} + GH_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}} + GH_APP_ID: ${file(./env.json):gh-app-id, ssm:/cla-gh-app-id-${opt:stage}} + GH_OAUTH_CLIENT_ID: ${file(./env.json):gh-oauth-client-id, ssm:/cla-gh-oauth-client-id-${opt:stage}} + GH_OAUTH_SECRET: ${file(./env.json):gh-oauth-secret, ssm:/cla-gh-oauth-secret-${opt:stage}} + GITHUB_OAUTH_TOKEN: ${file(./env.json):gh-access-token, ssm:/cla-gh-access-token-${opt:stage}} + GITHUB_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}} GH_STATUS_CTX_NAME: "EasyCLA" - AUTH0_DOMAIN: ${file(./env.json):auth0-domain, ssm:/cla-auth0-domain-${opt:stage}~true} - AUTH0_CLIENT_ID: ${file(./env.json):auth0-clientId, ssm:/cla-auth0-clientId-${opt:stage}~true} + AUTH0_DOMAIN: ${file(./env.json):auth0-domain, ssm:/cla-auth0-domain-${opt:stage}} + AUTH0_CLIENT_ID: ${file(./env.json):auth0-clientId, ssm:/cla-auth0-clientId-${opt:stage}} AUTH0_USERNAME_CLAIM: ${file(./env.json):auth0-username-claim, ssm:/cla-auth0-username-claim-${opt:stage}} AUTH0_ALGORITHM: ${file(./env.json):auth0-algorithm, ssm:/cla-auth0-algorithm-${opt:stage}} - SF_INSTANCE_URL: ${file(./env.json):sf-instance-url, ssm:/cla-sf-instance-url-${opt:stage}~true} - SF_CLIENT_ID: ${file(./env.json):sf-client-id, ssm:/cla-sf-consumer-key-${opt:stage}~true} - SF_CLIENT_SECRET: ${file(./env.json):sf-client-secret, ssm:/cla-sf-consumer-secret-${opt:stage}~true} - SF_USERNAME: ${file(./env.json):sf-username, ssm:/cla-sf-username-${opt:stage}~true} - SF_PASSWORD: ${file(./env.json):sf-password, ssm:/cla-sf-password-${opt:stage}~true} + SF_INSTANCE_URL: ${file(./env.json):sf-instance-url, ssm:/cla-sf-instance-url-${opt:stage}} + SF_CLIENT_ID: ${file(./env.json):sf-client-id, ssm:/cla-sf-consumer-key-${opt:stage}} + SF_CLIENT_SECRET: ${file(./env.json):sf-client-secret, ssm:/cla-sf-consumer-secret-${opt:stage}} + SF_USERNAME: ${file(./env.json):sf-username, ssm:/cla-sf-username-${opt:stage}} + SF_PASSWORD: ${file(./env.json):sf-password, ssm:/cla-sf-password-${opt:stage}} DOCRAPTOR_API_KEY: ${file(./env.json):doc-raptor-api-key, ssm:/cla-doc-raptor-api-key-${opt:stage}} DOCUSIGN_ROOT_URL: ${file(./env.json):docusign-root-url, ssm:/cla-docusign-root-url-${opt:stage}} DOCUSIGN_USERNAME: ${file(./env.json):docusign-username, ssm:/cla-docusign-username-${opt:stage}} @@ -320,8 +324,8 @@ provider: CLA_CORPORATE_BASE: ${file(./env.json):cla-corporate-base, ssm:/cla-corporate-base-${opt:stage}} CLA_CORPORATE_V2_BASE: ${file(./env.json):cla-corporate-v2-base, ssm:/cla-corporate-v2-base-${opt:stage}} CLA_LANDING_PAGE: ${file(./env.json):cla-landing-page, ssm:/cla-landing-page-${opt:stage}} - CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${opt:stage}~true} - CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${opt:stage}~true} + CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${opt:stage}} + CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${opt:stage}} SES_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-ses-sender-email-address, ssm:/cla-ses-sender-email-address-${opt:stage}} SMTP_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-smtp-sender-email-address, ssm:/cla-smtp-sender-email-address-${opt:stage}} LF_GROUP_CLIENT_ID: ${file(./env.json):lf-group-client-id, ssm:/cla-lf-group-client-id-${opt:stage}} @@ -391,8 +395,8 @@ functions: runtime: go1.x package: individually: true - include: - - auth/bin/** + patterns: + - 'auth/bin/**' apiv3: runtime: go1.x @@ -405,8 +409,8 @@ functions: # cors: true # CORS handled at the API implementation package: individually: true - include: - - ./bin/backend-aws-lambda + patterns: + - './bin/backend-aws-lambda' dynamo-projects-events-lambda: handler: dynamo-events-lambda @@ -415,8 +419,8 @@ functions: runtime: go1.x package: individually: true - include: - - ./bin/dynamo-events-lambda + patterns: + - './bin/dynamo-events-lambda' dynamo-signatures-events-lambda: handler: dynamo-events-lambda @@ -425,8 +429,8 @@ functions: runtime: go1.x package: individually: true - include: - - ./bin/dynamo-events-lambda + patterns: + - './bin/dynamo-events-lambda' dynamo-events-events-lambda: handler: dynamo-events-lambda @@ -435,8 +439,8 @@ functions: runtime: go1.x package: individually: true - include: - - ./bin/dynamo-events-lambda + patterns: + - './bin/dynamo-events-lambda' dynamo-repositories-events-lambda: handler: dynamo-events-lambda @@ -445,8 +449,8 @@ functions: runtime: go1.x package: individually: true - include: - - ./bin/dynamo-events-lambda + patterns: + - './bin/dynamo-events-lambda' dynamo-projects-cla-groups-events-lambda: handler: dynamo-events-lambda @@ -455,8 +459,8 @@ functions: runtime: go1.x package: individually: true - include: - - ./bin/dynamo-events-lambda + patterns: + - './bin/dynamo-events-lambda' dynamo-github-orgs-events-lambda: handler: dynamo-events-lambda @@ -465,8 +469,8 @@ functions: runtime: go1.x package: individually: true - include: - - ./bin/dynamo-events-lambda + patterns: + - './bin/dynamo-events-lambda' saveMetrics: description: "EasyCLA Save Metrics API handler" @@ -480,8 +484,8 @@ functions: enabled: true package: individually: true - include: - - ./bin/metrics-aws-lambda + patterns: + - './bin/metrics-aws-lambda' reportMetrics: description: "EasyCLA Report Metrics API handler" @@ -495,8 +499,8 @@ functions: enabled: true package: individually: true - include: - - ./bin/metrics-report-lambda + patterns: + - './bin/metrics-report-lambda' zipbuilder-scheduler-lambda: handler: zipbuilder-scheduler-lambda @@ -511,8 +515,8 @@ functions: enabled: true package: individually: true - include: - - ./bin/zipbuilder-scheduler-lambda + patterns: + - './bin/zipbuilder-scheduler-lambda' zipbuilder-lambda: handler: zipbuilder-lambda @@ -523,8 +527,8 @@ functions: memorySize: 1024 package: individually: true - include: - - ./bin/zipbuilder-lambda + patterns: + - './bin/zipbuilder-lambda' gitlab-repository-check-lambda: handler: gitlab-repository-check-lambda @@ -540,8 +544,8 @@ functions: enabled: true package: individually: true - include: - - ./bin/gitlab-repository-check-lambda + patterns: + - './bin/gitlab-repository-check-lambda' apiv1: handler: wsgi_handler.handler @@ -605,8 +609,8 @@ functions: description: Update easycla user data to user object in dynamodb package: individually: true - include: - - ./bin/user-subscribe-lambda + patterns: + - './bin/user-subscribe-lambda' reservedConcurrency: 5 events: - sns: diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index c7e1bab72..2e1b0fbf7 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -425,39 +425,38 @@ node-fetch "^2.6.0" shortid "^2.2.14" -"@serverless/components@^3.4.7": - version "3.4.7" - resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.4.7.tgz#9e5d9a58951000d9b5bcea78cad56f62d7dd5633" - integrity sha512-jY3+K3juQAa1HpFbvc1kztyDi4SFqG1+1GzUwh/kpRTlz2A01GnekWm8mf47l9HKxRzMxqVveg37wyyIQpw4xg== - dependencies: - "@serverless/platform-client" "^3.1.5" - "@serverless/platform-client-china" "^2.0.9" - "@serverless/platform-sdk" "^2.3.2" - "@serverless/utils" "^2.2.0" - adm-zip "^0.4.16" +"@serverless/components@^3.16.0": + version "3.16.0" + resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.16.0.tgz#a161481f73ffa9e1d62c447b3d8b6bacbf701ba6" + integrity sha512-vgsfR0V4dierB97GKgtfFp/s5XN3zmEQlhRfshWLHjoXvJe2HvWOZXy5Bqyzb1WMr6S0dp/wXQzcnVStbiyBaw== + dependencies: + "@serverless/platform-client" "^4.2.2" + "@serverless/platform-client-china" "^2.2.0" + "@serverless/utils" "^4.0.0" + adm-zip "^0.5.4" ansi-escapes "^4.3.1" - aws4 "^1.11.0" chalk "^4.1.0" child-process-ext "^2.1.1" - chokidar "^3.5.0" + chokidar "^3.5.1" + ci-info "^3.2.0" + dayjs "^1.10.4" dotenv "^8.2.0" + fastest-levenshtein "^1.0.12" figures "^3.2.0" - fs-extra "^9.0.1" - globby "^11.0.2" - got "^11.8.1" + fs-extra "^9.1.0" + got "^11.8.2" graphlib "^2.1.8" https-proxy-agent "^5.0.0" - ini "^1.3.8" inquirer-autocomplete-prompt "^1.3.0" js-yaml "^3.14.1" memoizee "^0.4.14" minimist "^1.2.5" - moment "^2.29.1" open "^7.3.1" prettyoutput "^1.2.0" ramda "^0.27.1" semver "^7.3.4" strip-ansi "^6.0.0" + tencent-serverless-http "^1.3.1" traverse "^0.6.6" uuid "^8.3.2" @@ -472,30 +471,30 @@ ramda "^0.26.1" semver "^6.1.1" -"@serverless/enterprise-plugin@^4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@serverless/enterprise-plugin/-/enterprise-plugin-4.4.2.tgz#ec635a2099e63ecd6a82a005272cbfad8cbdfac6" - integrity sha512-w5xD8R8tFK6B7QiLvWI5jqVHTtH1LdTyGp5eRcjkdJBa10/D2IZFpJimMAGsBxk9U1JGKO4j0miVnRHIW8ppeg== +"@serverless/dashboard-plugin@^5.4.4": + version "5.4.4" + resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.4.4.tgz#787d0214175f520776c46ae0ca38bf7a981f8d54" + integrity sha512-1lLChYK/zwrF5SEAubVr9Oz/xGnq1Yjbw36X1iz0j/+jwbtpt1AeumksArA3UVAgDSecVOfaksxUta2cc10pRA== dependencies: "@serverless/event-mocks" "^1.1.1" - "@serverless/platform-client" "^3.1.5" - "@serverless/platform-sdk" "^2.3.2" - chalk "^4.1.0" + "@serverless/platform-client" "^4.3.0" + "@serverless/utils" "^5.7.0" + chalk "^4.1.2" child-process-ext "^2.1.1" - chokidar "^3.5.0" + chokidar "^3.5.2" cli-color "^2.0.0" flat "^5.0.2" - fs-extra "^9.0.1" - js-yaml "^3.14.1" - jszip "^3.5.0" - lodash "^4.17.20" - memoizee "^0.4.14" - ncjsm "^4.1.0" + fs-extra "^9.1.0" + js-yaml "^4.1.0" + jszip "^3.7.1" + lodash "^4.17.21" + memoizee "^0.4.15" + ncjsm "^4.2.0" node-dir "^0.1.17" node-fetch "^2.6.1" - open "^7.3.0" - semver "^7.3.4" - simple-git "^2.31.0" + open "^7.4.2" + semver "^7.3.5" + simple-git "^2.44.0" uuid "^8.3.2" yamljs "^0.3.0" @@ -507,63 +506,47 @@ "@types/lodash" "^4.14.123" lodash "^4.17.11" -"@serverless/platform-client-china@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@serverless/platform-client-china/-/platform-client-china-2.0.9.tgz#473b9413781bec62c61c57b9d6ce00eb691f6f7d" - integrity sha512-qec3a5lVaMH0nccgjVgvcEF8M+M95BXZbbYDGypVHEieJQxrKqj057+VVKsiHBeHYXzr4B3v6pIyQHst40vpIw== +"@serverless/platform-client-china@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@serverless/platform-client-china/-/platform-client-china-2.2.3.tgz#686bece20a3b4271766a591ee4e5331bd1090f6a" + integrity sha512-MoDxI0d3HMesHwdyk7gzwLNU0wuJk/ovL9+XD/gAZgnrdZpFFq9kwnJAm2midL7jdhpnEFu/aoGJ0eE+xtQJ7w== dependencies: - "@serverless/utils-china" "^1.0.11" + "@serverless/utils-china" "^1.1.4" + adm-zip "^0.5.1" archiver "^5.0.2" + axios "^0.21.1" dotenv "^8.2.0" + fast-glob "^3.2.4" fs-extra "^9.0.1" https-proxy-agent "^5.0.0" js-yaml "^3.14.0" minimatch "^3.0.4" querystring "^0.2.0" + run-parallel-limit "^1.0.6" traverse "^0.6.6" urlencode "^1.1.0" ws "^7.3.1" -"@serverless/platform-client@^3.1.5": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-3.4.0.tgz#8c6c94bcbf8e22a06c07b1009c500aef238024d7" - integrity sha512-iOMsluUqf7rQDalDwTRA+fuAHxk8WXCPXnMFDuTf/34q/1uRCx/xJhBNIvEUIbzZnSjiykfTIXUAcJ6kKbh6qA== +"@serverless/platform-client@^4.2.2", "@serverless/platform-client@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-4.3.0.tgz#5eda7b7eb8bdd0da93f2ecf2130b4131349806a1" + integrity sha512-q2CMqCkKeBaKA/UwfJAZLkdUsbghSbiYPvAX4rl9rsR5APm4KWtjKQP9CTOtVO5JRMWYoysK6jF0d5VJOABRzQ== dependencies: - adm-zip "^0.4.13" - archiver "^5.0.0" + adm-zip "^0.5.5" + archiver "^5.3.0" axios "^0.21.1" - fast-glob "^3.2.4" + fast-glob "^3.2.7" https-proxy-agent "^5.0.0" ignore "^5.1.8" isomorphic-ws "^4.0.1" - js-yaml "^3.13.1" + js-yaml "^3.14.1" jwt-decode "^2.2.0" minimatch "^3.0.4" - querystring "^0.2.0" - run-parallel-limit "^1.0.6" + querystring "^0.2.1" + run-parallel-limit "^1.1.0" throat "^5.0.0" traverse "^0.6.6" - ws "^7.2.1" - -"@serverless/platform-sdk@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@serverless/platform-sdk/-/platform-sdk-2.3.2.tgz#d53e37c910e66687e0cc398c3b83fde9d7357806" - integrity sha512-JSX0/EphGVvnb4RAgZYewtBXPuVsU2TFCuXh6EEZ4jxK3WgUwNYeYdwB8EuVLrm1/dYqu/UWUC0rPKb+ZDycJg== - dependencies: - chalk "^2.4.2" - https-proxy-agent "^4.0.0" - is-docker "^1.1.0" - jwt-decode "^2.2.0" - node-fetch "^2.6.1" - opn "^5.5.0" - querystring "^0.2.0" - ramda "^0.25.0" - rc "^1.2.8" - regenerator-runtime "^0.13.7" - source-map-support "^0.5.19" - uuid "^3.4.0" - write-file-atomic "^2.4.3" - ws "<7.0.0" + ws "^7.5.3" "@serverless/template@^1.1.3": version "1.1.3" @@ -575,16 +558,17 @@ graphlib "^2.1.7" traverse "^0.6.6" -"@serverless/utils-china@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@serverless/utils-china/-/utils-china-1.0.11.tgz#368003260ccd1df55f7477da50d0b606f157e58b" - integrity sha512-raOPIoPSTrkWKBDuozkYWvLXP2W65K9Uk4ud+lPcbhhBSamO3uVW40nuAkC19MdIoAsFi5oTGYpcc9UDx8b+lg== +"@serverless/utils-china@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@serverless/utils-china/-/utils-china-1.1.4.tgz#5a76c90d5d0f33c144fe0e82c799251a46415adb" + integrity sha512-8s73M1k+mST7Z/Rp8wgmZh50tjpwX+fqsbYYRuFGgyuWTvgqAlUflDOWAeQuDx4pEndWEqjbG09ZrZNqlHuZqQ== dependencies: - "@tencent-sdk/capi" "^1.1.2" + "@tencent-sdk/capi" "^1.1.8" dijkstrajs "^1.0.1" dot-qs "0.2.0" duplexify "^4.1.1" end-of-stream "^1.4.4" + got "^11.8.2" https-proxy-agent "^5.0.0" kafka-node "^5.0.0" protobufjs "^6.9.0" @@ -604,18 +588,45 @@ uuid "^3.4.0" write-file-atomic "^2.4.3" -"@serverless/utils@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-2.2.0.tgz#80dba2a98307f9987e8c8e399381a9302dd4a39f" - integrity sha512-0TqmLwH9r2GAewvz9mhZ+TSyQBoE9ANuB4nNhn6lJvVUgzlzji3aqeFbAuDt+Z60ZkaIDNipU/J5Vf2Lo/QTQQ== +"@serverless/utils@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-4.1.0.tgz#99574f446509eac8935f58013399d7af89945b4b" + integrity sha512-cl5uPaGg72z0sCUpF0zsOhwYYUV72Gxc1FwFfxltO8hSvMeFDvwD7JrNE4kHcIcKRjwPGbSH0fdVPUpErZ8Mog== dependencies: chalk "^4.1.0" + ci-info "^3.1.1" inquirer "^7.3.3" - js-yaml "^4.0.0" - lodash "^4.17.20" + js-yaml "^4.1.0" + jwt-decode "^3.1.2" + lodash "^4.17.21" ncjsm "^4.1.0" - rc "^1.2.8" - type "^2.1.0" + type "^2.5.0" + uuid "^8.3.2" + write-file-atomic "^3.0.3" + +"@serverless/utils@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.7.0.tgz#eeef23bc85c16501716c107c262b80d127bf3562" + integrity sha512-4/9lTag4NNMtgoK7qRSoP//VplnKUTqgKMJ5pjvuXHFTBNoGYbdi5Cr1UmbHwnG8FfYBUy95jNUHjSEeUXDqgg== + dependencies: + archive-type "^4.0.0" + chalk "^4.1.2" + ci-info "^3.2.0" + content-disposition "^0.5.3" + decompress "^4.2.1" + ext-name "^5.0.0" + file-type "^16.5.3" + filenamify "^4.3.0" + get-stream "^6.0.1" + got "^11.8.2" + inquirer "^7.3.3" + js-yaml "^4.1.0" + jwt-decode "^3.1.2" + lodash "^4.17.21" + make-dir "^3.1.0" + ncjsm "^4.2.0" + p-event "^4.2.0" + type "^2.5.0" uuid "^8.3.2" write-file-atomic "^3.0.3" @@ -624,11 +635,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - "@sindresorhus/is@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" @@ -648,16 +654,21 @@ dependencies: defer-to-connect "^2.0.0" -"@tencent-sdk/capi@^1.1.2": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@tencent-sdk/capi/-/capi-1.1.5.tgz#ba2932e292deb659d3e9968b70d9a6ec54d47c66" - integrity sha512-cHkoMY/1L5VxeiKv51uKxbFK8lZ7pZbY3CukzOHro8YKT6dETKYzTGO/F8jDhH7r8vKWxuA+ZcALzxYuVlmwsg== +"@tencent-sdk/capi@^1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@tencent-sdk/capi/-/capi-1.1.8.tgz#955130f9c7da88a599c05b3eae01b2f0e15a9beb" + integrity sha512-AmyMQndtxMsM59eDeA0gGiw8T2LzNvDhx/xl+ygFXXrsw+yb/mit73ndHkiHKcRA1EpNHTyD1PN9ATxghzplfg== dependencies: "@types/request" "^2.48.3" "@types/request-promise-native" "^1.0.17" request "^2.88.0" request-promise-native "^1.0.8" +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@types/cacheable-request@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" @@ -739,21 +750,16 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== -adm-zip@^0.4.13, adm-zip@^0.4.16: - version "0.4.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" - integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== +adm-zip@^0.5.1, adm-zip@^0.5.4, adm-zip@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.5.tgz#b6549dbea741e4050309f1bb4d47c47397ce2c4f" + integrity sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w== after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - agent-base@6: version "6.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" @@ -1057,6 +1063,14 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + appdirectory@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/appdirectory/-/appdirectory-0.1.0.tgz#eb6c816320e7b2ab16f5ed997f28d8205df56375" @@ -1103,7 +1117,7 @@ archiver@^3.0.0: tar-stream "^2.1.0" zip-stream "^2.1.2" -archiver@^5.0.0, archiver@^5.0.2: +archiver@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.0.2.tgz#b2c435823499b1f46eb07aa18e7bcb332f6ca3fc" integrity sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg== @@ -1116,18 +1130,18 @@ archiver@^5.0.0, archiver@^5.0.2: tar-stream "^2.1.4" zip-stream "^4.0.0" -archiver@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.2.0.tgz#25aa1b3d9febf7aec5b0f296e77e69960c26db94" - integrity sha512-QEAKlgQuAtUxKeZB9w5/ggKXh21bZS+dzzuQ0RPBC20qtDCbTyzqmisoeJP46MP39fg4B4IcyvR+yeyEBdblsQ== +archiver@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" + integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== dependencies: archiver-utils "^2.1.0" async "^3.2.0" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" readdir-glob "^1.0.0" - tar-stream "^2.1.4" - zip-stream "^4.0.4" + tar-stream "^2.2.0" + zip-stream "^4.1.0" are-we-there-yet@~1.1.2: version "1.1.5" @@ -1240,10 +1254,10 @@ aws-sdk@^2.756.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.828.0: - version "2.828.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.828.0.tgz#6aa599c3582f219568f41fb287eb65753e4a9234" - integrity sha512-JoDujGdncSIF9ka+XFZjop/7G+fNGucwPwYj7OHYMmFIOV5p7YmqomdbVmH/vIzd988YZz8oLOinWc4jM6vvhg== +aws-sdk@^2.979.0: + version "2.980.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" + integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== dependencies: buffer "4.9.2" events "1.1.1" @@ -1260,11 +1274,6 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - aws4@^1.8.0: version "1.10.1" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" @@ -1373,10 +1382,10 @@ boxen@^4.2.0: type-fest "^0.8.1" widest-line "^3.1.0" -boxen@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.0.tgz#64fe9b16066af815f51057adcc800c3730120854" - integrity sha512-5bvsqw+hhgUi3oYGK0Vf4WpIkyemp60WBInn7+WNfoISzAqk/HX4L7WNROq38E6UR/y3YADpv6pEm4BfkeEAdA== +boxen@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" + integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== dependencies: ansi-align "^3.0.0" camelcase "^6.2.0" @@ -1430,11 +1439,6 @@ buffer-fill@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - buffer@4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -1469,24 +1473,16 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== +builtin-modules@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + cacheable-lookup@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3" integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w== -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -1565,6 +1561,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -1605,20 +1609,20 @@ chokidar@^3.4.1: optionalDependencies: fsevents "~2.1.2" -chokidar@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== +chokidar@^3.5.1, chokidar@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" chownr@^1.0.1: version "1.1.4" @@ -1635,11 +1639,28 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.1.1, ci-info@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + cli-boxes@^2.2.0, cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-color@^1.4: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" + integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== + dependencies: + ansi-regex "^2.1.1" + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.5" + cli-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" @@ -1666,6 +1687,17 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-progress-footer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz#8aa2292f5cca4f6639e3e0107ef04d897d620563" + integrity sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg== + dependencies: + cli-color "^1.4" + d "1" + es5-ext "^0.10.47" + process-utils "^2.0.1" + timers-ext "^0.1.7" + cli-width@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" @@ -1704,7 +1736,7 @@ clone-deep@^4.0.0: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@1.0.2, clone-response@^1.0.2: +clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= @@ -1844,13 +1876,13 @@ compress-commons@^4.0.0: normalize-path "^3.0.0" readable-stream "^3.6.0" -compress-commons@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.0.2.tgz#d6896be386e52f37610cef9e6fa5defc58c31bd7" - integrity sha512-qhd32a9xgzmpfoga1VQEiLEwdKZ6Plnpx5UCgIsf89FSolyJ7WnifY4Gtjgv5WR6hWAyRaHxC5MiEhU/38U70A== +compress-commons@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== dependencies: buffer-crc32 "^0.2.13" - crc32-stream "^4.0.1" + crc32-stream "^4.0.2" normalize-path "^3.0.0" readable-stream "^3.6.0" @@ -1876,7 +1908,7 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-disposition@^0.5.2: +content-disposition@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -1922,10 +1954,10 @@ crc32-stream@^4.0.0: crc "^3.4.4" readable-stream "^3.4.0" -crc32-stream@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.1.tgz#0f047d74041737f8a55e86837a1b826bd8ab0067" - integrity sha512-FN5V+weeO/8JaXsamelVYO1PHyeCsuL3HcG4cqsj0ceARcocxalaShCsohZMSAF+db7UYFwBy1rARK/0oFItUw== +crc32-stream@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== dependencies: crc-32 "^1.2.0" readable-stream "^3.4.0" @@ -1995,10 +2027,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.3.tgz#cf3357c8e7f508432826371672ebf376cb7d619b" - integrity sha512-/2fdLN987N8Ki7Id8BUN2nhuiRyxTLumQnSQf9CNncFCyqFsSKb9TNhzRYcC8K8eJSJOKvbvkImo/MKKhNi4iw== +dayjs@^1.10.4, dayjs@^1.10.6: + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: version "4.3.1" @@ -2208,28 +2240,21 @@ dot-qs@0.2.0: resolved "https://registry.yarnpkg.com/dot-qs/-/dot-qs-0.2.0.tgz#d36517fe24b7cda61fce7a5026a0024afaf5a439" integrity sha1-02UX/iS3zaYfznpQJqACSvr1pDk= +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -download@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/download/-/download-8.0.0.tgz#afc0b309730811731aae9f5371c9f46be73e51b1" - integrity sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA== - dependencies: - archive-type "^4.0.0" - content-disposition "^0.5.2" - decompress "^4.2.1" - ext-name "^5.0.0" - file-type "^11.1.0" - filenamify "^3.0.0" - get-stream "^4.1.0" - got "^8.3.1" - make-dir "^2.1.0" - p-event "^2.1.0" - pify "^4.0.1" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -2520,6 +2545,13 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" +ext@^1.1.0, ext@^1.4.0, ext@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" + integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== + dependencies: + type "^2.5.0" + ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -2575,6 +2607,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.4: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2623,10 +2666,14 @@ figures@^3.0.0, figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" -file-type@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8" - integrity sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g== +file-type@^16.5.3: + version "16.5.3" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06" + integrity sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" file-type@^3.8.0: version "3.9.0" @@ -2658,19 +2705,19 @@ filename-reserved-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= -filenamify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-3.0.0.tgz#9603eb688179f8c5d40d828626dcbb92c3a4672c" - integrity sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g== +filenamify@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" + integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== dependencies: filename-reserved-regex "^2.0.0" - strip-outer "^1.0.0" + strip-outer "^1.0.1" trim-repeated "^1.0.0" -filesize@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" - integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== +filesize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.0.tgz#532db71cb8a04df7d403da054a28de1b648534e0" + integrity sha512-sb690gQx3y/5KZIztgWAKM/r4Hf1V3R8mkAE0OhasMw2FDYduFTYCji8YN9BVpsGoMxrHPFvia1BMxwfLHX+fQ== fill-range@^7.0.1: version "7.0.1" @@ -2750,14 +2797,6 @@ formidable@^1.2.0: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== -from2@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -2798,6 +2837,16 @@ fs-extra@^9.0.0, fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -2823,15 +2872,28 @@ fs2@^0.3.8: memoizee "^0.4.14" type "^2.0.0" +fs2@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/fs2/-/fs2-0.3.9.tgz#3869e5b2ec7e0622eaa5f4373df540d3d427a9fb" + integrity sha512-WsOqncODWRlkjwll+73bAxVW3JPChDgaPX3DT4iTTm73UmG4VgALa7LaFblP232/DN60itkOrPZ8kaP1feksGQ== + dependencies: + d "^1.0.1" + deferred "^0.7.11" + es5-ext "^0.10.53" + event-emitter "^0.3.5" + ignore "^5.1.8" + memoizee "^0.4.14" + type "^2.1.0" + fsevents@~2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== -fsevents@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f" - integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" @@ -2862,11 +2924,6 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -2889,6 +2946,11 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2909,7 +2971,7 @@ glob-all@^3.1.0: glob "^7.1.2" yargs "^15.3.1" -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2935,10 +2997,10 @@ global-dirs@^2.0.1: dependencies: ini "^1.3.5" -globby@^11.0.2: - version "11.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" - integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og== +globby@^11.0.4: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -2947,10 +3009,10 @@ globby@^11.0.2: merge2 "^1.3.0" slash "^3.0.0" -got@^11.8.1: - version "11.8.1" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" - integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q== +got@^11.8.2: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" @@ -2964,29 +3026,6 @@ got@^11.8.1: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^8.3.1: - version "8.3.2" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -3004,11 +3043,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + graphlib@^2.1.7, graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -3051,23 +3095,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -3092,11 +3124,6 @@ hasbin@^1.2.3: dependencies: async "~1.5" -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -3119,14 +3146,6 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" @@ -3152,6 +3171,11 @@ ieee754@1.1.13, ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" @@ -3190,12 +3214,12 @@ info-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/info-symbol/-/info-symbol-0.1.0.tgz#27841d72867ddb4242cd612d79c10633881c6a78" integrity sha1-J4QdcoZ920JCzWEtecEGM4gcang= -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.5, ini@^1.3.7, ini@^1.3.8, ini@~1.3.0: +ini@^1.3.5, ini@^1.3.7, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== @@ -3254,14 +3278,6 @@ install@^0.13.0: resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776" integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA== -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3342,16 +3358,16 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-docker@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-1.1.0.tgz#f04374d4eee5310e9a8e113bf1495411e46176a1" - integrity sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE= - -is-docker@^2.0.0, is-docker@^2.1.1: +is-docker@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== +is-docker@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -3438,11 +3454,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= - is-path-inside@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" @@ -3472,11 +3483,6 @@ is-regex@^1.1.1: dependencies: has-symbols "^1.0.1" -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3561,14 +3567,6 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - java-invoke-local@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/java-invoke-local/-/java-invoke-local-0.0.6.tgz#0e04b20b5e306a1e8384846a9ac286790ee6d868" @@ -3600,10 +3598,10 @@ js-yaml@^3.14.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" - integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -3708,7 +3706,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.1.0, jszip@^3.2.2, jszip@^3.5.0: +jszip@^3.1.0, jszip@^3.2.2, jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -3740,6 +3738,11 @@ jwt-decode@^2.2.0: resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk= +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + kafka-node@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/kafka-node/-/kafka-node-5.0.0.tgz#4b6f65cc1d77ebe565859dfb8f9575ed15d543c0" @@ -3761,13 +3764,6 @@ kafka-node@^5.0.0: optionalDependencies: snappy "^6.0.1" -keyv@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" - integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== - dependencies: - json-buffer "3.0.0" - keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -3921,7 +3917,7 @@ lodash.values@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= -lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: +lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3985,11 +3981,6 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -4026,15 +4017,7 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -4060,6 +4043,11 @@ md5-file@^4.0.0: resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-4.0.0.tgz#f3f7ba1e2dd1144d5bf1de698d0e5f44a4409584" integrity sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg== +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + mem@^6.0.1: version "6.1.1" resolved "https://registry.yarnpkg.com/mem/-/mem-6.1.1.tgz#ea110c2ebc079eca3022e6b08c85a795e77f6318" @@ -4119,11 +4107,24 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +mime-db@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== + mime-db@1.x.x, mime-db@^1.28.0: version "1.45.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" @@ -4136,6 +4137,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.44.0" +mime-types@~2.1.24: + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== + dependencies: + mime-db "1.49.0" + mime@^1.2.11, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -4220,7 +4228,7 @@ moment-timezone@^0.5.31: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.29.1: +"moment@>= 2.9.0": version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -4278,6 +4286,19 @@ ncjsm@^4.1.0: fs2 "^0.3.8" type "^2.0.0" +ncjsm@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ncjsm/-/ncjsm-4.2.0.tgz#7b2d752c3a42db5f6a2c5ff6934cf66fb1bb5e38" + integrity sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw== + dependencies: + builtin-modules "^3.2.0" + deferred "^0.7.11" + es5-ext "^0.10.53" + es6-set "^0.1.5" + find-requires "^1.0.0" + fs2 "^0.3.9" + type "^2.5.0" + nested-error-stacks@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" @@ -4344,7 +4365,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@2.0.1, normalize-url@^4.1.0, normalize-url@^4.5.1: +normalize-url@^4.1.0, normalize-url@^4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== @@ -4390,10 +4411,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" - integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== object-inspect@^1.8.0: version "1.8.0" @@ -4458,14 +4479,6 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -open@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" - integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - open@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/open/-/open-7.3.1.tgz#111119cb919ca1acd988f49685c4fdd0f4755356" @@ -4474,12 +4487,13 @@ open@^7.3.1: is-docker "^2.0.0" is-wsl "^2.1.1" -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: - is-wsl "^1.1.0" + is-docker "^2.0.0" + is-wsl "^2.1.1" optional@^0.1.3: version "0.1.4" @@ -4496,11 +4510,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -4516,23 +4525,18 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= -p-event@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6" - integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== +p-event@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" + integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== dependencies: - p-timeout "^2.0.1" + p-timeout "^3.1.0" p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -4540,13 +4544,6 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -4578,14 +4575,7 @@ p-retry@^4.2.0: "@types/retry" "^0.12.0" retry "^0.12.0" -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== - dependencies: - p-finally "^1.0.0" - -p-timeout@^3.2.0: +p-timeout@^3.1.0, p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== @@ -4655,6 +4645,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path2@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/path2/-/path2-0.1.0.tgz#639828942cdbda44a41a45b074ae8873483b4efa" + integrity sha1-Y5golCzb2kSkGkWwdK6Ic0g7Tvo= + +peek-readable@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.0.1.tgz#9a045f291db254111c3412c1ce4fec27ddd4d202" + integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -4670,6 +4670,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -4680,11 +4685,6 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -4769,6 +4769,24 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-utils@^2.0.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-2.6.0.tgz#0f0d3b2633ec1f4656def20d36425f92ec60cf23" + integrity sha512-2zKFADQDvHiUDyJQTsBTdu1+Q2D/WtReBotZwXmD9oUueb0kNv4rXulK/78hMM+nclBNFZ/ZlHOJtobt8oHpqQ== + dependencies: + ext "^1.1.0" + type "^2.0.0" + +process-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-4.0.0.tgz#3e5b204e1d38e62fe39ef3144664a1fe94097b9e" + integrity sha512-fMyMQbKCxX51YxR7YGCzPjLsU3yDzXFkP4oi1/Mt5Ixnk7GO/7uUTj8mrCHUwuvozWzI+V7QSJR9cZYnwNOZPg== + dependencies: + ext "^1.4.0" + fs2 "^0.3.9" + memoizee "^0.4.14" + type "^2.1.0" + promise-queue@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4" @@ -4924,6 +4942,16 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystring@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -4938,11 +4966,6 @@ radio-symbol@^2.0.0: ansi-green "^0.1.1" is-windows "^1.0.1" -ramda@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9" - integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ== - ramda@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" @@ -4985,6 +5008,13 @@ readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -4999,6 +5029,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + readline-ui@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/readline-ui/-/readline-ui-2.2.3.tgz#9e873a7668bbd8ca8a5573ce810a6bafb70a5089" @@ -5024,7 +5061,7 @@ readline-utils@^2.2.1, readline-utils@^2.2.3: strip-color "^0.1.0" window-size "^1.1.0" -regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== @@ -5105,7 +5142,7 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== -responselike@1.0.2, responselike@^1.0.2: +responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -5167,6 +5204,13 @@ run-parallel-limit@^1.0.6: resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.6.tgz#0982a893d825b050cbaff1a35414832b195541b6" integrity sha512-yFFs4Q2kECi5mWXyyZj3UlAZ5OFq5E07opABC+EmhZdjEkrxXaUwFqOaaNF4tbayMnBxrsbujpeCYTVjGufZGQ== +run-parallel-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" + integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw== + dependencies: + queue-microtask "^1.2.2" + run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -5245,6 +5289,13 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + serverless-domain-manager@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/serverless-domain-manager/-/serverless-domain-manager-5.1.0.tgz#44c975b4adeac6b32507c92313ee001735d56c55" @@ -5355,61 +5406,66 @@ serverless-wsgi@^1.5.2: hasbin "^1.2.3" lodash "^4.17.15" -serverless@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.19.0.tgz#c464f0abac97f1a2da9d009cac17541e8b78050d" - integrity sha512-JvNB+llJXIfsMk6weTh5/aCMEvTGnizQ/ZHfyyXhLuBHm0cAa9h6bpyBagnC5CTtV++jwCR2WKu2a0SQQEmEvA== +serverless@^2.57.0: + version "2.57.0" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.57.0.tgz#74b5347f50969d81869762f53c92221d92e9999a" + integrity sha512-/gLzaqdisNlymMiQJMkFoACy7NXkOd7E67sbcOrENXuVb48QAS4PTT8VPkMNu6eQonlphGagbKhpqVmKzHJDmQ== dependencies: "@serverless/cli" "^1.5.2" - "@serverless/components" "^3.4.7" - "@serverless/enterprise-plugin" "^4.4.2" - "@serverless/utils" "^2.2.0" + "@serverless/components" "^3.16.0" + "@serverless/dashboard-plugin" "^5.4.4" + "@serverless/platform-client" "^4.3.0" + "@serverless/utils" "^5.7.0" ajv "^6.12.6" ajv-keywords "^3.5.2" - archiver "^5.2.0" - aws-sdk "^2.828.0" + archiver "^5.3.0" + aws-sdk "^2.979.0" bluebird "^3.7.2" - boxen "^5.0.0" + boxen "^5.0.1" cachedir "^2.3.0" - chalk "^4.1.0" + chalk "^4.1.2" child-process-ext "^2.1.1" + ci-info "^3.2.0" + cli-progress-footer "^1.1.1" d "^1.0.1" - dayjs "^1.10.3" + dayjs "^1.10.6" decompress "^4.2.1" - dotenv "^8.2.0" - download "^8.0.0" + dotenv "^10.0.0" + dotenv-expand "^5.1.0" essentials "^1.1.1" + ext "^1.5.0" fastest-levenshtein "^1.0.12" - filesize "^6.1.0" - fs-extra "^9.0.1" + filesize "^8.0.0" + fs-extra "^9.1.0" get-stdin "^8.0.0" - globby "^11.0.2" - got "^11.8.1" - graceful-fs "^4.2.4" + globby "^11.0.4" + got "^11.8.2" + graceful-fs "^4.2.8" https-proxy-agent "^5.0.0" - is-docker "^2.1.1" + is-docker "^2.2.1" is-wsl "^2.2.0" - js-yaml "^4.0.0" + js-yaml "^4.1.0" json-cycle "^1.3.0" json-refs "^3.0.15" - lodash "^4.17.20" + lodash "^4.17.21" memoizee "^0.4.15" - micromatch "^4.0.2" - ncjsm "^4.1.0" + micromatch "^4.0.4" + ncjsm "^4.2.0" node-fetch "^2.6.1" - object-hash "^2.1.1" - p-limit "^3.1.0" + object-hash "^2.2.0" + path2 "^0.1.0" + process-utils "^4.0.0" promise-queue "^2.2.5" replaceall "^0.1.6" - semver "^7.3.4" + semver "^7.3.5" + signal-exit "^3.0.3" tabtab "^3.0.2" - tar "^6.1.0" + tar "^6.1.11" timers-ext "^0.1.7" - type "^2.1.0" + type "^2.5.0" untildify "^4.0.0" uuid "^8.3.2" yaml-ast-parser "0.0.43" - yargs-parser "^20.2.4" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -5487,7 +5543,7 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -5506,10 +5562,10 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.31.0: - version "2.31.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.31.0.tgz#3e5954c1e36c76fb382c08eaa2749a206db9f613" - integrity sha512-/+rmE7dYZMbRAfEmn8EUIOwlM2G7UdzpkC60KF86YAfXGnmGtsPrKsym0hKvLBdFLLW019C+aZld1+6iIVy5xA== +simple-git@^2.44.0: + version "2.45.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.45.0.tgz#4d53b146fc23496099ebfc7af5b0d605d0e3e504" + integrity sha512-wu/Ujs9IXn0HuyYm4HyRvne+EKsjJSWKEMkB3wQa3gNHSMHt7y3oeNX9zRQ3UBPk7bRRMLLHAdIZCZfHT9ehPg== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -5581,19 +5637,6 @@ sorted-array-functions@^1.0.0: resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== -source-map-support@^0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - split2@^3.1.1: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -5775,13 +5818,21 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strip-outer@^1.0.0: +strip-outer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== dependencies: escape-string-regexp "^1.0.2" +strtok3@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.2.4.tgz#302aea64c0fa25d12a0385069ba66253fdc38a81" + integrity sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.0.1" + success-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897" @@ -5863,7 +5914,18 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^6.1.0: +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.1.11: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -5875,6 +5937,13 @@ tar@^6.1.0: mkdirp "^1.0.3" yallist "^4.0.0" +tencent-serverless-http@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/tencent-serverless-http/-/tencent-serverless-http-1.3.2.tgz#76fb76278bfb08c1d15d8350e723267a76b48314" + integrity sha512-HgIu9HuBdY0lx3jLKuicOSOrjmieklPh55x8ZmtuTnrZ5v1buAPUfLKBhTeBSz6e90ggyW+dPr5PWdz179kUkw== + dependencies: + type-is "^1.6.16" + term-size@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" @@ -5909,11 +5978,6 @@ time-stamp@^1.0.1: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - timers-ext@^0.1.5, timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" @@ -5965,6 +6029,14 @@ toggle-array@^1.0.1: dependencies: isobject "^3.0.0" +token-types@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.1.1.tgz#ef9e8c8e2e0ded9f1b3f8dbaa46a3228b113ba1a" + integrity sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -6027,6 +6099,14 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-is@^1.6.16: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + type@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" @@ -6037,6 +6117,11 @@ type@^2.0.0, type@^2.1.0: resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== +type@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -6069,6 +6154,11 @@ universalify@^1.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + untildify@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" @@ -6112,11 +6202,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= - url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" @@ -6287,7 +6372,7 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0, ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@~6.1.0: +ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== @@ -6351,11 +6436,6 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - yargs@^15.3.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -6386,11 +6466,6 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - zames@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/zames/-/zames-2.0.1.tgz#f52633e193699b707672e32aeb6d51a09b6c8b36" @@ -6417,11 +6492,11 @@ zip-stream@^4.0.0: compress-commons "^4.0.0" readable-stream "^3.6.0" -zip-stream@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.0.4.tgz#3a8f100b73afaa7d1ae9338d910b321dec77ff3a" - integrity sha512-a65wQ3h5gcQ/nQGWV1mSZCEzCML6EK/vyVPcrPNynySP1j3VBbQKh3nhC8CbORb+jfl2vXvh56Ul5odP1bAHqw== +zip-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== dependencies: archiver-utils "^2.1.0" - compress-commons "^4.0.2" + compress-commons "^4.1.0" readable-stream "^3.6.0" diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index e19fa14e6..258ac2fbf 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -19,22 +19,22 @@ "prebuild:dev": "STAGE_ENV=dev node ./src/app/config/scripts/prefetch-ssm.js", "deploy:cloudfront:dev": "SLS_DEBUG=* yarn sls deploy --stage='dev' --cloudfront=true --verbose", "deploy:frontend:dev": "SLS_DEBUG=* yarn sls client deploy --stage='dev' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose", - "deploy:invalidate:dev": "SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='dev' --region='us-east-1' --cloudfront='true' --verbose", - "deploy:dev": "echo 'Deploying to DEV...' && SLS_DEBUG=* yarn sls client deploy --stage='dev' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='dev' --region='us-east-1' --cloudfront=true --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='dev' --region='us-east-1' --cloudfront='true' --verbose", + "deploy:invalidate:dev": "SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='dev' --region='us-east-1' --cloudfront='true'", + "deploy:dev": "echo 'Deploying to DEV...' && SLS_DEBUG=* yarn sls client deploy --stage='dev' --region='us-east-1' --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='dev' --region='us-east-1' --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='dev' --region='us-east-1'", "remove:dev": "SLS_DEBUG=* yarn sls remove --stage='dev' --region='us-east-1' --verbose", "prebuild:staging": "STAGE_ENV=staging node ./src/app/config/scripts/prefetch-ssm.js", "build:staging": "yarn prebuild:staging && ./node_modules/@angular/cli/bin/ng build --prod --configuration=staging", "deploy:cloudfront:staging": "SLS_DEBUG=* PRODUCT_DOMAIN=staging.lfcla.com yarn sls deploy --stage='staging' --cloudfront=true --verbose", "deploy:frontend:staging": "SLS_DEBUG=* PRODUCT_DOMAIN=staging.lfcla.com yarn sls client deploy --stage='staging' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose", - "deploy:invalidate:staging": "SLS_DEBUG=* PRODUCT_DOMAIN=staging.lfcla.com yarn sls cloudfrontInvalidate --stage='staging' --region='us-east-1' --cloudfront='true' --verbose", - "deploy:staging": "echo 'Deploying to STAGING...' && SLS_DEBUG=* yarn sls client deploy --stage='staging' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='staging' --region='us-east-1' --cloudfront=true --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='staging' --region='us-east-1' --cloudfront='true' --verbose", + "deploy:invalidate:staging": "SLS_DEBUG=* PRODUCT_DOMAIN=staging.lfcla.com yarn sls cloudfrontInvalidate --stage='staging' --region='us-east-1' --cloudfront='true'", + "deploy:staging": "echo 'Deploying to STAGING...' && SLS_DEBUG=* yarn sls client deploy --stage='staging' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='staging' --region='us-east-1' --cloudfront=true --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='staging' --region='us-east-1' --cloudfront='true'", "remove:staging": "SLS_DEBUG=* yarn sls remove --stage='staging' --region='us-east-1' --verbose", "prebuild:prod": "STAGE_ENV=prod node ./src/app/config/scripts/prefetch-ssm.js", "build:prod": "yarn prebuild:prod && ./node_modules/@angular/cli/bin/ng build --prod --configuration=production", "deploy:frontend:prod": "SLS_DEBUG=* yarn sls client deploy --stage='prod' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose", "deploy:cloudfront:prod": "SLS_DEBUG=* yarn sls deploy --stage='prod' --region='us-east-1' --cloudfront=true --verbose", - "deploy:invalidate:prod": "SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='prod' --region='us-east-1' --cloudfront='true' --verbose", - "deploy:prod": "echo 'Deploying to PROD...' && SLS_DEBUG=* yarn sls client deploy --stage='prod' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='prod' --region='us-east-1' --cloudfront=true --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='prod' --region='us-east-1' --cloudfront='true' --verbose", + "deploy:invalidate:prod": "SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='prod' --region='us-east-1' --cloudfront='true'", + "deploy:prod": "echo 'Deploying to PROD...' && SLS_DEBUG=* yarn sls client deploy --stage='prod' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='prod' --region='us-east-1' --cloudfront=true --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='prod' --region='us-east-1' --cloudfront='true'", "remove:prod": "SLS_DEBUG=* yarn sls remove --stage='prod' --region='us-east-1' --verbose" }, "private": true, @@ -56,7 +56,7 @@ "bootstrap": "^4.4.0", "query-string": "^6.13.6", "rxjs": "~6.6.7", - "serverless": "^2.15.0", + "serverless": "^2.57.0", "serverless-aws-alias": "^1.8.0", "serverless-cloudfront-invalidate": "^1.5.0", "serverless-finch": "^2.6.0", diff --git a/cla-landing-page/project-vars.yml b/cla-landing-page/project-vars.yml new file mode 100644 index 000000000..cc67f26de --- /dev/null +++ b/cla-landing-page/project-vars.yml @@ -0,0 +1,3 @@ +# Don't put secrets into this file +projectIdentifier: lf-cla # Used for prefixing aws resources. +profile: lf-cla # Used for setting the AWS Profile with serverless. \ No newline at end of file diff --git a/cla-landing-page/serverless.yml b/cla-landing-page/serverless.yml index 53411bff4..5445e2845 100644 --- a/cla-landing-page/serverless.yml +++ b/cla-landing-page/serverless.yml @@ -1,23 +1,26 @@ service: cla-landing-page -frameworkVersion: ^2.8.0 +frameworkVersion: '^2.57.0' package: - exclude: - - "**" - include: - - dist/landing-page/* + patterns: + - '!**' + - 'dist/landing-page/*' provider: name: aws runtime: nodejs12.x + stage: ${opt:stage} region: us-east-1 # Region can't be configurable, lambda@edge is us-east-1 only. + logRetentionInDays: 14 + lambdaHashingVersion: '20201221' # Resolution of lambda version hashes was improved with better algorithm, which will be used in next major release. Switch to it now by setting "provider.lambdaHashingVersion" to "20201221" + deploymentBucket: serverSideEncryption: AES256 # Make sure items are uploaded encrypted. role: EdgeRole # This will add tags to every resource generated by your CloudFormation template stackTags: Name: ${self:service} - STAGE: ${self:provider.stage} + STAGE: ${opt:stage} Product: EasyCLA ServiceType: Product Service: ${self:service} @@ -27,14 +30,16 @@ provider: tracing: apiGateway: true - lambda: true + lambda: true # optional, enables tracing for all functions (can be true (true equals 'Active') 'Active' or 'PassThrough') - iamRoleStatements: - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: "*" + iam: + role: + statements: + - Effect: Allow + Action: + - xray:PutTraceSegments + - xray:PutTelemetryRecords + Resource: "*" plugins: # Serverless Finch does s3 uploading. Called with 'sls client deploy'. @@ -48,7 +53,7 @@ plugins: - serverless-pseudo-parameters custom: - project: ${file(../project-vars.yml):projectIdentifier} + project: ${file(project-vars.yml):projectIdentifier} client: # Configurations for serverless finch. bucketName: ${self:custom.project}-${opt:stage}-${self:service} distributionFolder: dist/landing-page @@ -145,7 +150,7 @@ resources: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: - Comment: "CloudFrontOriginAccessIdentity for ${self:service}-${self:provider.stage}" + Comment: "CloudFrontOriginAccessIdentity for ${self:service}-${opt:stage}" # The cloudfront distribution wraps around our static website S3 bucket. Using a CDN to host our SPA is good # practice, and also lets us set custom headers using lambda@edge. diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 59f47ffde..5f7bbf533 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1185,41 +1185,40 @@ node-fetch "^2.6.0" shortid "^2.2.14" -"@serverless/components@^3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.4.3.tgz#4764eda6117fef85876adaf5f4006f98f2e98118" - integrity sha512-buKvUPDeS54bUG9c56bmX5WcL3hBCHASKamHpUGmSa1ArSem8BE76LzPjiNcreOJGSFf9VGMgpsW1d1WKy2fAA== - dependencies: - "@serverless/platform-client" "^3.1.1" - "@serverless/platform-client-china" "^2.0.9" - "@serverless/platform-sdk" "^2.3.2" - "@serverless/utils" "^2.0.0" - adm-zip "^0.4.16" +"@serverless/components@^3.16.0": + version "3.16.0" + resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.16.0.tgz#a161481f73ffa9e1d62c447b3d8b6bacbf701ba6" + integrity sha512-vgsfR0V4dierB97GKgtfFp/s5XN3zmEQlhRfshWLHjoXvJe2HvWOZXy5Bqyzb1WMr6S0dp/wXQzcnVStbiyBaw== + dependencies: + "@serverless/platform-client" "^4.2.2" + "@serverless/platform-client-china" "^2.2.0" + "@serverless/utils" "^4.0.0" + adm-zip "^0.5.4" ansi-escapes "^4.3.1" - aws4 "^1.10.1" chalk "^4.1.0" child-process-ext "^2.1.1" - chokidar "^3.4.2" + chokidar "^3.5.1" + ci-info "^3.2.0" + dayjs "^1.10.4" dotenv "^8.2.0" + fastest-levenshtein "^1.0.12" figures "^3.2.0" - fs-extra "^9.0.1" - globby "^11.0.1" - got "^11.7.0" + fs-extra "^9.1.0" + got "^11.8.2" graphlib "^2.1.8" https-proxy-agent "^5.0.0" - ini "^1.3.5" - inquirer-autocomplete-prompt "^1.1.0" - js-yaml "^3.14.0" + inquirer-autocomplete-prompt "^1.3.0" + js-yaml "^3.14.1" memoizee "^0.4.14" minimist "^1.2.5" - moment "^2.29.0" - open "^7.2.1" + open "^7.3.1" prettyoutput "^1.2.0" ramda "^0.27.1" - semver "^7.3.2" + semver "^7.3.4" strip-ansi "^6.0.0" + tencent-serverless-http "^1.3.1" traverse "^0.6.6" - uuid "^8.3.1" + uuid "^8.3.2" "@serverless/core@^1.0.0", "@serverless/core@^1.1.2": version "1.1.2" @@ -1232,31 +1231,31 @@ ramda "^0.26.1" semver "^6.1.1" -"@serverless/enterprise-plugin@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@serverless/enterprise-plugin/-/enterprise-plugin-4.2.0.tgz#d4fe755806628e7d8c12b520cb3edb57004f944a" - integrity sha512-b7kVdcE+nLi9kWOu4lqvbYBeK0ChIPX9gbqMecs3fEAdPVMleZyC0CywdWnpOrV9xXij9tj4LxFv6NRRcFCXZg== +"@serverless/dashboard-plugin@^5.4.4": + version "5.4.4" + resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.4.4.tgz#787d0214175f520776c46ae0ca38bf7a981f8d54" + integrity sha512-1lLChYK/zwrF5SEAubVr9Oz/xGnq1Yjbw36X1iz0j/+jwbtpt1AeumksArA3UVAgDSecVOfaksxUta2cc10pRA== dependencies: "@serverless/event-mocks" "^1.1.1" - "@serverless/platform-client" "^3.1.2" - "@serverless/platform-sdk" "^2.3.2" - chalk "^4.1.0" + "@serverless/platform-client" "^4.3.0" + "@serverless/utils" "^5.7.0" + chalk "^4.1.2" child-process-ext "^2.1.1" - chokidar "^3.4.3" + chokidar "^3.5.2" cli-color "^2.0.0" flat "^5.0.2" - fs-extra "^9.0.1" - js-yaml "^3.14.0" - jszip "^3.5.0" - lodash "^4.17.20" - memoizee "^0.4.14" - ncjsm "^4.1.0" + fs-extra "^9.1.0" + js-yaml "^4.1.0" + jszip "^3.7.1" + lodash "^4.17.21" + memoizee "^0.4.15" + ncjsm "^4.2.0" node-dir "^0.1.17" node-fetch "^2.6.1" - open "^7.3.0" - semver "^7.3.4" - simple-git "^2.24.0" - uuid "^8.3.1" + open "^7.4.2" + semver "^7.3.5" + simple-git "^2.44.0" + uuid "^8.3.2" yamljs "^0.3.0" "@serverless/event-mocks@^1.1.1": @@ -1267,60 +1266,47 @@ "@types/lodash" "^4.14.123" lodash "^4.17.11" -"@serverless/platform-client-china@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@serverless/platform-client-china/-/platform-client-china-2.0.9.tgz#473b9413781bec62c61c57b9d6ce00eb691f6f7d" - integrity sha512-qec3a5lVaMH0nccgjVgvcEF8M+M95BXZbbYDGypVHEieJQxrKqj057+VVKsiHBeHYXzr4B3v6pIyQHst40vpIw== +"@serverless/platform-client-china@^2.2.0": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@serverless/platform-client-china/-/platform-client-china-2.2.3.tgz#686bece20a3b4271766a591ee4e5331bd1090f6a" + integrity sha512-MoDxI0d3HMesHwdyk7gzwLNU0wuJk/ovL9+XD/gAZgnrdZpFFq9kwnJAm2midL7jdhpnEFu/aoGJ0eE+xtQJ7w== dependencies: - "@serverless/utils-china" "^1.0.11" + "@serverless/utils-china" "^1.1.4" + adm-zip "^0.5.1" archiver "^5.0.2" + axios "^0.21.1" dotenv "^8.2.0" + fast-glob "^3.2.4" fs-extra "^9.0.1" https-proxy-agent "^5.0.0" js-yaml "^3.14.0" minimatch "^3.0.4" querystring "^0.2.0" + run-parallel-limit "^1.0.6" traverse "^0.6.6" urlencode "^1.1.0" ws "^7.3.1" -"@serverless/platform-client@^3.1.1", "@serverless/platform-client@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-3.1.2.tgz#2bf283bf2a21b1d05d650b90188bf2a3dd29bc7d" - integrity sha512-zTJBhzjWtDBogLFnzoz6NYiQ6CThsxuvHQxSbBLcNK4+VQPIkrZOxaQ+dNCNLeLN1Tb3NnZDPNGkoThvgGwq3Q== +"@serverless/platform-client@^4.2.2", "@serverless/platform-client@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-4.3.0.tgz#5eda7b7eb8bdd0da93f2ecf2130b4131349806a1" + integrity sha512-q2CMqCkKeBaKA/UwfJAZLkdUsbghSbiYPvAX4rl9rsR5APm4KWtjKQP9CTOtVO5JRMWYoysK6jF0d5VJOABRzQ== dependencies: - adm-zip "^0.4.13" - archiver "^5.0.0" - axios "^0.19.2" + adm-zip "^0.5.5" + archiver "^5.3.0" + axios "^0.21.1" + fast-glob "^3.2.7" https-proxy-agent "^5.0.0" ignore "^5.1.8" isomorphic-ws "^4.0.1" - js-yaml "^3.13.1" + js-yaml "^3.14.1" jwt-decode "^2.2.0" minimatch "^3.0.4" - querystring "^0.2.0" + querystring "^0.2.1" + run-parallel-limit "^1.1.0" + throat "^5.0.0" traverse "^0.6.6" - ws "^7.2.1" - -"@serverless/platform-sdk@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@serverless/platform-sdk/-/platform-sdk-2.3.2.tgz#d53e37c910e66687e0cc398c3b83fde9d7357806" - integrity sha512-JSX0/EphGVvnb4RAgZYewtBXPuVsU2TFCuXh6EEZ4jxK3WgUwNYeYdwB8EuVLrm1/dYqu/UWUC0rPKb+ZDycJg== - dependencies: - chalk "^2.4.2" - https-proxy-agent "^4.0.0" - is-docker "^1.1.0" - jwt-decode "^2.2.0" - node-fetch "^2.6.1" - opn "^5.5.0" - querystring "^0.2.0" - ramda "^0.25.0" - rc "^1.2.8" - regenerator-runtime "^0.13.7" - source-map-support "^0.5.19" - uuid "^3.4.0" - write-file-atomic "^2.4.3" - ws "<7.0.0" + ws "^7.5.3" "@serverless/template@^1.1.3": version "1.1.3" @@ -1332,16 +1318,17 @@ graphlib "^2.1.7" traverse "^0.6.6" -"@serverless/utils-china@^1.0.11": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@serverless/utils-china/-/utils-china-1.0.11.tgz#368003260ccd1df55f7477da50d0b606f157e58b" - integrity sha512-raOPIoPSTrkWKBDuozkYWvLXP2W65K9Uk4ud+lPcbhhBSamO3uVW40nuAkC19MdIoAsFi5oTGYpcc9UDx8b+lg== +"@serverless/utils-china@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@serverless/utils-china/-/utils-china-1.1.4.tgz#5a76c90d5d0f33c144fe0e82c799251a46415adb" + integrity sha512-8s73M1k+mST7Z/Rp8wgmZh50tjpwX+fqsbYYRuFGgyuWTvgqAlUflDOWAeQuDx4pEndWEqjbG09ZrZNqlHuZqQ== dependencies: - "@tencent-sdk/capi" "^1.1.2" + "@tencent-sdk/capi" "^1.1.8" dijkstrajs "^1.0.1" dot-qs "0.2.0" duplexify "^4.1.1" end-of-stream "^1.4.4" + got "^11.8.2" https-proxy-agent "^5.0.0" kafka-node "^5.0.0" protobufjs "^6.9.0" @@ -1361,18 +1348,46 @@ uuid "^3.4.0" write-file-atomic "^2.4.3" -"@serverless/utils@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-2.0.0.tgz#b91822f34e6fb78658207fa150aa016ef86b7910" - integrity sha512-yZQT2f8LIZZlH2ibAIvK4C/Ks72Y8CIWmGz04XGCLPHa/ANA6KqlXTKV6zWg/n1PDy2yj2zgX+m509VpIZuDeQ== +"@serverless/utils@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-4.1.0.tgz#99574f446509eac8935f58013399d7af89945b4b" + integrity sha512-cl5uPaGg72z0sCUpF0zsOhwYYUV72Gxc1FwFfxltO8hSvMeFDvwD7JrNE4kHcIcKRjwPGbSH0fdVPUpErZ8Mog== dependencies: chalk "^4.1.0" + ci-info "^3.1.1" inquirer "^7.3.3" - lodash "^4.17.20" + js-yaml "^4.1.0" + jwt-decode "^3.1.2" + lodash "^4.17.21" ncjsm "^4.1.0" - rc "^1.2.8" - type "^2.1.0" - uuid "^8.3.0" + type "^2.5.0" + uuid "^8.3.2" + write-file-atomic "^3.0.3" + +"@serverless/utils@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.7.0.tgz#eeef23bc85c16501716c107c262b80d127bf3562" + integrity sha512-4/9lTag4NNMtgoK7qRSoP//VplnKUTqgKMJ5pjvuXHFTBNoGYbdi5Cr1UmbHwnG8FfYBUy95jNUHjSEeUXDqgg== + dependencies: + archive-type "^4.0.0" + chalk "^4.1.2" + ci-info "^3.2.0" + content-disposition "^0.5.3" + decompress "^4.2.1" + ext-name "^5.0.0" + file-type "^16.5.3" + filenamify "^4.3.0" + get-stream "^6.0.1" + got "^11.8.2" + inquirer "^7.3.3" + js-yaml "^4.1.0" + jwt-decode "^3.1.2" + lodash "^4.17.21" + make-dir "^3.1.0" + ncjsm "^4.2.0" + p-event "^4.2.0" + type "^2.5.0" + uuid "^8.3.2" write-file-atomic "^3.0.3" "@sindresorhus/is@^0.14.0": @@ -1380,11 +1395,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - "@sindresorhus/is@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" @@ -1404,16 +1414,21 @@ dependencies: defer-to-connect "^2.0.0" -"@tencent-sdk/capi@^1.1.2": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@tencent-sdk/capi/-/capi-1.1.5.tgz#ba2932e292deb659d3e9968b70d9a6ec54d47c66" - integrity sha512-cHkoMY/1L5VxeiKv51uKxbFK8lZ7pZbY3CukzOHro8YKT6dETKYzTGO/F8jDhH7r8vKWxuA+ZcALzxYuVlmwsg== +"@tencent-sdk/capi@^1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@tencent-sdk/capi/-/capi-1.1.8.tgz#955130f9c7da88a599c05b3eae01b2f0e15a9beb" + integrity sha512-AmyMQndtxMsM59eDeA0gGiw8T2LzNvDhx/xl+ygFXXrsw+yb/mit73ndHkiHKcRA1EpNHTyD1PN9ATxghzplfg== dependencies: "@types/request" "^2.48.3" "@types/request-promise-native" "^1.0.17" request "^2.88.0" request-promise-native "^1.0.8" +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@types/auth0-js@^8.11.7": version "8.11.12" resolved "https://registry.yarnpkg.com/@types/auth0-js/-/auth0-js-8.11.12.tgz#7d074e393cb93cc40f017687040955ff27e1c485" @@ -1746,11 +1761,16 @@ acorn@^6.2.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -adm-zip@^0.4.13, adm-zip@^0.4.16, adm-zip@^0.4.9: +adm-zip@^0.4.9: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== +adm-zip@^0.5.1, adm-zip@^0.5.4, adm-zip@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.5.tgz#b6549dbea741e4050309f1bb4d47c47397ce2c4f" + integrity sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w== + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -1763,11 +1783,6 @@ agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - agent-base@6: version "6.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" @@ -2141,6 +2156,14 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + app-root-path@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" @@ -2181,7 +2204,7 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" -archiver@^5.0.0, archiver@^5.0.2: +archiver@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.0.2.tgz#b2c435823499b1f46eb07aa18e7bcb332f6ca3fc" integrity sha512-Tq3yV/T4wxBsD2Wign8W9VQKhaUxzzRmjEiSoOK0SLqPgDP/N1TKdYyBeIEu56T4I9iO4fKTTR0mN9NWkBA0sg== @@ -2194,18 +2217,18 @@ archiver@^5.0.0, archiver@^5.0.2: tar-stream "^2.1.4" zip-stream "^4.0.0" -archiver@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.1.0.tgz#05b0f6f7836f3e6356a0532763d2bb91017a7e37" - integrity sha512-iKuQUP1nuKzBC2PFlGet5twENzCfyODmvkxwDV0cEFXavwcLrIW5ssTuHi9dyTPvpWr6Faweo2eQaQiLIwyXTA== +archiver@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" + integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== dependencies: archiver-utils "^2.1.0" async "^3.2.0" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" readdir-glob "^1.0.0" - tar-stream "^2.1.4" - zip-stream "^4.0.4" + tar-stream "^2.2.0" + zip-stream "^4.1.0" are-we-there-yet@~1.1.2: version "1.1.5" @@ -2227,6 +2250,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + aria-query@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" @@ -2434,10 +2462,10 @@ aws-sdk@^2.224.1: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.803.0: - version "2.806.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.806.0.tgz#5bd3ac07425ee34f5dc9dc40a38d78c49066a88f" - integrity sha512-kCrGfZyzZiS56qblXEzznkTi64ZbzhQGlbyEjDI9cIUjX4dA9IyqvNWUdJvUQoZmiEnObbuXMVrv7blJzT8uhQ== +aws-sdk@^2.885.0: + version "2.899.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.899.0.tgz#fac90b8926707b3e9dcd1e7d5937cfa71c0be5ca" + integrity sha512-k8jSANDQGvTyyj1f/7Hj4SWaV61/gjj/BopRmavAr6n1ayjXtUeVrV8G29+ABD3V82pHXDqLq47bqNmZ9m86xQ== dependencies: buffer "4.9.2" events "1.1.1" @@ -2449,10 +2477,10 @@ aws-sdk@^2.803.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.885.0: - version "2.899.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.899.0.tgz#fac90b8926707b3e9dcd1e7d5937cfa71c0be5ca" - integrity sha512-k8jSANDQGvTyyj1f/7Hj4SWaV61/gjj/BopRmavAr6n1ayjXtUeVrV8G29+ABD3V82pHXDqLq47bqNmZ9m86xQ== +aws-sdk@^2.979.0: + version "2.980.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" + integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== dependencies: buffer "4.9.2" events "1.1.1" @@ -2469,17 +2497,17 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.10.1, aws4@^1.8.0: +aws4@^1.8.0: version "1.10.1" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.10.0" axobject-query@2.0.2: version "2.0.2" @@ -2687,19 +2715,19 @@ bootstrap@^4.4.0: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6" integrity sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ== -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== +boxen@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" + integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== dependencies: ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.0" + type-fest "^0.20.2" widest-line "^3.1.0" + wrap-ansi "^7.0.0" brace-expansion@^1.1.7: version "1.1.11" @@ -2899,6 +2927,11 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== +builtin-modules@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -3006,19 +3039,6 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3" integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w== -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -3079,6 +3099,11 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -3147,6 +3172,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -3172,7 +3205,7 @@ choices-separator@^2.0.0: debug "^2.6.6" strip-color "^0.1.0" -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: +"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.4.1: version "3.4.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== @@ -3206,6 +3239,21 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.5.1, chokidar@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -3223,6 +3271,11 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" +ci-info@^3.1.1, ci-info@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -3251,11 +3304,23 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: +cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-color@^1.4: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" + integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== + dependencies: + ansi-regex "^2.1.1" + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.5" + cli-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" @@ -3282,6 +3347,17 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-progress-footer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz#8aa2292f5cca4f6639e3e0107ef04d897d620563" + integrity sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg== + dependencies: + cli-color "^1.4" + d "1" + es5-ext "^0.10.47" + process-utils "^2.0.1" + timers-ext "^0.1.7" + cli-spinners@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.5.0.tgz#12763e47251bf951cb75c201dfa58ff1bcb2d047" @@ -3343,7 +3419,7 @@ clone-deep@^4.0.0, clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@1.0.2, clone-response@^1.0.2: +clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= @@ -3540,13 +3616,13 @@ compress-commons@^4.0.0: normalize-path "^3.0.0" readable-stream "^3.6.0" -compress-commons@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.0.2.tgz#d6896be386e52f37610cef9e6fa5defc58c31bd7" - integrity sha512-qhd32a9xgzmpfoga1VQEiLEwdKZ6Plnpx5UCgIsf89FSolyJ7WnifY4Gtjgv5WR6hWAyRaHxC5MiEhU/38U70A== +compress-commons@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== dependencies: buffer-crc32 "^0.2.13" - crc32-stream "^4.0.1" + crc32-stream "^4.0.2" normalize-path "^3.0.0" readable-stream "^3.6.0" @@ -3615,7 +3691,7 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3, content-disposition@^0.5.2: +content-disposition@0.5.3, content-disposition@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -3737,10 +3813,10 @@ crc32-stream@^4.0.0: crc "^3.4.4" readable-stream "^3.4.0" -crc32-stream@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.1.tgz#0f047d74041737f8a55e86837a1b826bd8ab0067" - integrity sha512-FN5V+weeO/8JaXsamelVYO1PHyeCsuL3HcG4cqsj0ceARcocxalaShCsohZMSAF+db7UYFwBy1rARK/0oFItUw== +crc32-stream@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== dependencies: crc-32 "^1.2.0" readable-stream "^3.4.0" @@ -4041,10 +4117,10 @@ date-format@^3.0.0: resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== -dayjs@^1.9.6: - version "1.9.7" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.7.tgz#4b260bb17dceed2d5f29038dfee03c65a6786fc0" - integrity sha512-IC877KBdMhBrCfBfJXHQlo0G8keZ0Opy7YIIq5QKtUbCuHMzim8S4PyiVK4YmihI3iOF9lhfUBW4AQWHTR5WHA== +dayjs@^1.10.4, dayjs@^1.10.6: + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== debug@2, debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8: version "2.6.9" @@ -4053,7 +4129,7 @@ debug@2, debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, de dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0, debug@~3.1.0: +debug@3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -4468,28 +4544,21 @@ dot-qs@0.2.0: resolved "https://registry.yarnpkg.com/dot-qs/-/dot-qs-0.2.0.tgz#d36517fe24b7cda61fce7a5026a0024afaf5a439" integrity sha1-02UX/iS3zaYfznpQJqACSvr1pDk= +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -download@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/download/-/download-8.0.0.tgz#afc0b309730811731aae9f5371c9f46be73e51b1" - integrity sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA== - dependencies: - archive-type "^4.0.0" - content-disposition "^0.5.2" - decompress "^4.2.1" - ext-name "^5.0.0" - file-type "^11.1.0" - filenamify "^3.0.0" - get-stream "^4.1.0" - got "^8.3.1" - make-dir "^2.1.0" - p-event "^2.1.0" - pify "^4.0.1" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -4803,7 +4872,7 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" -es6-weak-map@^2.0.2: +es6-weak-map@^2.0.2, es6-weak-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== @@ -5028,6 +5097,13 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" +ext@^1.1.0, ext@^1.4.0, ext@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" + integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== + dependencies: + type "^2.5.0" + ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -5105,6 +5181,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.4: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -5195,10 +5282,14 @@ file-loader@6.0.0: loader-utils "^2.0.0" schema-utils "^2.6.5" -file-type@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8" - integrity sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g== +file-type@^16.5.3: + version "16.5.3" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06" + integrity sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" file-type@^3.8.0: version "3.9.0" @@ -5230,13 +5321,13 @@ filename-reserved-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= -filenamify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-3.0.0.tgz#9603eb688179f8c5d40d828626dcbb92c3a4672c" - integrity sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g== +filenamify@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" + integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== dependencies: filename-reserved-regex "^2.0.0" - strip-outer "^1.0.0" + strip-outer "^1.0.1" trim-repeated "^1.0.0" fileset@^2.0.3: @@ -5247,10 +5338,10 @@ fileset@^2.0.3: glob "^7.0.3" minimatch "^3.0.3" -filesize@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" - integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== +filesize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.0.tgz#532db71cb8a04df7d403da054a28de1b648534e0" + integrity sha512-sb690gQx3y/5KZIztgWAKM/r4Hf1V3R8mkAE0OhasMw2FDYduFTYCji8YN9BVpsGoMxrHPFvia1BMxwfLHX+fQ== fill-range@^4.0.0: version "4.0.0" @@ -5341,18 +5432,16 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - follow-redirects@^1.0.0: version "1.13.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== +follow-redirects@^1.10.0: + version "1.14.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b" + integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA== + for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -5424,7 +5513,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.1.0, from2@^2.1.1: +from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -5474,6 +5563,16 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^1.2.5: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -5516,6 +5615,19 @@ fs2@^0.3.8: memoizee "^0.4.14" type "^2.0.0" +fs2@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/fs2/-/fs2-0.3.9.tgz#3869e5b2ec7e0622eaa5f4373df540d3d427a9fb" + integrity sha512-WsOqncODWRlkjwll+73bAxVW3JPChDgaPX3DT4iTTm73UmG4VgALa7LaFblP232/DN60itkOrPZ8kaP1feksGQ== + dependencies: + d "^1.0.1" + deferred "^0.7.11" + es5-ext "^0.10.53" + event-emitter "^0.3.5" + ignore "^5.1.8" + memoizee "^0.4.14" + type "^2.1.0" + fsevents@^1.2.7: version "1.2.13" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" @@ -5529,6 +5641,11 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + ftp@~0.3.10: version "0.3.10" resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" @@ -5581,11 +5698,6 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -5608,6 +5720,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-uri@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.4.tgz#d4937ab819e218d4cb5ae18e4f5962bef169cc6a" @@ -5652,6 +5769,13 @@ glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob@7.1.6, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -5681,6 +5805,18 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.0.4: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -5704,10 +5840,10 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -got@^11.7.0, got@^11.8.0: - version "11.8.0" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.0.tgz#be0920c3586b07fd94add3b5b27cb28f49e6545f" - integrity sha512-k9noyoIIY9EejuhaBNLyZ31D5328LeqnyPNXJQb2XlJZcKakLqN5m6O/ikhq/0lw56kUYS54fVm+D1x57YC9oQ== +got@^11.8.2: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" @@ -5721,29 +5857,6 @@ got@^11.7.0, got@^11.8.0: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^8.3.1: - version "8.3.2" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -5766,6 +5879,11 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1. resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + graphlib@^2.1.7, graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -5820,23 +5938,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -5958,7 +6064,7 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-cache-semantics@3.8.1, http-cache-semantics@^3.8.1: +http-cache-semantics@^3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== @@ -6075,14 +6181,6 @@ https-proxy-agent@^3.0.0: agent-base "^4.3.0" debug "^3.1.0" -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" @@ -6136,6 +6234,11 @@ ieee754@1.1.13, ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" @@ -6246,15 +6349,15 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@1.3.5, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@1.3.5, ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer-autocomplete-prompt@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.2.0.tgz#da13d80cb0758ff5d58703e17afc533b5b61cc6d" - integrity sha512-t8A0gFq0mOKGz8wmGBPh+M/AC8KSMMcn7dnHXzLWyKvLiRYWswQ6rg7d938hoR5tVP1GpHVmHOR6YP8G5dYhhQ== +inquirer-autocomplete-prompt@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.4.0.tgz#e767592f747e3d5bb6336fe71fb4094352e4c317" + integrity sha512-qHgHyJmbULt4hI+kCmwX92MnSxDs/Yhdt4wPA30qnoa01OF6uTXV8yvH4hKXgdaTNmkZ9D01MHjqKYEuJN+ONw== dependencies: ansi-escapes "^4.3.1" chalk "^4.0.0" @@ -6327,14 +6430,6 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -6486,16 +6581,16 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-docker@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-1.1.0.tgz#f04374d4eee5310e9a8e113bf1495411e46176a1" - integrity sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE= - -is-docker@^2.0.0, is-docker@^2.1.1: +is-docker@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== +is-docker@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -6581,11 +6676,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -6636,7 +6726,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-promise@^2.1: +is-promise@^2.1, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== @@ -6653,11 +6743,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -6837,14 +6922,6 @@ istanbul-reports@^2.2.5: dependencies: html-escaper "^2.0.0" -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - jasmine-core@^3.5.0: version "3.6.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20" @@ -6921,6 +6998,21 @@ js-yaml@^3.13.1, js-yaml@^3.14.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -7045,7 +7137,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.1.3, jszip@^3.5.0: +jszip@^3.1.3: version "3.5.0" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== @@ -7055,11 +7147,26 @@ jszip@^3.1.3, jszip@^3.5.0: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" +jszip@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk= +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + kafka-node@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/kafka-node/-/kafka-node-5.0.0.tgz#4b6f65cc1d77ebe565859dfb8f9575ed15d543c0" @@ -7145,13 +7252,6 @@ karma@~5.0.0: ua-parser-js "0.7.21" yargs "^15.3.1" -keyv@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" - integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== - dependencies: - json-buffer "3.0.0" - keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -7367,11 +7467,16 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: +lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-ok@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/log-ok/-/log-ok-0.1.1.tgz#bea3dd36acd0b8a7240d78736b5b97c65444a334" @@ -7456,11 +7561,6 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -7485,7 +7585,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-queue@0.1: +lru-queue@0.1, lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= @@ -7514,7 +7614,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.2: +make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -7614,6 +7714,20 @@ memoizee@^0.4.14: next-tick "1" timers-ext "^0.1.5" +memoizee@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -7684,6 +7798,14 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -7866,7 +7988,7 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.24.0, moment@^2.29.0: +moment@^2.24.0: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -7971,6 +8093,19 @@ ncjsm@^4.1.0: fs2 "^0.3.8" type "^2.0.0" +ncjsm@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/ncjsm/-/ncjsm-4.2.0.tgz#7b2d752c3a42db5f6a2c5ff6934cf66fb1bb5e38" + integrity sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw== + dependencies: + builtin-modules "^3.2.0" + deferred "^0.7.11" + es5-ext "^0.10.53" + es6-set "^0.1.5" + find-requires "^1.0.0" + fs2 "^0.3.9" + type "^2.5.0" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -7991,7 +8126,7 @@ netmask@^1.0.6: resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= -next-tick@1, next-tick@^1.0.0: +next-tick@1, next-tick@^1.0.0, next-tick@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== @@ -8115,15 +8250,6 @@ normalize-url@1.9.1: query-string "^4.1.0" sort-keys "^1.0.0" -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -8279,10 +8405,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" - integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== object-inspect@^1.8.0: version "1.8.0" @@ -8395,10 +8521,10 @@ open@7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^7.2.1, open@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" - integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== +open@^7.3.1, open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: is-docker "^2.0.0" is-wsl "^2.1.1" @@ -8485,11 +8611,6 @@ osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -8505,23 +8626,18 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= -p-event@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6" - integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== +p-event@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" + integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== dependencies: - p-timeout "^2.0.1" + p-timeout "^3.1.0" p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= - p-is-promise@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" @@ -8541,13 +8657,6 @@ p-limit@^3.0.1: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -8588,10 +8697,10 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== dependencies: p-finally "^1.0.0" @@ -8801,6 +8910,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path2@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/path2/-/path2-0.1.0.tgz#639828942cdbda44a41a45b074ae8873483b4efa" + integrity sha1-Y5golCzb2kSkGkWwdK6Ic0g7Tvo= + pbkdf2@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" @@ -8812,6 +8926,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +peek-readable@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.0.1.tgz#9a045f291db254111c3412c1ce4fec27ddd4d202" + integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -8827,6 +8946,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -9298,6 +9422,24 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-utils@^2.0.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-2.6.0.tgz#0f0d3b2633ec1f4656def20d36425f92ec60cf23" + integrity sha512-2zKFADQDvHiUDyJQTsBTdu1+Q2D/WtReBotZwXmD9oUueb0kNv4rXulK/78hMM+nclBNFZ/ZlHOJtobt8oHpqQ== + dependencies: + ext "^1.1.0" + type "^2.0.0" + +process-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-4.0.0.tgz#3e5b204e1d38e62fe39ef3144664a1fe94097b9e" + integrity sha512-fMyMQbKCxX51YxR7YGCzPjLsU3yDzXFkP4oi1/Mt5Ixnk7GO/7uUTj8mrCHUwuvozWzI+V7QSJR9cZYnwNOZPg== + dependencies: + ext "^1.4.0" + fs2 "^0.3.9" + memoizee "^0.4.14" + type "^2.1.0" + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -9585,15 +9727,6 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - query-string@^6.13.6: version "6.13.6" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.6.tgz#e5ac7c74f2a5da43fbca0b883b4f0bafba439966" @@ -9613,11 +9746,21 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystring@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -9632,11 +9775,6 @@ radio-symbol@^2.0.0: ansi-green "^0.1.1" is-windows "^1.0.1" -ramda@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9" - integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ== - ramda@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" @@ -9770,6 +9908,13 @@ readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1, readable string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -9803,6 +9948,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + readline-ui@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/readline-ui/-/readline-ui-2.2.3.tgz#9e873a7668bbd8ca8a5573ce810a6bafb70a5089" @@ -9850,7 +10002,7 @@ regenerator-runtime@0.13.5: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== -regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== @@ -10028,7 +10180,7 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2: is-core-module "^2.0.0" path-parse "^1.0.6" -responselike@1.0.2, responselike@^1.0.2: +responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -10127,6 +10279,13 @@ run-async@^2.2.0, run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel-limit@^1.0.6, run-parallel-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" + integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw== + dependencies: + queue-microtask "^1.2.2" + run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -10310,6 +10469,13 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -10415,62 +10581,66 @@ serverless-pseudo-parameters@^2.5.0: resolved "https://registry.yarnpkg.com/serverless-pseudo-parameters/-/serverless-pseudo-parameters-2.5.0.tgz#f30bf34db166e4b8b22144a8e65aca71b90dd1e6" integrity sha512-A/O49AR8LL6jlnPSmnOTYgL1KqVgskeRla4sVDeS/r5dHFJlwOU5MgFilc7aaQP8NWAwRJANaIS9oiSE3I+VUA== -serverless@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.15.0.tgz#4ed4ef800695271ff687b0cef0e416299b5810e1" - integrity sha512-lgLFdmjcTJAP3v/BlZkE3EyE1ZkIfcoBcL/J/z9dF8er71w6UJ0pixbnJ4CYSOgqS5DK7MphaP+hTTY12Aw6OQ== +serverless@^2.57.0: + version "2.57.0" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.57.0.tgz#74b5347f50969d81869762f53c92221d92e9999a" + integrity sha512-/gLzaqdisNlymMiQJMkFoACy7NXkOd7E67sbcOrENXuVb48QAS4PTT8VPkMNu6eQonlphGagbKhpqVmKzHJDmQ== dependencies: "@serverless/cli" "^1.5.2" - "@serverless/components" "^3.4.3" - "@serverless/enterprise-plugin" "^4.2.0" - "@serverless/utils" "^2.0.0" + "@serverless/components" "^3.16.0" + "@serverless/dashboard-plugin" "^5.4.4" + "@serverless/platform-client" "^4.3.0" + "@serverless/utils" "^5.7.0" ajv "^6.12.6" ajv-keywords "^3.5.2" - archiver "^5.1.0" - aws-sdk "^2.803.0" + archiver "^5.3.0" + aws-sdk "^2.979.0" bluebird "^3.7.2" - boxen "^4.2.0" + boxen "^5.0.1" cachedir "^2.3.0" - chalk "^4.1.0" + chalk "^4.1.2" child-process-ext "^2.1.1" + ci-info "^3.2.0" + cli-progress-footer "^1.1.1" d "^1.0.1" - dayjs "^1.9.6" + dayjs "^1.10.6" decompress "^4.2.1" - dotenv "^8.2.0" - download "^8.0.0" + dotenv "^10.0.0" + dotenv-expand "^5.1.0" essentials "^1.1.1" + ext "^1.5.0" fastest-levenshtein "^1.0.12" - filesize "^6.1.0" - fs-extra "^9.0.1" + filesize "^8.0.0" + fs-extra "^9.1.0" get-stdin "^8.0.0" - globby "^11.0.1" - got "^11.8.0" - graceful-fs "^4.2.4" + globby "^11.0.4" + got "^11.8.2" + graceful-fs "^4.2.8" https-proxy-agent "^5.0.0" - is-docker "^2.1.1" + is-docker "^2.2.1" is-wsl "^2.2.0" - js-yaml "^3.14.0" + js-yaml "^4.1.0" json-cycle "^1.3.0" json-refs "^3.0.15" - lodash "^4.17.20" - memoizee "^0.4.14" - micromatch "^4.0.2" - ncjsm "^4.1.0" + lodash "^4.17.21" + memoizee "^0.4.15" + micromatch "^4.0.4" + ncjsm "^4.2.0" node-fetch "^2.6.1" - object-hash "^2.0.3" - p-limit "^3.1.0" + object-hash "^2.2.0" + path2 "^0.1.0" + process-utils "^4.0.0" promise-queue "^2.2.5" replaceall "^0.1.6" - semver "^7.3.4" - stream-promise "^3.2.0" + semver "^7.3.5" + signal-exit "^3.0.3" tabtab "^3.0.2" - tar "^6.0.5" + tar "^6.1.11" timers-ext "^0.1.7" - type "^2.1.0" + type "^2.5.0" untildify "^4.0.0" - uuid "^8.3.1" + uuid "^8.3.2" yaml-ast-parser "0.0.43" - yargs-parser "^20.2.4" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -10564,7 +10734,7 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -10583,10 +10753,10 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.24.0: - version "2.25.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.25.0.tgz#55cf97e70817f760db61b8785f5c46f992f29482" - integrity sha512-RMBFWKiPDl3rAoFbEaCVsjQ0v6sgR0q7cyUF9e/4lR81Mf2/5xwop0aCQatUDXKMIXnkuOG6aTEadQmGtOw4mg== +simple-git@^2.44.0: + version "2.45.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.45.0.tgz#4d53b146fc23496099ebfc7af5b0d605d0e3e504" + integrity sha512-wu/Ujs9IXn0HuyYm4HyRvne+EKsjJSWKEMkB3wQa3gNHSMHt7y3oeNX9zRQ3UBPk7bRRMLLHAdIZCZfHT9ehPg== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -10771,13 +10941,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -10802,7 +10965,7 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.19, source-map-support@^0.5.5, source-map-support@^0.5.6, source-map-support@~0.5.12: +source-map-support@^0.5.5, source-map-support@^0.5.6, source-map-support@~0.5.12: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -11176,13 +11339,21 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strip-outer@^1.0.0: +strip-outer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== dependencies: escape-string-regexp "^1.0.2" +strtok3@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.2.4.tgz#302aea64c0fa25d12a0385069ba66253fdc38a81" + integrity sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.0.1" + style-loader@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.1.3.tgz#9e826e69c683c4d9bf9db924f85e9abb30d5e200" @@ -11362,6 +11533,17 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^4.4.10: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" @@ -11375,7 +11557,7 @@ tar@^4.4.10: safe-buffer "^5.1.2" yallist "^3.0.3" -tar@^6.0.1, tar@^6.0.2, tar@^6.0.5: +tar@^6.0.1, tar@^6.0.2: version "6.0.5" resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== @@ -11387,10 +11569,24 @@ tar@^6.0.1, tar@^6.0.2, tar@^6.0.5: mkdirp "^1.0.3" yallist "^4.0.0" -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== +tar@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tencent-serverless-http@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/tencent-serverless-http/-/tencent-serverless-http-1.3.2.tgz#76fb76278bfb08c1d15d8350e723267a76b48314" + integrity sha512-HgIu9HuBdY0lx3jLKuicOSOrjmieklPh55x8ZmtuTnrZ5v1buAPUfLKBhTeBSz6e90ggyW+dPr5PWdz179kUkw== + dependencies: + type-is "^1.6.16" terminal-paginator@^2.0.2: version "2.0.2" @@ -11454,6 +11650,11 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -11482,11 +11683,6 @@ time-stamp@^1.0.1: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - timers-browserify@^2.0.4: version "2.0.11" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" @@ -11597,6 +11793,14 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +token-types@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.1.1.tgz#ef9e8c8e2e0ded9f1b3f8dbaa46a3228b113ba1a" + integrity sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -11713,12 +11917,12 @@ type-fest@^0.11.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -11736,6 +11940,11 @@ type@^2.0.0, type@^2.1.0: resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== +type@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -11852,6 +12061,11 @@ universalify@^1.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -11925,11 +12139,6 @@ url-parse@^1.5.2: querystringify "^2.1.1" requires-port "^1.0.0" -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= - url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" @@ -12014,10 +12223,10 @@ uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0, uuid@^8.3.1: - version "8.3.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" - integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== validate-npm-package-license@^3.0.1: version "3.0.4" @@ -12372,6 +12581,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -12396,18 +12614,23 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@<7.0.0, ws@^6.2.1: +ws@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== dependencies: async-limiter "~1.0.0" -ws@^7.1.2, ws@^7.2.1, ws@^7.3.1: +ws@^7.1.2, ws@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== +ws@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.4.tgz#56bfa20b167427e138a7795de68d134fe92e21f9" + integrity sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg== + ws@~6.1.0: version "6.1.4" resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" @@ -12508,11 +12731,6 @@ yargs-parser@^18.1.0, yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - yargs@15.3.0: version "15.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.0.tgz#403af6edc75b3ae04bf66c94202228ba119f0976" @@ -12599,11 +12817,6 @@ yn@^3.0.0: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - zip-stream@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.0.2.tgz#3a20f1bd7729c2b59fd4efa04df5eb7a5a217d2e" @@ -12613,13 +12826,13 @@ zip-stream@^4.0.0: compress-commons "^4.0.0" readable-stream "^3.6.0" -zip-stream@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.0.4.tgz#3a8f100b73afaa7d1ae9338d910b321dec77ff3a" - integrity sha512-a65wQ3h5gcQ/nQGWV1mSZCEzCML6EK/vyVPcrPNynySP1j3VBbQKh3nhC8CbORb+jfl2vXvh56Ul5odP1bAHqw== +zip-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== dependencies: archiver-utils "^2.1.0" - compress-commons "^4.0.2" + compress-commons "^4.1.0" readable-stream "^3.6.0" zone.js@~0.10.2: From 834218a98a5b8568798e39c0709bd2a17345231c Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 31 Aug 2021 20:41:21 -0700 Subject: [PATCH 0484/1276] Added GitLab Auth Decode Test (#3228) --- .../tests/gitlab_access_token_decode_test.go | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 cla-backend-go/tests/gitlab_access_token_decode_test.go diff --git a/cla-backend-go/tests/gitlab_access_token_decode_test.go b/cla-backend-go/tests/gitlab_access_token_decode_test.go new file mode 100644 index 000000000..8ad05bf0d --- /dev/null +++ b/cla-backend-go/tests/gitlab_access_token_decode_test.go @@ -0,0 +1,54 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package tests + +import ( + "os" + "testing" + + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + ini "github.com/communitybridge/easycla/cla-backend-go/init" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestGitLabAccessTokenDecode(t *testing.T) { // no lint + if false { // nolint + ctx := utils.NewContext() + // Need to initialize the system to load the configuration which contains a number of SSM parameters + stage := os.Getenv("STAGE") + if stage == "" { + assert.Fail(t, "set STAGE environment variable to run unit and functional tests.") + } + dynamodbRegion := os.Getenv("DYNAMODB_AWS_REGION") + if dynamodbRegion == "" { + assert.Fail(t, "set DYNAMODB_AWS_REGION environment variable to run unit and functional tests.") + } + + viper.Set("STAGE", stage) + viper.Set("DYNAMODB_AWS_REGION", dynamodbRegion) + ini.Init() + awsSession, err := ini.GetAWSSession() + if err != nil { + assert.Fail(t, "unable to load AWS session", err) + } + ini.ConfigVariable() + config := ini.GetConfig() + + // Create a new GitLab App client instance + gitLabApp := gitlab_api.Init(config.Gitlab.AppClientID, config.Gitlab.AppClientSecret, config.Gitlab.AppPrivateKey) + + gitLabOrgRepo := gitlab_organizations.NewRepository(awsSession, stage) + + gitlabOrg, err := gitLabOrgRepo.GetGitLabOrganizationByFullPath(ctx, "linuxfoundation/product/easycla") + assert.Nil(t, err, "get gitlab organization by name error should be nil") + assert.NotNil(t, gitlabOrg, "gitlab organization should not nil") + oauthResp, err := gitlab_api.DecryptAuthInfo(gitlabOrg.AuthInfo, gitLabApp) + assert.Nil(t, err, "decrypt auth info error should be nil") + assert.NotNil(t, oauthResp, "oauth response should not be nil") + t.Logf("decoded oauth client with access token : %s", oauthResp.AccessToken) + } +} From b487088be8bbb1237661de10a05a3b3209a3a70d Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 31 Aug 2021 22:29:48 -0700 Subject: [PATCH 0485/1276] EasyCLA Lambda Deployment + Serverless Updates (#3229) --- cla-backend-go/package.json | 8 +- cla-backend-go/serverless.yml | 173 ++-- cla-backend-go/yarn.lock | 1407 +++++++++++++++++++++++++++++-- cla-backend/package.json | 13 +- cla-backend/serverless.yml | 281 ++++--- cla-backend/yarn.lock | 1455 ++++++++++++++++++++------------- 6 files changed, 2506 insertions(+), 831 deletions(-) diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index 2d94ad13f..a3100320f 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -20,11 +20,11 @@ "node.extend": "^2.0.2", "request": "^2.88.0", "serverless": "^2.57.0", - "serverless-finch": "^2.3.2", - "serverless-layers": "^1.4.3", + "serverless-finch": "^2.6.0", + "serverless-layers": "^2.5.1", + "serverless-offline": "^8.0.0", "serverless-plugin-tracing": "^2.0.0", - "serverless-prune-plugin": "^1.4.3", - "serverless-pseudo-parameters": "^2.5.0" + "serverless-prune-plugin": "^1.5.1" }, "resolutions": { "axios": "^0.21.1", diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index 5e23e2e1e..b3b3926f5 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -90,8 +90,8 @@ provider: Action: - ssm:GetParameter Resource: - - "arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/cla-*" - - "arn:aws:ssm:${self:custom.dynamodb.region}:#{AWS::AccountId}:parameter/cla-*" + - "arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/cla-*" + - "arn:aws:ssm:${self:custom.dynamodb.region}:${aws:accountId}:parameter/cla-*" - Effect: Allow Action: - ses:SendEmail @@ -121,91 +121,91 @@ provider: - dynamodb:DescribeStream - dynamodb:ListStreams Resource: - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-session-store" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-store" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-user-permissions" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-companies" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-company-invites" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-session-store" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-store" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-user-permissions" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-metrics" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs" - Effect: Allow Action: - dynamodb:Query Resource: - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/external-company-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/external-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:${self:custom.dynamodb.region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/lf-username-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-users/index/lf-email-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/external-company-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/company-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/external-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/user-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: STAGE: ${self:provider.stage} @@ -290,7 +290,6 @@ provider: plugins: - serverless-plugin-tracing - - serverless-pseudo-parameters # Serverless Finch does s3 uploading. Called with 'sls client deploy'. # Also allows bucket removal with 'sls client remove'. - serverless-finch @@ -305,11 +304,11 @@ functions: # Provide name, otherwise the default will be something like: ${self:service}-${stage}-api # which is fine, but we need to add the name to the platform api-gw config to make lambda-to-lambda invoke call # so, setting this allows us to always know the name of the lambda (hard-coded now, which ignores the stage identifier in the name) - name: ${self:service}-api + name: ${self:service}-api-v4-lambda # must match lfx-api-gw lambda name description: "EasyCLA v2 API" runtime: go1.x - handler: backend-aws-lambda + handler: 'bin/backend-aws-lambda' package: individually: true patterns: - - './bin/backend-aws-lambda' + - 'bin/backend-aws-lambda' diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index 9e645ab1d..db2a2f4e4 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -27,6 +27,266 @@ wraptile "^2.0.0" zames "^2.0.0" +"@hapi/accept@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" + integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/ammo@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-5.0.1.tgz#9d34560f5c214eda563d838c01297387efaab490" + integrity sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/b64@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" + integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/boom@9.x.x", "@hapi/boom@^9.1.0", "@hapi/boom@^9.1.2": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" + integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/bounce@2.x.x", "@hapi/bounce@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-2.0.0.tgz#e6ef56991c366b1e2738b2cd83b01354d938cf3d" + integrity sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/bourne@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" + integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== + +"@hapi/call@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-8.0.1.tgz#9e64cd8ba6128eb5be6e432caaa572b1ed8cd7c0" + integrity sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/catbox-memory@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz#cb63fca0ded01d445a2573b38eb2688df67f70ac" + integrity sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/catbox@^11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-11.1.1.tgz#d277e2d5023fd69cddb33d05b224ea03065fec0c" + integrity sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/podium" "4.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/content@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-5.0.2.tgz#ae57954761de570392763e64cdd75f074176a804" + integrity sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw== + dependencies: + "@hapi/boom" "9.x.x" + +"@hapi/cryptiles@5.x.x": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" + integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== + dependencies: + "@hapi/boom" "9.x.x" + +"@hapi/file@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/file/-/file-2.0.0.tgz#2ecda37d1ae9d3078a67c13b7da86e8c3237dfb9" + integrity sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ== + +"@hapi/h2o2@^9.0.2": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.1.0.tgz#b223f4978b6f2b0d7d9db10a84a567606c4c3551" + integrity sha512-B7E58bMhxmpiDI22clxTexoAaVShNBk1Ez6S8SQjQZu5FxxD6Tqa44sXeZQBtWrdJF7ZRbsY60/C8AHLRxagNA== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + "@hapi/wreck" "17.x.x" + +"@hapi/hapi@^20.1.3": + version "20.1.5" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.1.5.tgz#235dbc6bcc72960724696028c5145c0ecfe6962d" + integrity sha512-BhJ5XFR9uWPUBj/z5pPqXSk8OnvQQU/EbQjwpmjZy0ymNEiq7kIhXkAmzXcntbBHta9o7zpW8XMeXnfV4wudXw== + dependencies: + "@hapi/accept" "^5.0.1" + "@hapi/ammo" "^5.0.1" + "@hapi/boom" "^9.1.0" + "@hapi/bounce" "^2.0.0" + "@hapi/call" "^8.0.0" + "@hapi/catbox" "^11.1.1" + "@hapi/catbox-memory" "^5.0.0" + "@hapi/heavy" "^7.0.1" + "@hapi/hoek" "^9.0.4" + "@hapi/mimos" "^6.0.0" + "@hapi/podium" "^4.1.1" + "@hapi/shot" "^5.0.5" + "@hapi/somever" "^3.0.0" + "@hapi/statehood" "^7.0.3" + "@hapi/subtext" "^7.0.3" + "@hapi/teamwork" "^5.1.0" + "@hapi/topo" "^5.0.0" + "@hapi/validate" "^1.1.1" + +"@hapi/heavy@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-7.0.1.tgz#73315ae33b6e7682a0906b7a11e8ca70e3045874" + integrity sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" + integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== + +"@hapi/iron@6.x.x": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" + integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== + dependencies: + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/mimos@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-6.0.0.tgz#daa523d9c07222c7e8860cb7c9c5501fd6506484" + integrity sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg== + dependencies: + "@hapi/hoek" "9.x.x" + mime-db "1.x.x" + +"@hapi/nigel@4.x.x": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-4.0.2.tgz#8f84ef4bca4fb03b2376463578f253b0b8e863c4" + integrity sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw== + dependencies: + "@hapi/hoek" "^9.0.4" + "@hapi/vise" "^4.0.0" + +"@hapi/pez@^5.0.1": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-5.0.3.tgz#b75446e6fef8cbb16816573ab7da1b0522e7a2a1" + integrity sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA== + dependencies: + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/content" "^5.0.2" + "@hapi/hoek" "9.x.x" + "@hapi/nigel" "4.x.x" + +"@hapi/podium@4.x.x", "@hapi/podium@^4.1.1": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" + integrity sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g== + dependencies: + "@hapi/hoek" "9.x.x" + "@hapi/teamwork" "5.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/shot@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-5.0.5.tgz#a25c23d18973bec93c7969c51bf9579632a5bebd" + integrity sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A== + dependencies: + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/somever@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-3.0.1.tgz#9961cd5bdbeb5bb1edc0b2acdd0bb424066aadcc" + integrity sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w== + dependencies: + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/statehood@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-7.0.3.tgz#655166f3768344ed3c3b50375a303cdeca8040d9" + integrity sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/iron" "6.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/subtext@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-7.0.3.tgz#f7440fc7c966858e1f39681e99eb6171c71e7abd" + integrity sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/content" "^5.0.2" + "@hapi/file" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/pez" "^5.0.1" + "@hapi/wreck" "17.x.x" + +"@hapi/teamwork@5.x.x", "@hapi/teamwork@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124" + integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" + integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/vise@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-4.0.0.tgz#c6a94fe121b94a53bf99e7489f7fcc74c104db02" + integrity sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/wreck@17.x.x": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" + integrity sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/hoek" "9.x.x" + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -453,6 +713,11 @@ dependencies: "@types/node" "*" +"@types/retry@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + "@types/tough-cookie@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" @@ -878,6 +1143,11 @@ arr-swap@^1.0.1: dependencies: is-number "^3.0.0" +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -900,6 +1170,16 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +async-array-reduce@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/async-array-reduce/-/async-array-reduce-0.2.1.tgz#c8be010a2b5cd00dea96c81116034693dfdd82d1" + integrity sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE= + +async-each@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + async@^2.6.1, async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -922,7 +1202,7 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sdk@^2.979.0: +aws-sdk@^2.834.0, aws-sdk@^2.979.0: version "2.980.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== @@ -1031,12 +1311,12 @@ blob@0.0.5: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== -bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: +bluebird@^3.4.1, bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -boxen@^5.0.1: +boxen@^5.0.0, boxen@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== @@ -1083,6 +1363,11 @@ buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3, buffer-crc32@~0. resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -1163,6 +1448,14 @@ cachedir@^2.3.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + camelcase@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" @@ -1189,6 +1482,14 @@ chalk@^2.0.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -1270,6 +1571,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + ci-info@^3.1.1, ci-info@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -1365,6 +1671,16 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= + +clone@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1513,6 +1829,18 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -1535,6 +1863,26 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/copy/-/copy-0.3.2.tgz#870b871d02a599b3c6ef27bc5b3d4c4102261909" + integrity sha512-drDFuUZctIuvSuvL9dOF/v5GxrwB1Q8eMIRlYONC0lSMEq+L2xabXP3jme8cQFdDO8cgP8JsuYhQg7JtTwezmg== + dependencies: + async-each "^1.0.0" + bluebird "^3.4.1" + extend-shallow "^2.0.1" + file-contents "^0.3.1" + glob-parent "^2.0.0" + graceful-fs "^4.1.4" + has-glob "^0.1.1" + is-absolute "^0.2.5" + lazy-cache "^2.0.1" + log-ok "^0.1.1" + matched "^0.4.1" + mkdirp "^0.5.1" + resolve-dir "^0.1.0" + to-file "^0.2.0" + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1579,6 +1927,14 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" +cron-parser@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" + integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== + dependencies: + is-nan "^1.3.0" + moment-timezone "^0.5.31" + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -1590,6 +1946,25 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +cuid@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0" + integrity sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg== + currify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/currify/-/currify-3.0.0.tgz#ec5b18fe65c2b3b08daba7f2a75a01063b2c89c2" @@ -1636,6 +2011,13 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -1736,6 +2118,13 @@ deferred@^0.7.11: next-tick "^1.0.0" timers-ext "^0.1.7" +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -1799,6 +2188,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + dot-qs@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dot-qs/-/dot-qs-0.2.0.tgz#d36517fe24b7cda61fce7a5026a0024afaf5a439" @@ -1850,6 +2246,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -1912,6 +2315,38 @@ error-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= +es-abstract@^1.18.0-next.2: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.47, es5-ext@^0.10.49, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" @@ -1972,6 +2407,11 @@ es6-weak-map@^2.0.2, es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2003,11 +2443,31 @@ event-emitter@^0.3.5, event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + exit-on-epipe@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" @@ -2018,6 +2478,13 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +expand-tilde@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" + integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk= + dependencies: + os-homedir "^1.0.1" + ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -2047,14 +2514,14 @@ ext@^1.1.2: dependencies: type "^2.0.0" -extend-shallow@^2.0.1: +extend-shallow@^2.0.0, extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= dependencies: is-extendable "^0.1.0" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2154,6 +2621,56 @@ figures@^3.0.0, figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" +file-contents@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/file-contents/-/file-contents-0.2.4.tgz#0506f7b8eff62afa45ae45da4df9e9d47df453cb" + integrity sha1-BQb3uO/2KvpFrkXaTfnp1H30U8s= + dependencies: + extend-shallow "^2.0.0" + file-stat "^0.1.0" + graceful-fs "^4.1.2" + is-buffer "^1.1.0" + is-utf8 "^0.2.0" + lazy-cache "^0.2.3" + through2 "^2.0.0" + +file-contents@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/file-contents/-/file-contents-0.3.2.tgz#a0939fed1b8cda1580266fc6b753a232fb46de53" + integrity sha1-oJOf7RuM2hWAJm/Gt1OiMvtG3lM= + dependencies: + define-property "^0.2.5" + extend-shallow "^2.0.1" + file-stat "^0.2.3" + fs-exists-sync "^0.1.0" + graceful-fs "^4.1.4" + is-buffer "^1.1.3" + isobject "^2.1.0" + lazy-cache "^2.0.1" + strip-bom-buffer "^0.1.1" + strip-bom-string "^0.1.2" + through2 "^2.0.1" + vinyl "^1.1.1" + +file-stat@^0.1.0: + version "0.1.3" + resolved "https://registry.yarnpkg.com/file-stat/-/file-stat-0.1.3.tgz#d0f1961d7d10732928120a6e6955471c2a5b5411" + integrity sha1-0PGWHX0QcykoEgpuaVVHHCpbVBE= + dependencies: + graceful-fs "^4.1.2" + lazy-cache "^0.2.3" + through2 "^2.0.0" + +file-stat@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/file-stat/-/file-stat-0.2.3.tgz#469a7e927d6930079624cdb38109405456cb06a9" + integrity sha1-Rpp+kn1pMAeWJM2zgQlAVFbLBqk= + dependencies: + fs-exists-sync "^0.1.0" + graceful-fs "^4.1.4" + lazy-cache "^2.0.1" + through2 "^2.0.1" + file-type@^16.5.3: version "16.5.3" resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06" @@ -2227,6 +2744,15 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +folder-hash@^3.3.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/folder-hash/-/folder-hash-3.3.3.tgz#883c8359d54f91b3f02c1a646c00c30e5831365b" + integrity sha512-SDgHBgV+RCjrYs8aUwCb9rTgbTVuSdzvFmLaChsLre1yf+D64khCW++VYciaByZ8Rm0uKF8R/XEpXuTRSGUM1A== + dependencies: + debug "^4.1.1" + graceful-fs "~4.2.0" + minimatch "~3.0.4" + follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" @@ -2289,6 +2815,11 @@ fs-copy-file@^2.1.2: dependencies: "@cloudcmd/copy-file" "^1.1.0" +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= + fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -2298,6 +2829,15 @@ fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" @@ -2385,6 +2925,15 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" @@ -2412,7 +2961,7 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.1: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -2429,6 +2978,13 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= + dependencies: + is-glob "^2.0.0" + glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -2448,6 +3004,43 @@ glob@^7.0.5, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + +global-modules@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" + integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0= + dependencies: + global-prefix "^0.1.4" + is-windows "^0.2.0" + +global-prefix@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" + integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948= + dependencies: + homedir-polyfill "^1.0.0" + ini "^1.3.4" + is-windows "^0.2.0" + which "^1.2.12" + globby@^11.0.4: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" @@ -2499,7 +3092,7 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.8: +graceful-fs@^4.1.4, graceful-fs@^4.2.8, graceful-fs@~4.2.0: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -2524,6 +3117,11 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -2546,11 +3144,35 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-glob@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-0.1.1.tgz#a261c4c2a6c667e0c77b700a7f297c39ef3aa589" + integrity sha1-omHEwqbGZ+DHe3AKfyl8Oe86pYk= + dependencies: + is-glob "^2.0.1" + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -2558,6 +3180,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +homedir-polyfill@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -2588,6 +3217,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + iconv-lite@^0.4.24, iconv-lite@~0.4.11: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -2615,6 +3249,11 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -2643,7 +3282,7 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.7, ini@~1.3.0: +ini@2.0.0, ini@^1.3.4, ini@^1.3.7, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== @@ -2702,6 +3341,23 @@ install@^0.13.0: resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776" integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA== +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-absolute@^0.2.5: + version "0.2.6" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" + integrity sha1-IN5p89uULvLYe5wto28XIjWxtes= + dependencies: + is-relative "^0.2.1" + is-windows "^0.2.0" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -2721,6 +3377,13 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -2728,11 +3391,31 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.5: +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.0, is-buffer@^1.1.3, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -2747,6 +3430,13 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -2780,6 +3470,11 @@ is-extendable@^0.1.0, is-extendable@^0.1.1: resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2802,6 +3497,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= + dependencies: + is-extglob "^1.0.0" + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -2809,11 +3511,44 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-nan@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -2831,6 +3566,16 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -2848,16 +3593,72 @@ is-promise@^2.1, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== +is-regex@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-relative@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" + integrity sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU= + dependencies: + is-unc-path "^0.1.1" + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unc-path@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" + integrity sha1-arBTpyVzwQJQ/0FqOBTDUXivObk= + dependencies: + unc-path-regex "^0.1.0" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-valid-glob@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" + integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4= + +is-windows@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" + integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw= + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -2870,6 +3671,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + is@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" @@ -2880,21 +3686,28 @@ is_js@^0.9.0: resolved "https://registry.yarnpkg.com/is_js/-/is_js-0.9.0.tgz#0ab94540502ba7afa24c856aa985561669e9c52d" integrity sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0= +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isarray@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= -isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isobject@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -2910,11 +3723,21 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +java-invoke-local@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/java-invoke-local/-/java-invoke-local-0.0.6.tgz#0e04b20b5e306a1e8384846a9ac286790ee6d868" + integrity sha512-gZmQKe1QrfkkMjCn8Qv9cpyJFyogTYqkP5WCobX5RNaHsJzIV/6NvAnlnouOcwKr29QrxLGDGcqYuJ+ae98s1A== + jmespath@0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= + js-yaml@^3.13.1, js-yaml@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" @@ -3003,6 +3826,32 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^5.0.2: + version "5.1.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz#2fc4b2e461950626c98525425a3a3518b85af6c3" + integrity sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ== + +jsonschema@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" + integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== + +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3013,7 +3862,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.7.1: +jszip@^3.5.0, jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -3023,6 +3872,23 @@ jszip@^3.7.1: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" @@ -3097,6 +3963,18 @@ kuler@1.0.x: dependencies: colornames "^1.1.1" +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + +lazy-cache@^0.2.3: + version "0.2.7" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" + integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U= + lazy-cache@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" @@ -3133,11 +4011,41 @@ lodash.flatten@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -3192,6 +4100,11 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" +long-timeout@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" + integrity sha1-lyHXiLR+C8taJMLivuGg2lXatRQ= + long@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/long/-/long-1.1.2.tgz#eaef5951ca7551d96926b82da242db9d6b28fb53" @@ -3226,6 +4139,11 @@ lru-queue@0.1, lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +luxon@^1.25.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -3233,13 +4151,20 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-dir@^3.1.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -3247,11 +4172,34 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +matched@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/matched/-/matched-0.4.4.tgz#56d7b7eb18033f0cf9bc52eb2090fac7dc1e89fa" + integrity sha1-Vte36xgDPwz5vFLrIJD6x9weifo= + dependencies: + arr-union "^3.1.0" + async-array-reduce "^0.2.0" + extend-shallow "^2.0.1" + fs-exists-sync "^0.1.0" + glob "^7.0.5" + has-glob "^0.1.1" + is-valid-glob "^0.3.0" + lazy-cache "^2.0.1" + resolve-dir "^0.1.0" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +mem@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/mem/-/mem-6.1.1.tgz#ea110c2ebc079eca3022e6b08c85a795e77f6318" + integrity sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^3.0.0" + memoizee@^0.4.14: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" @@ -3280,6 +4228,11 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -3311,7 +4264,7 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-db@1.49.0: +mime-db@1.49.0, mime-db@1.x.x: version "1.49.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== @@ -3350,6 +4303,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -3360,7 +4318,7 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -3395,7 +4353,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -3407,6 +4365,18 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +moment-timezone@^0.5.31: + version "0.5.33" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" + integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0": + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3512,6 +4482,15 @@ node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-schedule@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.3.tgz#f8e01c5fb9597f09ecf9c4c25d6938e5e7a06f48" + integrity sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw== + dependencies: + cron-parser "^2.18.0" + long-timeout "0.1.1" + sorted-array-functions "^1.3.0" + node.extend@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" @@ -3535,6 +4514,13 @@ normalize-url@^4.1.0, normalize-url@^4.5.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -3574,6 +4560,16 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -3581,6 +4577,26 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.fromentries@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3600,7 +4616,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -3648,6 +4664,11 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + p-event@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" @@ -3660,7 +4681,31 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-timeout@^3.1.0: +p-memoize@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/p-memoize/-/p-memoize-4.0.1.tgz#6f4231857fec10de2504611fe820c808fa8c5f8b" + integrity sha512-km0sP12uE0dOZ5qP+s7kGVf07QngxyG0gS8sYFvFWhqlgzOsSy+m71aUejf/0akxj5W7gE//2G74qTv6b4iMog== + dependencies: + mem "^6.0.1" + mimic-fn "^3.0.0" + +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-retry@^4.3.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.13.1" + +p-timeout@^3.1.0, p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== @@ -3682,6 +4727,11 @@ pako@~1.0.2: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + parseqs@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" @@ -3702,6 +4752,11 @@ path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-loader@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/path-loader/-/path-loader-1.0.10.tgz#dd3d1bd54cb6f2e6423af2ad334a41cc0bce4cf6" @@ -3772,11 +4827,27 @@ pipe-io@^3.0.0: resolved "https://registry.yarnpkg.com/pipe-io/-/pipe-io-3.0.12.tgz#90ff84888876a1feccbf9f753eacf22b260b2884" integrity sha512-reR49NtpkVgedzCQ9DPV727VAZKw8Ax3N/3iQwD1vHxTmswsuhurFh0Z5woVNM1OhHDigKzDN7u4kNipAA9yyA== +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + pointer-symbol@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pointer-symbol/-/pointer-symbol-1.0.0.tgz#60f9110204ea7a929b62644a21315543cbb3d447" integrity sha1-YPkRAgTqepKbYmRKITFVQ8uz1Ec= +portfinder@^1.0.28: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + prebuild-install@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" @@ -3969,6 +5040,13 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" @@ -4127,6 +5205,11 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= + replaceall@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e" @@ -4179,6 +5262,14 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== +resolve-dir@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" + integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4= + dependencies: + expand-tilde "^1.2.2" + global-modules "^0.2.3" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -4214,6 +5305,11 @@ retry@^0.10.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -4280,16 +5376,35 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" -semver@^5.4.1, semver@^5.5.0: +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.1, semver@^6.2.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.2, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" @@ -4297,14 +5412,7 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -serverless-finch@^2.3.2: +serverless-finch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/serverless-finch/-/serverless-finch-2.6.0.tgz#c74e7492dbfae52aa6383d4a21bac9138bcd9383" integrity sha512-G5umIBoNyo3MKCtdtbbkkb/7Z84qNstbQnkdscG/VhukYUib+7BiWidAMI+WAFq+JEUf3PW7c3bvt/uFEiMnnA== @@ -4314,34 +5422,71 @@ serverless-finch@^2.3.2: minimatch "^3.0.4" prompt-confirm "^1.2.0" -serverless-layers@^1.4.3: - version "1.5.0" - resolved "https://registry.yarnpkg.com/serverless-layers/-/serverless-layers-1.5.0.tgz#f1596c7f65f9ef76d061e0d0f8b908c32b50c94e" - integrity sha512-/VnGeEVoaE8w23lgMRw5W3CMlf/7tLq35px+Ab0QyvCK+WnNKX5VtHUzDyIBA/WrFXw7XXWeNKnqLezsuoiLyw== +serverless-layers@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/serverless-layers/-/serverless-layers-2.5.1.tgz#2343920c3618a10643d4646f3d825c56b6e31047" + integrity sha512-gG1ph4J4SamX185sC/MZAVBhae74vJXU8PocnKHkIogGKvw45dhsflh49tU27nnS5PBCDuA5OeHbRfDqTr++4Q== dependencies: "@babel/runtime" "^7.3.1" archiver "^3.0.0" bluebird "^3.5.3" + chalk "^3.0.0" + copy "^0.3.2" + folder-hash "^3.3.0" fs-copy-file "^2.1.2" + fs-extra "^8.1.0" + glob "^7.1.6" mkdirp "^0.5.1" + semver "^7.3.2" + slugify "^1.4.0" + +serverless-offline@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/serverless-offline/-/serverless-offline-8.0.0.tgz#66ffb69d8f631b096957b9cc657cc5c66600f1b8" + integrity sha512-ov5PbO8gbT+KtHK0rA6NiYnNdzeVaCt8FLn9sJGN2nPvuyTbemJ5i0eOeOOX8Wn9LsHIIyYuxGa+tQM353M8wg== + dependencies: + "@hapi/boom" "^9.1.2" + "@hapi/h2o2" "^9.0.2" + "@hapi/hapi" "^20.1.3" + aws-sdk "^2.834.0" + boxen "^5.0.0" + chalk "^4.1.0" + cuid "^2.1.8" + execa "^5.0.0" + extend "^3.0.2" + fs-extra "^9.1.0" + java-invoke-local "0.0.6" + js-string-escape "^1.0.1" + jsonpath-plus "^5.0.2" + jsonschema "^1.4.0" + jsonwebtoken "^8.5.1" + jszip "^3.5.0" + luxon "^1.25.0" + node-fetch "^2.6.1" + node-schedule "^1.3.3" + object.fromentries "^2.0.3" + p-memoize "^4.0.1" + p-queue "^6.6.2" + p-retry "^4.3.0" + please-upgrade-node "^3.2.0" + portfinder "^1.0.28" + semver "^7.3.4" + update-notifier "^5.0.1" + velocityjs "^2.0.3" + ws "^7.4.2" serverless-plugin-tracing@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/serverless-plugin-tracing/-/serverless-plugin-tracing-2.0.0.tgz#df6b8b3166ac9bb70a37c7fc875014b2369158f6" integrity sha1-32uLMWasm7cKN8f8h1AUsjaRWPY= -serverless-prune-plugin@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/serverless-prune-plugin/-/serverless-prune-plugin-1.4.3.tgz#556d76a86e37bf57d4ccd8449a7d98b6496bd5ed" - integrity sha512-gsZF3oLs5rFdp6ynjiWf5cuXZ4DZrAhxRd5Zf2gfH/43kPqtZMZzUqcGYbHh1OXbOzogdn8fEg5d4Q3xxWwRBA== +serverless-prune-plugin@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/serverless-prune-plugin/-/serverless-prune-plugin-1.5.1.tgz#a628551151228eeee8da2f454d0b062d9c1dda40" + integrity sha512-lCxHNQXmGL0GEe7gBGOx1MdafYXAi9RkiuKPE4DDSSdrdbVnn0KLcbriKWGC2MdOpn/vs358MaeBuVSefuzMzA== dependencies: bluebird "^3.4.7" -serverless-pseudo-parameters@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/serverless-pseudo-parameters/-/serverless-pseudo-parameters-2.5.0.tgz#f30bf34db166e4b8b22144a8e65aca71b90dd1e6" - integrity sha512-A/O49AR8LL6jlnPSmnOTYgL1KqVgskeRla4sVDeS/r5dHFJlwOU5MgFilc7aaQP8NWAwRJANaIS9oiSE3I+VUA== - serverless@^2.57.0: version "2.57.0" resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.57.0.tgz#74b5347f50969d81869762f53c92221d92e9999a" @@ -4450,11 +5595,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shortid@^2.2.14: version "2.2.15" resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122" @@ -4462,6 +5619,15 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -4502,6 +5668,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slugify@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.0.tgz#6bdf8ed01dabfdc46425b67e3320b698832ff893" + integrity sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang== + snappy@^6.0.1: version "6.3.5" resolved "https://registry.yarnpkg.com/snappy/-/snappy-6.3.5.tgz#c14b8dea8e9bc2687875b5e491d15dd900e6023c" @@ -4551,6 +5722,11 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +sorted-array-functions@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" + integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== + split2@^3.1.1: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -4652,6 +5828,22 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -4694,6 +5886,19 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-bom-buffer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/strip-bom-buffer/-/strip-bom-buffer-0.1.1.tgz#ca3ddc4919c13f9fddf30b1dff100a9835248b4d" + integrity sha1-yj3cSRnBP5/d8wsd/xAKmDUki00= + dependencies: + is-buffer "^1.1.0" + is-utf8 "^0.2.0" + +strip-bom-string@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-0.1.2.tgz#9c6e720a313ba9836589518405ccfb88a5f41b9c" + integrity sha1-nG5yCjE7qYNliVGEBcz7iKX0G5w= + strip-color@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/strip-color/-/strip-color-0.1.0.tgz#106f65d3d3e6a2d9401cac0eb0ce8b8a702b4f7b" @@ -4706,6 +5911,11 @@ strip-dirs@^2.0.0: dependencies: is-natural-number "^4.0.1" +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -4856,6 +6066,14 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +through2@^2.0.0, through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4891,6 +6109,20 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== +to-file@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/to-file/-/to-file-0.2.0.tgz#236c6c088065e570defbd15cf4b4e565be46ea93" + integrity sha1-I2xsCIBl5XDe+9Fc9LTlZb5G6pM= + dependencies: + define-property "^0.2.5" + extend-shallow "^2.0.1" + file-contents "^0.2.4" + glob-parent "^2.0.0" + is-valid-glob "^0.3.0" + isobject "^2.1.0" + lazy-cache "^2.0.1" + vinyl "^1.1.1" + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -5012,6 +6244,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -5020,6 +6262,18 @@ unbzip2-stream@^1.0.9: buffer "^5.2.1" through "^2.3.8" +unc-path-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -5045,6 +6299,26 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +update-notifier@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + uri-js@^4.2.2: version "4.4.0" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" @@ -5094,6 +6368,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +velocityjs@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/velocityjs/-/velocityjs-2.0.3.tgz#cc772f687061997127b7d8a827dbef3af8a0bbe6" + integrity sha512-sUkygY7HwvbKZvS3naiI7t2o4RTqui6efSwTXLb03igdvPKm3SwCpnqA2kU4/jLD2f0eHB9xPoiza9XAkpuU+g== + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -5103,23 +6382,50 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vinyl@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" + integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + warning-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" integrity sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.2.9: +which@^1.2.12, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -5193,7 +6499,7 @@ write-file-atomic@^2.4.3: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.3: +write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== @@ -5203,11 +6509,16 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: +ws@^7.3.1, ws@^7.4.2, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" @@ -5226,7 +6537,7 @@ xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== -xtend@^4.0.0: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== diff --git a/cla-backend/package.json b/cla-backend/package.json index 8b3e21772..92cb8160a 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -35,14 +35,13 @@ "request": "^2.88.0", "serverless": "^2.57.0", "serverless-domain-manager": "^5.1.0", - "serverless-finch": "^2.3.2", - "serverless-layers": "^1.4.3", - "serverless-offline": "^6.1.5", + "serverless-finch": "^2.6.0", + "serverless-layers": "^2.5.1", + "serverless-offline": "^8.0.0", "serverless-plugin-tracing": "^2.0.0", - "serverless-prune-plugin": "^1.4.2", - "serverless-pseudo-parameters": "^2.5.0", - "serverless-python-requirements": "^4.2.5", - "serverless-wsgi": "^1.5.2" + "serverless-prune-plugin": "^1.5.1", + "serverless-python-requirements": "^5.1.1", + "serverless-wsgi": "^2.0.1" }, "resolutions": { "axios": "^0.21.1", diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 6fc652b2e..af804ba41 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -20,11 +20,11 @@ package: - '!.git*' - '!.git/**' - '!.vscode/**' - - '!.serverless-wsgi' - '!.pylintrc' - '!node_modules/**' - '!package-lock.json' - '!yarn.lock' + - '.serverless-wsgi' custom: allowed_origins: ${file(./env.json):cla-allowed-origins-${opt:stage}, ssm:/cla-allowed-origins-${opt:stage}} @@ -37,7 +37,7 @@ custom: prune: automatic: true number: 3 - userEventsSNSTopicARN: arn:aws:sns:us-east-2:#{AWS::AccountId}:userservice-triggers-${self:provider.stage}-user-sns-topic + userEventsSNSTopicARN: arn:aws:sns:us-east-2:${aws:accountId}:userservice-triggers-${self:provider.stage}-user-sns-topic certificate: arn: @@ -165,12 +165,12 @@ provider: Action: - lambda:InvokeFunction Resource: - - "arn:aws:lambda:${self:provider.region}:#{AWS::AccountId}:function:cla-backend-${opt:stage}-zipbuilder-lambda" + - "arn:aws:lambda:${self:provider.region}:${aws:accountId}:function:cla-backend-${opt:stage}-zipbuilder-lambda" - Effect: Allow Action: - ssm:GetParameter Resource: - - "arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/cla-*" + - "arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/cla-*" - Effect: Allow Action: - ses:SendEmail @@ -205,92 +205,92 @@ provider: - dynamodb:DescribeStream - dynamodb:ListStreams Resource: - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-session-store" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-store" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-user-permissions" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-company-invites" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-session-store" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-store" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-user-permissions" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-metrics" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs" - Effect: Allow Action: - dynamodb:Query Resource: - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-username-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-username-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-users/index/lf-email-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/external-company-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/external-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/user-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/lf-username-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/lf-email-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/external-company-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/company-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/external-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/user-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: STAGE: ${self:provider.stage} @@ -378,7 +378,6 @@ plugins: - serverless-python-requirements - serverless-wsgi - serverless-plugin-tracing - - serverless-pseudo-parameters # Serverless Finch does s3 uploading. Called with 'sls client deploy'. # Also allows bucket removal with 'sls client remove'. - serverless-finch @@ -398,10 +397,11 @@ functions: patterns: - 'auth/bin/**' - apiv3: - runtime: go1.x - handler: backend-aws-lambda + api-v3-lambda: + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-api-v3-lambda description: "EasyCLA Golang API handler for the /v3 endpoints" + runtime: go1.x + handler: 'bin/backend-aws-lambda' events: - http: method: ANY @@ -410,72 +410,80 @@ functions: package: individually: true patterns: - - './bin/backend-aws-lambda' + - '!**' + - 'bin/backend-aws-lambda' dynamo-projects-events-lambda: - handler: dynamo-events-lambda name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-projects-lambda description: "EasyCLA DynamoDB stream events handler for the projects table" + handler: 'bin/dynamo-events-lambda' runtime: go1.x package: individually: true patterns: - - './bin/dynamo-events-lambda' + - '!**' + - 'bin/dynamo-events-lambda' dynamo-signatures-events-lambda: - handler: dynamo-events-lambda + handler: 'bin/dynamo-events-lambda' name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-signatures-events-lambda description: "EasyCLA DynamoDB stream events handler for the signatures table" runtime: go1.x package: individually: true patterns: - - './bin/dynamo-events-lambda' + - '!**' + - 'bin/dynamo-events-lambda' dynamo-events-events-lambda: - handler: dynamo-events-lambda + handler: 'bin/dynamo-events-lambda' name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-events-events-lambda description: "EasyCLA DynamoDB stream events handler for the events table" runtime: go1.x package: individually: true patterns: - - './bin/dynamo-events-lambda' + - '!**' + - 'bin/dynamo-events-lambda' dynamo-repositories-events-lambda: - handler: dynamo-events-lambda + handler: 'bin/dynamo-events-lambda' name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-repositories-events-lambda description: "EasyCLA DynamoDB stream events handler for the repositories table" runtime: go1.x package: individually: true patterns: - - './bin/dynamo-events-lambda' + - '!**' + - 'bin/dynamo-events-lambda' dynamo-projects-cla-groups-events-lambda: - handler: dynamo-events-lambda + handler: 'bin/dynamo-events-lambda' name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-projects-cla-groups-events-lambda description: "EasyCLA DynamoDB stream events handler for the projects-cla-groups table" runtime: go1.x package: individually: true patterns: - - './bin/dynamo-events-lambda' + - '!**' + - 'bin/dynamo-events-lambda' dynamo-github-orgs-events-lambda: - handler: dynamo-events-lambda + handler: 'bin/dynamo-events-lambda' name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-github-orgs-events-lambda description: "EasyCLA DynamoDB stream events handler for cla--github-orgs the table" runtime: go1.x package: individually: true patterns: - - './bin/dynamo-events-lambda' + - '!**' + - 'bin/dynamo-events-lambda' - saveMetrics: + save-metrics-lambda: + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-save-metrics-lambda description: "EasyCLA Save Metrics API handler" runtime: go1.x - handler: metrics-aws-lambda + handler: 'bin/metrics-aws-lambda' timeout: 900 # maximum time allowed events: - schedule: @@ -485,12 +493,14 @@ functions: package: individually: true patterns: - - './bin/metrics-aws-lambda' + - '!**' + - 'bin/metrics-aws-lambda' - reportMetrics: + report-metrics-lambda: + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-report-metrics-lambda description: "EasyCLA Report Metrics API handler" runtime: go1.x - handler: metrics-report-lambda + handler: 'bin/metrics-report-lambda' timeout: 900 # maximum time allowed events: - schedule: @@ -500,12 +510,13 @@ functions: package: individually: true patterns: - - './bin/metrics-report-lambda' + - '!**' + - 'bin/metrics-report-lambda' - zipbuilder-scheduler-lambda: - handler: zipbuilder-scheduler-lambda - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-zipbuilder-scheduler-lambda + zip-builder-scheduler-lambda: + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-zip-builder-scheduler-lambda description: "call zipbuilder-lambda for all cla groups periodically" + handler: 'bin/zipbuilder-scheduler-lambda' runtime: go1.x timeout: 900 # maximum time allowed events: @@ -516,11 +527,12 @@ functions: package: individually: true patterns: - - './bin/zipbuilder-scheduler-lambda' + - '!**' + - 'bin/zipbuilder-scheduler-lambda' - zipbuilder-lambda: - handler: zipbuilder-lambda - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-zipbuilder-lambda + zip-builder-lambda: + handler: 'bin/zipbuilder-lambda' + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-zip-builder-lambda description: "build zip of signed signature pdf for cla group" runtime: go1.x timeout: 900 # maximum time allowed @@ -528,10 +540,11 @@ functions: package: individually: true patterns: - - './bin/zipbuilder-lambda' + - '!**' + - 'bin/zipbuilder-lambda' gitlab-repository-check-lambda: - handler: gitlab-repository-check-lambda + handler: 'bin/gitlab-repository-check-lambda' name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-gitlab-repository-check-lambda description: "routine to periodically check the GitLab repository list for auto-enabled GitLab Groups" runtime: go1.x @@ -545,7 +558,24 @@ functions: package: individually: true patterns: - - './bin/gitlab-repository-check-lambda' + - '!**' + - 'bin/gitlab-repository-check-lambda' + + # User Subscribe event for dynamodb cla-stage-users table. + easycla-user-event-handler-lambda: + handler: 'bin/user-subscribe-lambda' + name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-user-event-handler-lambda + runtime: go1.x + description: Update easycla user data to user object in dynamodb + package: + individually: true + patterns: + - '!**' + - 'bin/user-subscribe-lambda' + reservedConcurrency: 5 + events: + - sns: + arn: ${self:custom.userEventsSNSTopicARN} apiv1: handler: wsgi_handler.handler @@ -601,21 +631,6 @@ functions: method: POST path: v2/github/activity - # User Subscribe event for dynamodb cla-stage-users table. - easyClaUserSubscribe: - handler: user-subscribe-lambda - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-user-subscribe-lambda - runtime: go1.x - description: Update easycla user data to user object in dynamodb - package: - individually: true - patterns: - - './bin/user-subscribe-lambda' - reservedConcurrency: 5 - events: - - sns: - arn: ${self:custom.userEventsSNSTopicARN} - resources: Conditions: # Helper functions since we conditionally create some resources diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 2e1b0fbf7..99a5aec79 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -27,292 +27,270 @@ wraptile "^2.0.0" zames "^2.0.0" -"@hapi/accept@^3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-3.2.4.tgz#687510529493fe1d7d47954c31aff360d9364bd1" - integrity sha512-soThGB+QMgfxlh0Vzhzlf3ZOEOPk5biEwcOXhkF0Eedqx8VnhGiggL9UYHrIsOb1rUg3Be3K8kp0iDL2wbVSOQ== +"@hapi/accept@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" + integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - -"@hapi/address@2.x.x", "@hapi/address@^2.1.2": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" - integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/ammo@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-3.1.2.tgz#a9edf5d48d99b75fdcd7ab3dabf9059942a06961" - integrity sha512-ej9OtFmiZv1qr45g1bxEZNGyaR4jRpyMxU6VhbxjaYThymvOwsyIsUKMZnP5Qw2tfYFuwqCJuIBHGpeIbdX9gQ== +"@hapi/ammo@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-5.0.1.tgz#9d34560f5c214eda563d838c01297387efaab490" + integrity sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/b64@4.x.x": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-4.2.1.tgz#bf8418d7907c5e73463f2e3b5c6fca7e9f2a1357" - integrity sha512-zqHpQuH5CBMw6hADzKfU/IGNrxq1Q+/wTYV+OiZRQN9F3tMyk+9BUMeBvFRMamduuqL8iSp62QAnJ+7ATiYLWA== +"@hapi/b64@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" + integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/boom@7.x.x", "@hapi/boom@^7.4.11": - version "7.4.11" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.11.tgz#37af8417eb9416aef3367aa60fa04a1a9f1fc262" - integrity sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A== +"@hapi/boom@9.x.x", "@hapi/boom@^9.1.0", "@hapi/boom@^9.1.2": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" + integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/bounce@1.x.x": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-1.3.2.tgz#3b096bb02f67de6115e6e4f0debc390be5a86bad" - integrity sha512-3bnb1AlcEByFZnpDIidxQyw1Gds81z/1rSqlx4bIEE+wUN0ATj0D49B5cE1wGocy90Rp/de4tv7GjsKd5RQeew== +"@hapi/bounce@2.x.x", "@hapi/bounce@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-2.0.0.tgz#e6ef56991c366b1e2738b2cd83b01354d938cf3d" + integrity sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "^8.3.1" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/bourne@1.x.x": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" - integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== +"@hapi/bourne@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" + integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== -"@hapi/call@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@hapi/call/-/call-5.1.3.tgz#217af45e3bc3d38b03aa5c9edfe1be939eee3741" - integrity sha512-5DfWpMk7qZiYhvBhM5oUiT4GQ/O8a2rFR121/PdwA/eZ2C1EsuD547ZggMKAR5bZ+FtxOf0fdM20zzcXzq2mZA== +"@hapi/call@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-8.0.1.tgz#9e64cd8ba6128eb5be6e432caaa572b1ed8cd7c0" + integrity sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/catbox-memory@4.x.x": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-4.1.1.tgz#263a6f3361f7a200552c5772c98a8e80a1da712f" - integrity sha512-T6Hdy8DExzG0jY7C8yYWZB4XHfc0v+p1EGkwxl2HoaPYAmW7I3E59M/IvmSVpis8RPcIoBp41ZpO2aZPBpM2Ww== +"@hapi/catbox-memory@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz#cb63fca0ded01d445a2573b38eb2688df67f70ac" + integrity sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/catbox@10.x.x": - version "10.2.3" - resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-10.2.3.tgz#2df51ab943d7613df3718fa2bfd981dd9558cec5" - integrity sha512-kN9hXO4NYyOHW09CXiuj5qW1syc/0XeVOBsNNk0Tz89wWNQE5h21WF+VsfAw3uFR8swn/Wj3YEVBnWqo82m/JQ== +"@hapi/catbox@^11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-11.1.1.tgz#d277e2d5023fd69cddb33d05b224ea03065fec0c" + integrity sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - "@hapi/podium" "3.x.x" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/podium" "4.x.x" + "@hapi/validate" "1.x.x" -"@hapi/content@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-4.1.1.tgz#179673d1e2b7eb36c564d8f9605d019bd2252cbf" - integrity sha512-3TWvmwpVPxFSF3KBjKZ8yDqIKKZZIm7VurDSweYpXYENZrJH3C1hd1+qEQW9wQaUaI76pPBLGrXl6I3B7i3ipA== +"@hapi/content@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-5.0.2.tgz#ae57954761de570392763e64cdd75f074176a804" + integrity sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw== dependencies: - "@hapi/boom" "7.x.x" + "@hapi/boom" "9.x.x" -"@hapi/cryptiles@4.x.x": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-4.2.1.tgz#ff0f18d79074659838caedbb911851313ad1ffbc" - integrity sha512-XoqgKsHK0l/VpqPs+tr6j6vE+VQ3+2bkF2stvttmc7xAOf1oSAwHcJ0tlp/6MxMysktt1IEY0Csy3khKaP9/uQ== +"@hapi/cryptiles@5.x.x": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" + integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== dependencies: - "@hapi/boom" "7.x.x" + "@hapi/boom" "9.x.x" -"@hapi/file@1.x.x": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@hapi/file/-/file-1.0.0.tgz#c91c39fd04db8bed5af82d2e032e7a4e65555b38" - integrity sha512-Bsfp/+1Gyf70eGtnIgmScvrH8sSypO3TcK3Zf0QdHnzn/ACnAkI6KLtGACmNRPEzzIy+W7aJX5E+1fc9GwIABQ== +"@hapi/file@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/file/-/file-2.0.0.tgz#2ecda37d1ae9d3078a67c13b7da86e8c3237dfb9" + integrity sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ== -"@hapi/formula@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-1.2.0.tgz#994649c7fea1a90b91a0a1e6d983523f680e10cd" - integrity sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA== +"@hapi/h2o2@^9.0.2": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.1.0.tgz#b223f4978b6f2b0d7d9db10a84a567606c4c3551" + integrity sha512-B7E58bMhxmpiDI22clxTexoAaVShNBk1Ez6S8SQjQZu5FxxD6Tqa44sXeZQBtWrdJF7ZRbsY60/C8AHLRxagNA== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + "@hapi/wreck" "17.x.x" + +"@hapi/hapi@^20.1.3": + version "20.1.5" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.1.5.tgz#235dbc6bcc72960724696028c5145c0ecfe6962d" + integrity sha512-BhJ5XFR9uWPUBj/z5pPqXSk8OnvQQU/EbQjwpmjZy0ymNEiq7kIhXkAmzXcntbBHta9o7zpW8XMeXnfV4wudXw== + dependencies: + "@hapi/accept" "^5.0.1" + "@hapi/ammo" "^5.0.1" + "@hapi/boom" "^9.1.0" + "@hapi/bounce" "^2.0.0" + "@hapi/call" "^8.0.0" + "@hapi/catbox" "^11.1.1" + "@hapi/catbox-memory" "^5.0.0" + "@hapi/heavy" "^7.0.1" + "@hapi/hoek" "^9.0.4" + "@hapi/mimos" "^6.0.0" + "@hapi/podium" "^4.1.1" + "@hapi/shot" "^5.0.5" + "@hapi/somever" "^3.0.0" + "@hapi/statehood" "^7.0.3" + "@hapi/subtext" "^7.0.3" + "@hapi/teamwork" "^5.1.0" + "@hapi/topo" "^5.0.0" + "@hapi/validate" "^1.1.1" + +"@hapi/heavy@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-7.0.1.tgz#73315ae33b6e7682a0906b7a11e8ca70e3045874" + integrity sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" -"@hapi/h2o2@^8.3.2": - version "8.3.2" - resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-8.3.2.tgz#008a8f9ec3d9bba29077691aa9ec0ace93d4de80" - integrity sha512-2WkZq+QAkvYHWGqnUuG0stcVeGyv9T7bopBYnCJSUEuvBZlUf2BTX2JCVSKxsnTLOxCYwoC/aI4Rr0ZSRd2oVg== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - "@hapi/wreck" "15.x.x" - -"@hapi/hapi@^18.4.1": - version "18.4.1" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-18.4.1.tgz#023fbc131074b1cb2cd7f6766d65f4b0e92df788" - integrity sha512-9HjVGa0Z4Qv9jk9AVoUdJMQLA+KuZ+liKWyEEkVBx3e3H1F0JM6aGbPkY9jRfwsITBWGBU2iXazn65SFKSi/tg== - dependencies: - "@hapi/accept" "^3.2.4" - "@hapi/ammo" "^3.1.2" - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/call" "^5.1.3" - "@hapi/catbox" "10.x.x" - "@hapi/catbox-memory" "4.x.x" - "@hapi/heavy" "6.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "15.x.x" - "@hapi/mimos" "4.x.x" - "@hapi/podium" "3.x.x" - "@hapi/shot" "4.x.x" - "@hapi/somever" "2.x.x" - "@hapi/statehood" "6.x.x" - "@hapi/subtext" "^6.1.3" - "@hapi/teamwork" "3.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/heavy@6.x.x": - version "6.2.2" - resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-6.2.2.tgz#d42a282c62d5bb6332e497d8ce9ba52f1609f3e6" - integrity sha512-PY1dCCO6dsze7RlafIRhTaGeyTgVe49A/lSkxbhKGjQ7x46o/OFf7hLiRqTCDh3atcEKf6362EaB3+kTUbCsVA== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - -"@hapi/hoek@8.x.x", "@hapi/hoek@^8.2.4", "@hapi/hoek@^8.3.0", "@hapi/hoek@^8.3.1": - version "8.5.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" - integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== - -"@hapi/iron@5.x.x": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-5.1.4.tgz#7406f36847f798f52b92d1d97f855e27973832b7" - integrity sha512-+ElC+OCiwWLjlJBmm8ZEWjlfzTMQTdgPnU/TsoU5QsktspIWmWi9IU4kU83nH+X/SSya8TP8h8P11Wr5L7dkQQ== - dependencies: - "@hapi/b64" "4.x.x" - "@hapi/boom" "7.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/cryptiles" "4.x.x" - "@hapi/hoek" "8.x.x" - -"@hapi/joi@15.x.x": - version "15.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" - integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== - dependencies: - "@hapi/address" "2.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/joi@16.x.x": - version "16.1.8" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.8.tgz#84c1f126269489871ad4e2decc786e0adef06839" - integrity sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg== - dependencies: - "@hapi/address" "^2.1.2" - "@hapi/formula" "^1.2.0" - "@hapi/hoek" "^8.2.4" - "@hapi/pinpoint" "^1.0.2" - "@hapi/topo" "^3.1.3" - -"@hapi/mimos@4.x.x": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-4.1.1.tgz#4dab8ed5c64df0603c204c725963a5faa4687e8a" - integrity sha512-CXoi/zfcTWfKYX756eEea8rXJRIb9sR4d7VwyAH9d3BkDyNgAesZxvqIdm55npQc6S9mU3FExinMAQVlIkz0eA== +"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" + integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== + +"@hapi/iron@6.x.x": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" + integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/mimos@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-6.0.0.tgz#daa523d9c07222c7e8860cb7c9c5501fd6506484" + integrity sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg== + dependencies: + "@hapi/hoek" "9.x.x" mime-db "1.x.x" -"@hapi/nigel@3.x.x": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-3.1.1.tgz#84794021c9ee6e48e854fea9fb76e9f7e78c99ad" - integrity sha512-R9YWx4S8yu0gcCBrMUDCiEFm1SQT895dMlYoeNBp8I6YhF1BFF1iYPueKA2Kkp9BvyHdjmvrxCOns7GMmpl+Fw== +"@hapi/nigel@4.x.x": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-4.0.2.tgz#8f84ef4bca4fb03b2376463578f253b0b8e863c4" + integrity sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw== dependencies: - "@hapi/hoek" "8.x.x" - "@hapi/vise" "3.x.x" + "@hapi/hoek" "^9.0.4" + "@hapi/vise" "^4.0.0" -"@hapi/pez@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-4.1.2.tgz#14984d0c31fed348f10c962968a21d9761f55503" - integrity sha512-8zSdJ8cZrJLFldTgwjU9Fb1JebID+aBCrCsycgqKYe0OZtM2r3Yv3aAwW5z97VsZWCROC1Vx6Mdn4rujh5Ktcg== +"@hapi/pez@^5.0.1": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-5.0.3.tgz#b75446e6fef8cbb16816573ab7da1b0522e7a2a1" + integrity sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA== dependencies: - "@hapi/b64" "4.x.x" - "@hapi/boom" "7.x.x" - "@hapi/content" "^4.1.1" - "@hapi/hoek" "8.x.x" - "@hapi/nigel" "3.x.x" + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/content" "^5.0.2" + "@hapi/hoek" "9.x.x" + "@hapi/nigel" "4.x.x" -"@hapi/pinpoint@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-1.0.2.tgz#025b7a36dbbf4d35bf1acd071c26b20ef41e0d13" - integrity sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ== +"@hapi/podium@4.x.x", "@hapi/podium@^4.1.1": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" + integrity sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g== + dependencies: + "@hapi/hoek" "9.x.x" + "@hapi/teamwork" "5.x.x" + "@hapi/validate" "1.x.x" -"@hapi/podium@3.x.x": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-3.4.3.tgz#d28935870ae1372e2f983a7161e710c968a60de1" - integrity sha512-QJlnYLEYZWlKQ9fSOtuUcpANyoVGwT68GA9P0iQQCAetBK0fI+nbRBt58+aMixoifczWZUthuGkNjqKxgPh/CQ== +"@hapi/shot@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-5.0.5.tgz#a25c23d18973bec93c7969c51bf9579632a5bebd" + integrity sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A== dependencies: - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" -"@hapi/shot@4.x.x": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-4.1.2.tgz#69f999956041fe468701a89a413175a521dabed5" - integrity sha512-6LeHLjvsq/bQ0R+fhEyr7mqExRGguNTrxFZf5DyKe3CK6pNabiGgYO4JVFaRrLZ3JyuhkS0fo8iiRE2Ql2oA/A== +"@hapi/somever@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-3.0.1.tgz#9961cd5bdbeb5bb1edc0b2acdd0bb424066aadcc" + integrity sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w== dependencies: - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/somever@2.x.x": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-2.1.1.tgz#142bddf7cc4d829f678ed4e60618630a9a7ae845" - integrity sha512-cic5Sto4KGd9B0oQSdKTokju+rYhCbdpzbMb0EBnrH5Oc1z048hY8PaZ1lx2vBD7I/XIfTQVQetBH57fU51XRA== - dependencies: - "@hapi/bounce" "1.x.x" - "@hapi/hoek" "8.x.x" - -"@hapi/statehood@6.x.x": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-6.1.2.tgz#6dda508b5da99a28a3ed295c3cac795cf6c12a02" - integrity sha512-pYXw1x6npz/UfmtcpUhuMvdK5kuOGTKcJNfLqdNptzietK2UZH5RzNJSlv5bDHeSmordFM3kGItcuQWX2lj2nQ== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/cryptiles" "4.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/iron" "5.x.x" - "@hapi/joi" "16.x.x" - -"@hapi/subtext@^6.1.3": - version "6.1.3" - resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-6.1.3.tgz#bbd07771ae2a4e73ac360c93ed74ac641718b9c6" - integrity sha512-qWN6NbiHNzohVcJMeAlpku/vzbyH4zIpnnMPMPioQMwIxbPFKeNViDCNI6fVBbMPBiw/xB4FjqiJkRG5P9eWWg== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/content" "^4.1.1" - "@hapi/file" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/pez" "^4.1.2" - "@hapi/wreck" "15.x.x" - -"@hapi/teamwork@3.x.x": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-3.3.1.tgz#b52d0ec48682dc793926bd432e22ceb19c915d3f" - integrity sha512-61tiqWCYvMKP7fCTXy0M4VE6uNIwA0qvgFoiDubgfj7uqJ0fdHJFQNnVPGrxhLWlwz0uBPWrQlBH7r8y9vFITQ== - -"@hapi/topo@3.x.x", "@hapi/topo@^3.1.3": - version "3.1.6" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" - integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== - dependencies: - "@hapi/hoek" "^8.3.0" - -"@hapi/vise@3.x.x": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-3.1.1.tgz#dfc88f2ac90682f48bdc1b3f9b8f1eab4eabe0c8" - integrity sha512-OXarbiCSadvtg+bSdVPqu31Z1JoBL+FwNYz3cYoBKQ5xq1/Cr4A3IkGpAZbAuxU5y4NL5pZFZG3d2a3ZGm/dOQ== +"@hapi/statehood@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-7.0.3.tgz#655166f3768344ed3c3b50375a303cdeca8040d9" + integrity sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/iron" "6.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/subtext@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-7.0.3.tgz#f7440fc7c966858e1f39681e99eb6171c71e7abd" + integrity sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/content" "^5.0.2" + "@hapi/file" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/pez" "^5.0.1" + "@hapi/wreck" "17.x.x" + +"@hapi/teamwork@5.x.x", "@hapi/teamwork@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124" + integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "^9.0.0" -"@hapi/wreck@15.x.x": - version "15.1.0" - resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-15.1.0.tgz#7917cd25950ce9b023f7fd2bea6e2ef72c71e59d" - integrity sha512-tQczYRTTeYBmvhsek/D49En/5khcShaBEmzrAaDjMrFXKJRuF8xA8+tlq1ETLBFwGd6Do6g2OC74rt11kzawzg== +"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" + integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/vise@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-4.0.0.tgz#c6a94fe121b94a53bf99e7489f7fcc74c104db02" + integrity sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/wreck@17.x.x": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" + integrity sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/hoek" "9.x.x" + +"@iarna/toml@^2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" + integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== "@kwsites/file-exists@^1.1.1": version "1.1.1" @@ -1175,6 +1153,11 @@ arr-swap@^1.0.1: dependencies: is-number "^3.0.0" +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -1197,6 +1180,16 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +async-array-reduce@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/async-array-reduce/-/async-array-reduce-0.2.1.tgz#c8be010a2b5cd00dea96c81116034693dfdd82d1" + integrity sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE= + +async-each@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + async@^2.6.1, async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -1224,21 +1217,6 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sdk@^2.624.0: - version "2.774.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.774.0.tgz#1d9512ae42f0cfb9b98d0d6e0d7df7634cf4e680" - integrity sha512-3a/fM1E3nCPwT4AVbysOWCMmsu/TOdJDD3urjywWE/qO1JShxRwLSdRLD1xRkacR9JcnydfkmdU0qk+VsM3nqg== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - aws-sdk@^2.756.0: version "2.787.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.787.0.tgz#4d8966d11c7dbe770de26632e552c97b2d91e340" @@ -1254,7 +1232,7 @@ aws-sdk@^2.756.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.979.0: +aws-sdk@^2.834.0, aws-sdk@^2.979.0: version "2.980.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== @@ -1363,26 +1341,12 @@ blob@0.0.5: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== -bluebird@^3.0.6, bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: +bluebird@^3.4.1, bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -boxen@^5.0.1: +boxen@^5.0.0, boxen@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== @@ -1514,7 +1478,15 @@ cachedir@^2.3.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== -camelcase@^5.0.0, camelcase@^5.3.1: +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -1644,7 +1616,7 @@ ci-info@^3.1.1, ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== -cli-boxes@^2.2.0, cli-boxes@^2.2.1: +cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== @@ -1743,6 +1715,16 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= + +clone@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1925,6 +1907,26 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/copy/-/copy-0.3.2.tgz#870b871d02a599b3c6ef27bc5b3d4c4102261909" + integrity sha512-drDFuUZctIuvSuvL9dOF/v5GxrwB1Q8eMIRlYONC0lSMEq+L2xabXP3jme8cQFdDO8cgP8JsuYhQg7JtTwezmg== + dependencies: + async-each "^1.0.0" + bluebird "^3.4.1" + extend-shallow "^2.0.1" + file-contents "^0.3.1" + glob-parent "^2.0.0" + graceful-fs "^4.1.4" + has-glob "^0.1.1" + is-absolute "^0.2.5" + lazy-cache "^2.0.1" + log-ok "^0.1.1" + matched "^0.4.1" + mkdirp "^0.5.1" + resolve-dir "^0.1.0" + to-file "^0.2.0" + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1969,10 +1971,10 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" -cron-parser@^2.7.3: - version "2.16.3" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.16.3.tgz#acb8e405eed1733aac542fdf604cb7c1daf0204a" - integrity sha512-XNJBD1QLFeAMUkZtZQuncAAOgJFWNhBdIbwgD22hZxrcWOImBFMKgPC66GzaXpyoJs7UvYLLgPH/8BRk/7gbZg== +cron-parser@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" + integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== dependencies: is-nan "^1.3.0" moment-timezone "^0.5.31" @@ -1988,7 +1990,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: +cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2355,40 +2357,28 @@ error-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= -es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: - version "1.17.7" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== +es-abstract@^1.18.0-next.2: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.11.0" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" es-to-primitive@^1.2.1: version "1.2.1" @@ -2505,19 +2495,19 @@ events@1.1.1: resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -execa@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" - integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" strip-final-newline "^2.0.0" exit-on-epipe@~1.0.1: @@ -2530,6 +2520,13 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +expand-tilde@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" + integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk= + dependencies: + os-homedir "^1.0.1" + ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -2559,7 +2556,7 @@ ext@^1.1.2: dependencies: type "^2.0.0" -extend-shallow@^2.0.1: +extend-shallow@^2.0.0, extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= @@ -2666,6 +2663,56 @@ figures@^3.0.0, figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" +file-contents@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/file-contents/-/file-contents-0.2.4.tgz#0506f7b8eff62afa45ae45da4df9e9d47df453cb" + integrity sha1-BQb3uO/2KvpFrkXaTfnp1H30U8s= + dependencies: + extend-shallow "^2.0.0" + file-stat "^0.1.0" + graceful-fs "^4.1.2" + is-buffer "^1.1.0" + is-utf8 "^0.2.0" + lazy-cache "^0.2.3" + through2 "^2.0.0" + +file-contents@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/file-contents/-/file-contents-0.3.2.tgz#a0939fed1b8cda1580266fc6b753a232fb46de53" + integrity sha1-oJOf7RuM2hWAJm/Gt1OiMvtG3lM= + dependencies: + define-property "^0.2.5" + extend-shallow "^2.0.1" + file-stat "^0.2.3" + fs-exists-sync "^0.1.0" + graceful-fs "^4.1.4" + is-buffer "^1.1.3" + isobject "^2.1.0" + lazy-cache "^2.0.1" + strip-bom-buffer "^0.1.1" + strip-bom-string "^0.1.2" + through2 "^2.0.1" + vinyl "^1.1.1" + +file-stat@^0.1.0: + version "0.1.3" + resolved "https://registry.yarnpkg.com/file-stat/-/file-stat-0.1.3.tgz#d0f1961d7d10732928120a6e6955471c2a5b5411" + integrity sha1-0PGWHX0QcykoEgpuaVVHHCpbVBE= + dependencies: + graceful-fs "^4.1.2" + lazy-cache "^0.2.3" + through2 "^2.0.0" + +file-stat@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/file-stat/-/file-stat-0.2.3.tgz#469a7e927d6930079624cdb38109405456cb06a9" + integrity sha1-Rpp+kn1pMAeWJM2zgQlAVFbLBqk= + dependencies: + fs-exists-sync "^0.1.0" + graceful-fs "^4.1.4" + lazy-cache "^2.0.1" + through2 "^2.0.1" + file-type@^16.5.3: version "16.5.3" resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06" @@ -2747,6 +2794,15 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +folder-hash@^3.3.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/folder-hash/-/folder-hash-3.3.3.tgz#883c8359d54f91b3f02c1a646c00c30e5831365b" + integrity sha512-SDgHBgV+RCjrYs8aUwCb9rTgbTVuSdzvFmLaChsLre1yf+D64khCW++VYciaByZ8Rm0uKF8R/XEpXuTRSGUM1A== + dependencies: + debug "^4.1.1" + graceful-fs "~4.2.0" + minimatch "~3.0.4" + follow-redirects@^1.10.0: version "1.13.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" @@ -2809,7 +2865,12 @@ fs-copy-file@^2.1.2: dependencies: "@cloudcmd/copy-file" "^1.1.0" -fs-extra@^7.0.0, fs-extra@^7.0.1: +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= + +fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== @@ -2827,7 +2888,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== @@ -2919,6 +2980,15 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" @@ -2939,14 +3009,14 @@ get-stream@^4.1.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" -get-stream@^6.0.1: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -2963,7 +3033,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-all@^3.1.0: +glob-all@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.2.1.tgz#082ca81afd2247cbd3ed2149bb2630f4dc877d95" integrity sha512-x877rVkzB3ipid577QOp+eQCR6M5ZyiwrtaYgrX/z3EThaSPFtLDwBXFHc3sH1cG0R0vFYI5SRYeWMMSEyXkUw== @@ -2971,6 +3041,13 @@ glob-all@^3.1.0: glob "^7.1.2" yargs "^15.3.1" +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= + dependencies: + is-glob "^2.0.0" + glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -2990,12 +3067,42 @@ glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== +glob@^7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== dependencies: - ini "^1.3.5" + ini "2.0.0" + +global-modules@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" + integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0= + dependencies: + global-prefix "^0.1.4" + is-windows "^0.2.0" + +global-prefix@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" + integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948= + dependencies: + homedir-polyfill "^1.0.0" + ini "^1.3.4" + is-windows "^0.2.0" + which "^1.2.12" globby@^11.0.4: version "11.0.4" @@ -3048,7 +3155,7 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.8: +graceful-fs@^4.1.4, graceful-fs@^4.2.8, graceful-fs@~4.2.0: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -3073,6 +3180,11 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -3095,11 +3207,30 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-glob@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-0.1.1.tgz#a261c4c2a6c667e0c77b700a7f297c39ef3aa589" + integrity sha1-omHEwqbGZ+DHe3AKfyl8Oe86pYk= + dependencies: + is-glob "^2.0.1" + has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -3124,6 +3255,13 @@ hasbin@^1.2.3: dependencies: async "~1.5" +homedir-polyfill@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -3154,10 +3292,10 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@^0.4.24, iconv-lite@~0.4.11: version "0.4.24" @@ -3219,7 +3357,7 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.5, ini@^1.3.7, ini@~1.3.0: +ini@2.0.0, ini@^1.3.4, ini@^1.3.7, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== @@ -3278,6 +3416,23 @@ install@^0.13.0: resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776" integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA== +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-absolute@^0.2.5: + version "0.2.6" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" + integrity sha1-IN5p89uULvLYe5wto28XIjWxtes= + dependencies: + is-relative "^0.2.1" + is-windows "^0.2.0" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3297,6 +3452,13 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3304,16 +3466,29 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.5: +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.0, is-buffer@^1.1.3, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.2: +is-callable@^1.1.4: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -3373,6 +3548,11 @@ is-extendable@^0.1.0, is-extendable@^0.1.1: resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -3395,6 +3575,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= + dependencies: + is-extglob "^1.0.0" + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -3402,13 +3589,13 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" + global-dirs "^3.0.0" + is-path-inside "^3.0.2" is-nan@^1.3.0: version "1.3.0" @@ -3422,15 +3609,22 @@ is-natural-number@^4.0.1: resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" is-number@^3.0.0: version "3.0.0" @@ -3454,10 +3648,10 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@^1.0.0: version "1.1.0" @@ -3476,12 +3670,20 @@ is-promise@^2.1, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== +is-regex@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: - has-symbols "^1.0.1" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-relative@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" + integrity sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU= + dependencies: + is-unc-path "^0.1.1" is-stream@^1.1.0: version "1.1.0" @@ -3493,6 +3695,13 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -3500,21 +3709,45 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" +is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unc-path@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" + integrity sha1-arBTpyVzwQJQ/0FqOBTDUXivObk= + dependencies: + unc-path-regex "^0.1.0" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-valid-glob@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" + integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4= + +is-windows@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" + integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw= + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -3537,21 +3770,28 @@ is_js@^0.9.0: resolved "https://registry.yarnpkg.com/is_js/-/is_js-0.9.0.tgz#0ab94540502ba7afa24c856aa985561669e9c52d" integrity sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0= +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isarray@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= -isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isobject@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -3670,15 +3910,15 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonpath-plus@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-3.0.0.tgz#194ab4792a5e9b4ed27bf442188c8eb7e697a04b" - integrity sha512-WQwgWEBgn+SJU1tlDa/GiY5/ngRpa9yrSj8n4BYPHcwoxTDaMEaYCHMOn42hIHHDd3CrUoRr3+HpsK0hCKoxzA== +jsonpath-plus@^5.0.2: + version "5.1.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz#2fc4b2e461950626c98525425a3a3518b85af6c3" + integrity sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ== -jsonschema@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.3.0.tgz#def86ca039b4f4254e76528d173462bc5da36162" - integrity sha512-qg48ckmeeQNPyPAUVIb4Qgmg/U2Kgg5SuEyMs8Z72cnxsw5Ra088U/Foi6sMp/cs7sZ+LNrmvX0Ww+ohE2By0g== +jsonschema@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" + integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== jsonwebtoken@^8.5.1: version "8.5.1" @@ -3706,7 +3946,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.1.0, jszip@^3.2.2, jszip@^3.7.1: +jszip@^3.5.0, jszip@^3.6.0, jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -3807,13 +4047,18 @@ kuler@1.0.x: dependencies: colornames "^1.1.1" -latest-version@^5.0.0: +latest-version@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== dependencies: package-json "^6.3.0" +lazy-cache@^0.2.3: + version "0.2.7" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" + integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U= + lazy-cache@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" @@ -3907,7 +4152,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash.uniqby@^4.0.0: +lodash.uniqby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= @@ -4005,10 +4250,10 @@ lru-queue@0.1, lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" -luxon@^1.22.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.25.0.tgz#d86219e90bc0102c0eb299d65b2f5e95efe1fe72" - integrity sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ== +luxon@^1.25.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== make-dir@^1.0.0: version "1.3.0" @@ -4038,10 +4283,20 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -md5-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-4.0.0.tgz#f3f7ba1e2dd1144d5bf1de698d0e5f44a4409584" - integrity sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg== +matched@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/matched/-/matched-0.4.4.tgz#56d7b7eb18033f0cf9bc52eb2090fac7dc1e89fa" + integrity sha1-Vte36xgDPwz5vFLrIJD6x9weifo= + dependencies: + arr-union "^3.1.0" + async-array-reduce "^0.2.0" + extend-shallow "^2.0.1" + fs-exists-sync "^0.1.0" + glob "^7.0.5" + has-glob "^0.1.1" + is-valid-glob "^0.3.0" + lazy-cache "^2.0.1" + resolve-dir "^0.1.0" media-typer@0.3.0: version "0.3.0" @@ -4174,7 +4429,7 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -4338,14 +4593,14 @@ node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-schedule@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.2.tgz#d774b383e2a6f6ade59eecc62254aea07cd758cb" - integrity sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw== +node-schedule@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.3.tgz#f8e01c5fb9597f09ecf9c4c25d6938e5e7a06f48" + integrity sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw== dependencies: - cron-parser "^2.7.3" + cron-parser "^2.18.0" long-timeout "0.1.1" - sorted-array-functions "^1.0.0" + sorted-array-functions "^1.3.0" node.extend@^2.0.2: version "2.0.2" @@ -4370,7 +4625,7 @@ normalize-url@^4.1.0, normalize-url@^4.5.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== -npm-run-path@^4.0.0: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -4416,10 +4671,10 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -4433,24 +4688,24 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" -object.fromentries@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" - integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== +object.fromentries@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" has "^1.0.3" once@^1.3.0, once@^1.3.1, once@^1.4.0: @@ -4472,7 +4727,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -4551,7 +4806,7 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-memoize@^4.0.0: +p-memoize@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/p-memoize/-/p-memoize-4.0.1.tgz#6f4231857fec10de2504611fe820c808fa8c5f8b" integrity sha512-km0sP12uE0dOZ5qP+s7kGVf07QngxyG0gS8sYFvFWhqlgzOsSy+m71aUejf/0akxj5W7gE//2G74qTv6b4iMog== @@ -4559,7 +4814,7 @@ p-memoize@^4.0.0: mem "^6.0.1" mimic-fn "^3.0.0" -p-queue@^6.3.0: +p-queue@^6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== @@ -4567,13 +4822,13 @@ p-queue@^6.3.0: eventemitter3 "^4.0.4" p-timeout "^3.2.0" -p-retry@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.2.0.tgz#ea9066c6b44f23cab4cd42f6147cdbbc6604da5d" - integrity sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA== +p-retry@^4.3.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== dependencies: "@types/retry" "^0.12.0" - retry "^0.12.0" + retry "^0.13.1" p-timeout@^3.1.0, p-timeout@^3.2.0: version "3.2.0" @@ -4602,6 +4857,11 @@ pako@~1.0.2: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + parseqs@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" @@ -4714,7 +4974,7 @@ pointer-symbol@^1.0.0: resolved "https://registry.yarnpkg.com/pointer-symbol/-/pointer-symbol-1.0.0.tgz#60f9110204ea7a929b62644a21315543cbb3d447" integrity sha1-YPkRAgTqepKbYmRKITFVQ8uz1Ec= -portfinder@^1.0.25: +portfinder@^1.0.28: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== @@ -4915,10 +5175,10 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== dependencies: escape-goat "^2.0.0" @@ -5080,6 +5340,11 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= + replaceall@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e" @@ -5142,6 +5407,14 @@ resolve-alpn@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== +resolve-dir@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" + integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4= + dependencies: + expand-tilde "^1.2.2" + global-modules "^0.2.3" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -5177,20 +5450,20 @@ retry@^0.10.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" @@ -5277,10 +5550,12 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.1.3: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.3.2, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" semver@^7.3.4: version "7.3.4" @@ -5289,13 +5564,6 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - serverless-domain-manager@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/serverless-domain-manager/-/serverless-domain-manager-5.1.0.tgz#44c975b4adeac6b32507c92313ee001735d56c55" @@ -5304,7 +5572,7 @@ serverless-domain-manager@^5.1.0: aws-sdk "^2.756.0" chalk "^4.1.0" -serverless-finch@^2.3.2: +serverless-finch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/serverless-finch/-/serverless-finch-2.6.0.tgz#c74e7492dbfae52aa6383d4a21bac9138bcd9383" integrity sha512-G5umIBoNyo3MKCtdtbbkkb/7Z84qNstbQnkdscG/VhukYUib+7BiWidAMI+WAFq+JEUf3PW7c3bvt/uFEiMnnA== @@ -5314,97 +5582,100 @@ serverless-finch@^2.3.2: minimatch "^3.0.4" prompt-confirm "^1.2.0" -serverless-layers@^1.4.3: - version "1.5.0" - resolved "https://registry.yarnpkg.com/serverless-layers/-/serverless-layers-1.5.0.tgz#f1596c7f65f9ef76d061e0d0f8b908c32b50c94e" - integrity sha512-/VnGeEVoaE8w23lgMRw5W3CMlf/7tLq35px+Ab0QyvCK+WnNKX5VtHUzDyIBA/WrFXw7XXWeNKnqLezsuoiLyw== +serverless-layers@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/serverless-layers/-/serverless-layers-2.5.1.tgz#2343920c3618a10643d4646f3d825c56b6e31047" + integrity sha512-gG1ph4J4SamX185sC/MZAVBhae74vJXU8PocnKHkIogGKvw45dhsflh49tU27nnS5PBCDuA5OeHbRfDqTr++4Q== dependencies: "@babel/runtime" "^7.3.1" archiver "^3.0.0" bluebird "^3.5.3" + chalk "^3.0.0" + copy "^0.3.2" + folder-hash "^3.3.0" fs-copy-file "^2.1.2" + fs-extra "^8.1.0" + glob "^7.1.6" mkdirp "^0.5.1" + semver "^7.3.2" + slugify "^1.4.0" -serverless-offline@^6.1.5: - version "6.8.0" - resolved "https://registry.yarnpkg.com/serverless-offline/-/serverless-offline-6.8.0.tgz#65d2192e41ef87ebcbc1093108db6a25059d6ab5" - integrity sha512-DBDMcU58Bl+zZGSTAZ96Ed57k11oh0fQwgSoH2iVJpO6xFV9dTIttBYzTCwnfAgG0kB6NZK99Q/69b4brChTnQ== - dependencies: - "@hapi/boom" "^7.4.11" - "@hapi/h2o2" "^8.3.2" - "@hapi/hapi" "^18.4.1" - aws-sdk "^2.624.0" - boxen "^4.2.0" - chalk "^3.0.0" +serverless-offline@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/serverless-offline/-/serverless-offline-8.0.0.tgz#66ffb69d8f631b096957b9cc657cc5c66600f1b8" + integrity sha512-ov5PbO8gbT+KtHK0rA6NiYnNdzeVaCt8FLn9sJGN2nPvuyTbemJ5i0eOeOOX8Wn9LsHIIyYuxGa+tQM353M8wg== + dependencies: + "@hapi/boom" "^9.1.2" + "@hapi/h2o2" "^9.0.2" + "@hapi/hapi" "^20.1.3" + aws-sdk "^2.834.0" + boxen "^5.0.0" + chalk "^4.1.0" cuid "^2.1.8" - execa "^4.0.0" + execa "^5.0.0" extend "^3.0.2" - fs-extra "^8.1.0" + fs-extra "^9.1.0" java-invoke-local "0.0.6" js-string-escape "^1.0.1" - jsonpath-plus "^3.0.0" - jsonschema "^1.2.6" + jsonpath-plus "^5.0.2" + jsonschema "^1.4.0" jsonwebtoken "^8.5.1" - jszip "^3.2.2" - luxon "^1.22.0" - node-fetch "^2.6.0" - node-schedule "^1.3.2" - object.fromentries "^2.0.2" - p-memoize "^4.0.0" - p-queue "^6.3.0" - p-retry "^4.2.0" + jszip "^3.5.0" + luxon "^1.25.0" + node-fetch "^2.6.1" + node-schedule "^1.3.3" + object.fromentries "^2.0.3" + p-memoize "^4.0.1" + p-queue "^6.6.2" + p-retry "^4.3.0" please-upgrade-node "^3.2.0" - portfinder "^1.0.25" - semver "^7.1.3" - update-notifier "^4.1.0" - velocityjs "^2.0.0" - ws "^7.2.1" + portfinder "^1.0.28" + semver "^7.3.4" + update-notifier "^5.0.1" + velocityjs "^2.0.3" + ws "^7.4.2" serverless-plugin-tracing@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/serverless-plugin-tracing/-/serverless-plugin-tracing-2.0.0.tgz#df6b8b3166ac9bb70a37c7fc875014b2369158f6" integrity sha1-32uLMWasm7cKN8f8h1AUsjaRWPY= -serverless-prune-plugin@^1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/serverless-prune-plugin/-/serverless-prune-plugin-1.4.3.tgz#556d76a86e37bf57d4ccd8449a7d98b6496bd5ed" - integrity sha512-gsZF3oLs5rFdp6ynjiWf5cuXZ4DZrAhxRd5Zf2gfH/43kPqtZMZzUqcGYbHh1OXbOzogdn8fEg5d4Q3xxWwRBA== +serverless-prune-plugin@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/serverless-prune-plugin/-/serverless-prune-plugin-1.5.1.tgz#a628551151228eeee8da2f454d0b062d9c1dda40" + integrity sha512-lCxHNQXmGL0GEe7gBGOx1MdafYXAi9RkiuKPE4DDSSdrdbVnn0KLcbriKWGC2MdOpn/vs358MaeBuVSefuzMzA== dependencies: bluebird "^3.4.7" -serverless-pseudo-parameters@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/serverless-pseudo-parameters/-/serverless-pseudo-parameters-2.5.0.tgz#f30bf34db166e4b8b22144a8e65aca71b90dd1e6" - integrity sha512-A/O49AR8LL6jlnPSmnOTYgL1KqVgskeRla4sVDeS/r5dHFJlwOU5MgFilc7aaQP8NWAwRJANaIS9oiSE3I+VUA== - -serverless-python-requirements@^4.2.5: - version "4.3.0" - resolved "https://registry.yarnpkg.com/serverless-python-requirements/-/serverless-python-requirements-4.3.0.tgz#ba2b78e01213428ecd62a6368af82beec4de17d1" - integrity sha512-VyXdEKNxUWoQDbssWZeR5YMaTDf1U4CO3yJH2953Y2Rt8zD6hG+vpTkVR490/Ws1PQsBopWuFfgDcLyvAppaRg== +serverless-python-requirements@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/serverless-python-requirements/-/serverless-python-requirements-5.1.1.tgz#62cfed95212bd535ca412bb983d73ad86d64f28f" + integrity sha512-frqZhQcqf3kLMpkS1U8VUqiRnbDG4C81eQndlZ150gSFkAqCc521LemedtWEWGrBQQoWSTo4LivugLiQU7s/Sg== dependencies: + "@iarna/toml" "^2.2.5" appdirectory "^0.1.0" - bluebird "^3.0.6" - fs-extra "^7.0.0" - glob-all "^3.1.0" - is-wsl "^1.1.0" - jszip "^3.1.0" + bluebird "^3.7.2" + fs-extra "^9.1.0" + glob-all "^3.2.1" + is-wsl "^2.2.0" + jszip "^3.6.0" lodash.get "^4.4.2" lodash.set "^4.3.2" - lodash.uniqby "^4.0.0" + lodash.uniqby "^4.7.0" lodash.values "^4.3.0" - md5-file "^4.0.0" - rimraf "^2.6.2" - shell-quote "^1.6.1" + rimraf "^3.0.2" + sha256-file "1.0.0" + shell-quote "^1.7.2" -serverless-wsgi@^1.5.2: - version "1.7.5" - resolved "https://registry.yarnpkg.com/serverless-wsgi/-/serverless-wsgi-1.7.5.tgz#7598437bd574e1999c4adee92e877ab99cb97637" - integrity sha512-6a+rHqAemAJuZiH9ecqwoigiVZKuATIxJft0CUWAxXwietzg0BkSowcUKcfah+fbjgSQks7LDl8B+xe8Kb7waA== +serverless-wsgi@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/serverless-wsgi/-/serverless-wsgi-2.0.1.tgz#3d2858b414e9819d15ef6010637b67b37a227f10" + integrity sha512-nEBmY3qWrH7XLQTLKJR8SUNLYwLCWJP0IK7v7NLYM1fAtui8Mm0mtWvN/r4bcLlvo70DY0FdXR3JhJ4XLLM3aw== dependencies: bluebird "^3.7.2" - fs-extra "^9.0.0" + fs-extra "^9.1.0" hasbin "^1.2.3" - lodash "^4.17.15" + lodash "^4.17.21" serverless@^2.57.0: version "2.57.0" @@ -5491,6 +5762,11 @@ set-value@^3.0.0: dependencies: is-plain-object "^2.0.4" +sha256-file@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/sha256-file/-/sha256-file-1.0.0.tgz#02cade5e658da3fbc167c3270bdcdfd5409f1b65" + integrity sha512-nqf+g0veqgQAkDx0U2y2Tn2KWyADuuludZTw9A7J3D+61rKlIIl9V5TS4mfnwKuXZOH9B7fQyjYJ9pKRHIsAyg== + shallow-clone@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" @@ -5531,7 +5807,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1: +shell-quote@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== @@ -5543,6 +5819,15 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -5583,6 +5868,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slugify@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.0.tgz#6bdf8ed01dabfdc46425b67e3320b698832ff893" + integrity sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang== + snappy@^6.0.1: version "6.3.5" resolved "https://registry.yarnpkg.com/snappy/-/snappy-6.3.5.tgz#c14b8dea8e9bc2687875b5e491d15dd900e6023c" @@ -5632,7 +5922,7 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sorted-array-functions@^1.0.0: +sorted-array-functions@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== @@ -5738,21 +6028,21 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" string_decoder@^1.1.1: version "1.3.0" @@ -5796,6 +6086,19 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-bom-buffer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/strip-bom-buffer/-/strip-bom-buffer-0.1.1.tgz#ca3ddc4919c13f9fddf30b1dff100a9835248b4d" + integrity sha1-yj3cSRnBP5/d8wsd/xAKmDUki00= + dependencies: + is-buffer "^1.1.0" + is-utf8 "^0.2.0" + +strip-bom-string@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-0.1.2.tgz#9c6e720a313ba9836589518405ccfb88a5f41b9c" + integrity sha1-nG5yCjE7qYNliVGEBcz7iKX0G5w= + strip-color@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/strip-color/-/strip-color-0.1.0.tgz#106f65d3d3e6a2d9401cac0eb0ce8b8a702b4f7b" @@ -5944,11 +6247,6 @@ tencent-serverless-http@^1.3.1: dependencies: type-is "^1.6.16" -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - terminal-paginator@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/terminal-paginator/-/terminal-paginator-2.0.2.tgz#967e66056f28fe8f55ba7c1eebfb7c3ef371c1d3" @@ -5968,6 +6266,14 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +through2@^2.0.0, through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -6003,6 +6309,20 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== +to-file@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/to-file/-/to-file-0.2.0.tgz#236c6c088065e570defbd15cf4b4e565be46ea93" + integrity sha1-I2xsCIBl5XDe+9Fc9LTlZb5G6pM= + dependencies: + define-property "^0.2.5" + extend-shallow "^2.0.1" + file-contents "^0.2.4" + glob-parent "^2.0.0" + is-valid-glob "^0.3.0" + isobject "^2.1.0" + lazy-cache "^2.0.1" + vinyl "^1.1.1" + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -6094,11 +6414,6 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - type-is@^1.6.16: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -6129,6 +6444,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6137,6 +6462,11 @@ unbzip2-stream@^1.0.9: buffer "^5.2.1" through "^2.3.8" +unc-path-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -6169,22 +6499,23 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-notifier@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" - integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== +update-notifier@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== dependencies: - boxen "^4.2.0" - chalk "^3.0.0" + boxen "^5.0.0" + chalk "^4.1.0" configstore "^5.0.1" has-yarn "^2.1.0" import-lazy "^2.1.0" is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" semver-diff "^3.1.1" xdg-basedir "^4.0.0" @@ -6237,10 +6568,10 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -velocityjs@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/velocityjs/-/velocityjs-2.0.2.tgz#834f488e0ee4a9f63ff4201bf1e524ac51bdcfbf" - integrity sha512-TUQ7/lOEFEho7zSXlh6M/+lAOIRU0g7nMDUlGn1Jt40Y0JLOnIVM4RTuB4KpkN6eL7BPl+ygc2zi5XJIi874zQ== +velocityjs@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/velocityjs/-/velocityjs-2.0.3.tgz#cc772f687061997127b7d8a827dbef3af8a0bbe6" + integrity sha512-sUkygY7HwvbKZvS3naiI7t2o4RTqui6efSwTXLb03igdvPKm3SwCpnqA2kU4/jLD2f0eHB9xPoiza9XAkpuU+g== verror@1.10.0: version "1.10.0" @@ -6251,11 +6582,31 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vinyl@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" + integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + warning-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" integrity sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -6266,7 +6617,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.2.9: +which@^1.2.12, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -6372,7 +6723,7 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.2.1, ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: +ws@^7.3.1, ws@^7.4.2, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== @@ -6400,7 +6751,7 @@ xmlhttprequest-ssl@^1.6.2, xmlhttprequest-ssl@~1.5.4: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== -xtend@^4.0.0: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From 40bfe62422cfa8915036f2e16a19085520f6222f Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 31 Aug 2021 22:56:33 -0700 Subject: [PATCH 0486/1276] Resolved Functional Tests CI/CD Issue (#3231) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fe8f8026e..3ddb6dbc1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -859,7 +859,7 @@ jobs: echo "Running golang functional tests for stage: ${STAGE}" echo "Home directory : $(ls ${HOME})" echo "${HOME}/cla-backend-go directory: $(ls ${HOME}/cla-backend-go)" - ${HOME}/cla-backend-go/functional-tests + ${HOME}/cla-backend-go/bin/functional-tests functionalTestsTavernDev: <<: *functionalTestsTavern From 4d1b40c2b93e02f151f3279bbb78aab57baf353a Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 1 Sep 2021 11:18:40 +0300 Subject: [PATCH 0487/1276] Bug/MR Update - Resolved icla callback to gitlab trigger for MR update Signed-off-by: Harold Wanyama --- cla-backend/cla/models/docusign_models.py | 31 +++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 4197c702a..16c390214 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -10,29 +10,28 @@ """ import io +import json import os import urllib.request import uuid import xml.etree.ElementTree as ET -from typing import Dict, Any, Optional, List +from typing import Any, Dict, List, Optional from urllib.parse import urlparse -import requests +import cla import pydocusign # type: ignore +import requests from attr import dataclass -from pydocusign.exceptions import DocuSignException # type: ignore - -import cla from cla.controllers.lf_group import LFGroup -from cla.models import signing_service_interface, DoesNotExist -from cla.models.dynamo_models import Signature, User, \ - Project, Company, Gerrit, \ - Document, Event +from cla.models import DoesNotExist, signing_service_interface +from cla.models.dynamo_models import (Company, Document, Event, Gerrit, + Project, Signature, User) from cla.models.event_types import EventType from cla.models.s3_storage import S3Storage from cla.user_service import UserService -from cla.utils import get_email_help_content, append_email_help_sign_off_content, get_corporate_url, \ - get_project_cla_group_instance +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', '') @@ -255,9 +254,9 @@ def request_individual_signature(self, project_id, user_id, return_url=None, ret if return_url_type.lower() == "github": acl = user.get_user_github_id() elif return_url_type.lower() == "gitlab": - acl = user.get_user_github_id() + acl = user.get_user_gitlab_id() cla.log.debug('Individual Signature - setting ACL using user {} id: {}'.format(return_url_type, acl)) - signature.set_signature_acl('github:{}'.format(acl)) + signature.set_signature_acl('{}:{}'.format(return_url_type.lower(),acl)) # Populate sign url self.populate_sign_url(signature, callback_url, default_values=default_cla_values, @@ -1622,13 +1621,17 @@ def _update_gitlab_mr(self, organization_id: str , gitlab_repository_id: int, me """ fn = 'models.docusign_models._update_gitlab_mr' try: + headers = { + 'Content-type': 'application/json', + 'Accept': 'application/json' + } url = f'{cla.config.PLATFORM_GATEWAY_URL}/cla-service/v4/gitlab/trigger' payload = { "gitlab_external_repository_id": gitlab_repository_id, "gitlab_mr_id": merge_request_id, "gitlab_organization_id": organization_id } - requests.post(url, data=payload) + requests.post(url, data=json.dumps(payload), headers=headers) cla.log.debug(f'{fn} - Updating GitLab MR with payload: {payload}') except requests.exceptions.HTTPError as err: msg = f'{fn} - Unable to update GitLab MR: {merge_request_id}, error: {err}' From d2f8bdb23b9117db94650c08cb84176ff594b12b Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 1 Sep 2021 11:54:07 +0300 Subject: [PATCH 0488/1276] Bug/Emp Acknowedgement Gitlab - Resolved repo check for post signing of GitLab/GitHub Signed-off-by: Harold Wanyama --- cla-backend/cla/models/docusign_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 16c390214..92dbba862 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -738,7 +738,7 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url github_repository_id = signature_metadata['repository_id'] change_request_id = signature_metadata['pull_request_id'] - if return_url_type == "github": + if return_url_type.lower() == "github": # Get repository installation_id = cla.utils.get_installation_id_from_github_repository(github_repository_id) if installation_id is None: @@ -746,7 +746,7 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url update_repository_provider(installation_id, github_repository_id, change_request_id) - elif return_url_type == "gitlab": + elif return_url_type.lower() == "gitlab": gitlab_repository_id = signature_metadata['repository_id'] merge_request_id = signature_metadata['merge_request_id'] organization_id = cla.utils.get_organization_id_from_gitlab_repository(gitlab_repository_id) From e71509065d707c59467c145e4f00993a95c144b9 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Thu, 2 Sep 2021 08:45:30 -0700 Subject: [PATCH 0489/1276] [#3236] Bug/GitLab ECLA (#3238) --- cla-backend/cla/models/dynamo_models.py | 53 ++++++++++++++++++++++ cla-backend/cla/models/model_interfaces.py | 6 +++ cla-backend/cla/utils.py | 45 ++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index b1aaf9223..6d669e7fc 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2056,6 +2056,45 @@ def is_approved(self, ccla_signature: Signature) -> bool: cla.log.debug(f'{fn} - no github organization approval list defined for this CCLA') else: cla.log.debug(f'{fn} - user\'s github_username is not defined - skipping github org approval list check') + + #Check GitLab username and id + gitlab_username = self.get_user_gitlab_username() + gitlab_id = self.get_user_gitlab_id() + + # Attempt to fetch the gitlab username based on the gitlab id + if gitlab_username is None and gitlab_id is not None: + github_username = cla.utils.lookup_user_gitlab_username(gitlab_id) + if gitlab_username is not None: + cla.log.debug(f'{fn} - updating user record - adding gitlab username: {gitlab_username}') + self.set_user_gitlab_username(gitlab_username) + self.save() + + # Attempt to fetch the gitlab id based on the gitlab username + if gitlab_id is None and gitlab_username is not None: + gitlab_username = gitlab_username.strip() + gitlab_id = cla.utils.lookup_user_gitlab_id(gitlab_username) + if gitlab_id is not None: + cla.log.debug(f'{fn} - updating user record - adding gitlab id: {gitlab_id}') + self.set_user_gitlab_id(gitlab_id) + self.save() + + # GitHub username approval list processing + if gitlab_username is not None: + # remove leading and trailing whitespace from gitlab username + gitlab_username = gitlab_username.strip() + gitlab_whitelist = ccla_signature.get_gitlab_username_approval_list() + cla.log.debug(f'{fn} - testing user github username: {github_username} with ' + f'CCLA github approval list: {github_whitelist}') + + if gitlab_whitelist is not None: + # case insensitive search + if gitlab_username.lower() in (s.lower() for s in github_whitelist): + cla.log.debug(f'{fn} - found github username in github approval list') + return True + else: + cla.log.debug(f'{fn} - users github_username is not defined - ' + 'skipping github username approval list check') + cla.log.debug(f'{fn} - unable to find user in any whitelist') return False @@ -2398,6 +2437,8 @@ class Meta: email_whitelist = ListAttribute(null=True) github_whitelist = ListAttribute(null=True) github_org_whitelist = ListAttribute(null=True) + gitlab_org_approval_list = ListAttribute(null=True) + gitlab_username_approval_list = ListAttribute(null=True) # Additional attributes for ICLAs user_email = UnicodeAttribute(null=True) @@ -2660,6 +2701,12 @@ def get_github_whitelist(self): def get_github_org_whitelist(self): return self.model.github_org_whitelist + + def get_gitlab_org_approval_list(self): + return self.model.gitlab_org_approval_list + + def get_gitlab_username_approval_list(self): + return self.model.gitlab_username_approval_list def get_note(self): return self.model.note @@ -2810,6 +2857,12 @@ def set_github_whitelist(self, github_whitelist): def set_github_org_whitelist(self, github_org_whitelist): self.model.github_org_whitelist = [github_org.strip() for github_org in github_org_whitelist] + + def set_gitlab_username_approval_list(self, gitlab_username_approval_list): + self.model.gitlab_username_approval_list = [gitlab_user.strip() for gitlab_user in gitlab_username_approval_list] + + def set_gitlab_org_approval_list(self, gitlab_org_approval_list): + self.model.gitlab_org_approval_list = [gitlab_org.strip() for gitlab_org in gitlab_org_approval_list] def set_note(self, note): self.model.note = note diff --git a/cla-backend/cla/models/model_interfaces.py b/cla-backend/cla/models/model_interfaces.py index 0dce58e9d..5138cb19a 100644 --- a/cla-backend/cla/models/model_interfaces.py +++ b/cla-backend/cla/models/model_interfaces.py @@ -936,6 +936,12 @@ def get_github_whitelist(self): def get_github_org_whitelist(self): raise NotImplementedError() + + def get_gitlab_org_approval_list(self): + raise NotImplementedError + + def get_gitlab_username_approval_list(self): + raise NotImplementedError def get_note(self): raise NotImplementedError() diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index fe15b6610..46853f153 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1370,6 +1370,51 @@ def request_individual_signature(installation_id, github_repository_id, user, ch raise falcon.HTTPFound(return_url) +def lookup_user_gitlab_username(user_gitlab_id: int) -> Optional[str]: + """ + Given a user gitlab ID, looks up the user's gitlab login/username. + :param user_gitlab_id: the gitlab id + :return: the user's gitlab login/username + """ + try: + r = requests.get(f'https://gitlab.com/api/v4/users/{user_gitlab_id}') + r.raise_for_status() + except requests.exceptions.HTTPError as err: + msg = f'Could not get user github user from id: {user_gitlab_id}: error: {err}' + cla.log.warning(msg) + return None + + gitlab_user = r.json() + if 'id' in gitlab_user: + return gitlab_user['id'] + else: + cla.log.warning('Malformed HTTP response from GitLab - expecting "id" attribute ' + f'- response: {gitlab_user}') + return None + +def lookup_user_gitlab_id(user_gitlab_username: str) -> Optional[str]: + """ + Given a user gitlab username, looks up the user's gitlab id. + :param user_gitlab_username: the gitlab username + :return: the user's gitlab id + """ + try: + r = requests.get(f'https://gitlab.com/api/v4/users?username={user_gitlab_username}') + r.raise_for_status() + except requests.exceptions.HTTPError as err: + msg = f'Could not get user github user from username: {user_gitlab_username}: error: {err}' + cla.log.warning(msg) + return None + + gitlab_user = r.json() + if 'username' in gitlab_user: + return gitlab_user['username'] + else: + cla.log.warning('Malformed HTTP response from GitLab - expecting "username" attribute ' + f'- response: {gitlab_user}') + return None + + def lookup_user_github_username(user_github_id: int) -> Optional[str]: """ Given a user github ID, looks up the user's github login/username. From 3684ee597e38f9e6a874efcafdfefca1c0944ef2 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Thu, 2 Sep 2021 11:11:10 -0700 Subject: [PATCH 0490/1276] [#3236]Bug/GitLab Emp check (#3240) --- cla-backend/cla/models/dynamo_models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 6d669e7fc..131ff996c 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2078,17 +2078,17 @@ def is_approved(self, ccla_signature: Signature) -> bool: self.set_user_gitlab_id(gitlab_id) self.save() - # GitHub username approval list processing + # GitLab username approval list processing if gitlab_username is not None: # remove leading and trailing whitespace from gitlab username gitlab_username = gitlab_username.strip() gitlab_whitelist = ccla_signature.get_gitlab_username_approval_list() - cla.log.debug(f'{fn} - testing user github username: {github_username} with ' - f'CCLA github approval list: {github_whitelist}') + cla.log.debug(f'{fn} - testing user github username: {gitlab_username} with ' + f'CCLA github approval list: {gitlab_whitelist}') if gitlab_whitelist is not None: # case insensitive search - if gitlab_username.lower() in (s.lower() for s in github_whitelist): + if gitlab_username.lower() in (s.lower() for s in gitlab_whitelist): cla.log.debug(f'{fn} - found github username in github approval list') return True else: From b15c582c43daba79d186d163905c6dd441c0733e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:35:16 -0700 Subject: [PATCH 0491/1276] Bump tar from 4.4.15 to 4.4.19 in /cla-frontend-project-console (#3241) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index 30230a8eb..8514db0a2 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -1481,7 +1481,7 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.1: +chownr@^1.0.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -2742,7 +2742,7 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.5: +fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== @@ -4177,7 +4177,7 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: +minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -4192,7 +4192,7 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.2.1: +minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== @@ -4223,7 +4223,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5321,7 +5321,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6012,17 +6012,17 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^4, tar@^4.0.2: - version "4.4.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" - integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" tar@^6.0.5: version "6.0.5" @@ -6528,7 +6528,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From d2150914da224fd4a6964aee8898c2bbb2a6442d Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Thu, 2 Sep 2021 08:45:30 -0700 Subject: [PATCH 0492/1276] [#3239,#3244] Bug/GitLab ECLA (#3238) - Added v2 endpoint that fetches members of a given group id - Updated controller for employee ack with right return_url_types(gitlab/github) - check for gitlab group approval list criteria Signed-off-by: Harold Wanyama --- .../github_organizations/handlers.go | 1 + cla-backend-go/gitlab_api/client_groups.go | 18 +++++++ cla-backend-go/go.mod | 2 + cla-backend-go/go.sum | 6 +++ cla-backend-go/swagger/cla.v2.yaml | 36 ++++++++++++- .../swagger/common/gitlab-group-member.yaml | 36 +++++++++++++ .../common/gitlab-group-members-list.yaml | 9 ++++ .../v2/gitlab_organizations/handlers.go | 17 +++++++ .../v2/gitlab_organizations/service.go | 48 +++++++++++++++++ cla-backend/cla/controllers/signing.py | 2 +- cla-backend/cla/models/dynamo_models.py | 51 +++++++++++++++++-- cla-backend/cla/utils.py | 10 ++++ 12 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 cla-backend-go/swagger/common/gitlab-group-member.yaml create mode 100644 cla-backend-go/swagger/common/gitlab-group-members-list.yaml diff --git a/cla-backend-go/github_organizations/handlers.go b/cla-backend-go/github_organizations/handlers.go index e40db30fe..721113f1a 100644 --- a/cla-backend-go/github_organizations/handlers.go +++ b/cla-backend-go/github_organizations/handlers.go @@ -176,6 +176,7 @@ func Configure(api *operations.ClaAPI, service ServiceInterface, eventService ev return github_organizations.NewUpdateProjectGithubOrganizationConfigOK() }) + } type codedResponse interface { diff --git a/cla-backend-go/gitlab_api/client_groups.go b/cla-backend-go/gitlab_api/client_groups.go index 533c29edc..edbb249bf 100644 --- a/cla-backend-go/gitlab_api/client_groups.go +++ b/cla-backend-go/gitlab_api/client_groups.go @@ -192,6 +192,24 @@ func GetGroupProjectListByGroupID(ctx context.Context, client *goGitLab.Client, return projectList, nil } +// ListGroupMembers lists the members of a given groupID +func ListGroupMembers(ctx context.Context, client *goGitLab.Client, groupID int) ([]*goGitLab.GroupMember, error) { + f := logrus.Fields{ + "functionName": "gitlab_api.client_groups.GetGroupMembers", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + log.WithFields(f).Debugf("fetching gitlab members for groupID: %d", groupID) + + opts := &goGitLab.ListGroupMembersOptions{} + members, _, err := client.Groups.ListGroupMembers(groupID, opts) + if err != nil { + log.WithFields(f).Debugf("unable to fetch members for gitlab GroupID : %d", groupID) + return nil, err + } + return members, err +} + // ListUserProjectGroups fetches the unique groups of a gitlab users groups, // note: it doesn't list the projects/groups the user is member of ..., it's very limited func ListUserProjectGroups(ctx context.Context, client *goGitLab.Client, userID int) ([]*UserGroup, error) { diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 3f438b245..677c999d8 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -98,9 +98,11 @@ require ( github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/myitcv/gobin v0.0.14 // indirect github.com/pelletier/go-toml v1.9.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 7a0dc864a..0e95067da 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -448,6 +448,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= +github.com/myitcv/gobin v0.0.14 h1:YkTUz0IeRspEJlly/+AXRBMA3GN7ArRVbsLJ1uYFwRk= +github.com/myitcv/gobin v0.0.14/go.mod h1:GvHEiYCWroKI2KrMT+xQkHC3FC551wigVWeR4Sgg5P4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= @@ -463,6 +465,7 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -476,6 +479,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index c014011ae..b05dfc76a 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -1842,7 +1842,35 @@ paths: $ref: '#/responses/internal-server-error' tags: - gitlab-repositories - + + /gitlab/group/{gitLabGroupID}/members: + get: + summary: List members of a given GitLab group + description: Endpoint that returs the list of GitLab organization members + operationId: getGitLabGroupMembers + security: [] + parameters: + - $ref: "#/parameters/x-request-id" + - name: gitLabGroupID + in: path + type: string + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/gitlab-group-members-list' + '400': + $ref: '#/responses/invalid-request' + '404': + $ref: '#/responses/not-found' + tags: + - gitlab-organizations + /cla-group/{claGroupID}/icla/signatures: get: summary: List individual signatures for CLA Group @@ -4523,6 +4551,12 @@ definitions: gitlab-repositories-enroll: $ref: './common/gitlab-repositories-enroll.yaml' + + gitlab-group-member: + $ref: './common/gitlab-group-member.yaml' + + gitlab-group-members-list: + $ref: './common/gitlab-group-members-list.yaml' # --------------------------------------------------------------------------- # CLA Group Definitions diff --git a/cla-backend-go/swagger/common/gitlab-group-member.yaml b/cla-backend-go/swagger/common/gitlab-group-member.yaml new file mode 100644 index 000000000..3b49d2f81 --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-group-member.yaml @@ -0,0 +1,36 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + id: + type: string + description: user id of the gitlab member + username: + type: string + description: username of the gitlab member + name: + type: string + description: name of the gitlab member + state: + type: string + description: state of the gitlab member + avatar_url: + type: string + description: avatar_url of the gitlab member + web_url: + type: string + description: web_url of gitlab member + expired_at: + type: string + description: user expiry time + created_at: + type: string + description: created_at user date + access_level: + type: string + description: access level for user + group_saml_identity: + type: string + description: group saml identity + \ No newline at end of file diff --git a/cla-backend-go/swagger/common/gitlab-group-members-list.yaml b/cla-backend-go/swagger/common/gitlab-group-members-list.yaml new file mode 100644 index 000000000..fd2a7530f --- /dev/null +++ b/cla-backend-go/swagger/common/gitlab-group-members-list.yaml @@ -0,0 +1,9 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + list: + type: array + items: + $ref: '#/definitions/gitlab-group-member' \ No newline at end of file diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 12759ada2..55fa5cd69 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -228,6 +228,23 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return gitlab_organizations.NewAddProjectGitlabOrganizationOK().WithPayload(result) }) + api.GitlabOrganizationsGetGitLabGroupMembersHandler = gitlab_organizations.GetGitLabGroupMembersHandlerFunc(func(params gitlab_organizations.GetGitLabGroupMembersParams) middleware.Responder { + reqID := utils.GetRequestID(params.XREQUESTID) + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.handlers.GitlabOrganizationsGetGitLabGroupMembersHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabGroupID": params.GitLabGroupID, + } + log.WithFields(f).Debug("fetching gitlab group member details") + memberList, err := service.GetGitLabGroupMembers(ctx, params.GitLabGroupID) + if err != nil { + msg := fmt.Sprintf("unable to get groupID: %s member list: %+v ", params.GitLabGroupID, err) + return gitlab_organizations.NewGetGitLabGroupMembersBadRequest().WithPayload(utils.ErrorResponseBadRequest(reqID, msg)) + } + return gitlab_organizations.NewGetGitLabGroupMembersOK().WithPayload(memberList) + }) + api.GitlabOrganizationsUpdateProjectGitlabGroupConfigHandler = gitlab_organizations.UpdateProjectGitlabGroupConfigHandlerFunc(func(params gitlab_organizations.UpdateProjectGitlabGroupConfigParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index cbd1e3031..0f37a564a 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -10,6 +10,7 @@ import ( "io" "net/url" "sort" + "strconv" "strings" "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" @@ -44,6 +45,7 @@ type ServiceInterface interface { GetGitLabOrganizationsEnabledWithAutoEnabled(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabProjectOrganizations, error) GetGitLabOrganizationByState(ctx context.Context, gitLabOrganizationID, authState string) (*v2Models.GitlabOrganization, error) + GetGitLabGroupMembers(ctx context.Context, groupID string) (*v2Models.GitlabGroupMembersList, error) UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) error UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlabApi.OauthSuccessResponse) error DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID string, gitlabOrgFullPath string) error @@ -283,6 +285,52 @@ func (s *Service) GetGitLabOrganizationsEnabledWithAutoEnabled(ctx context.Conte return out, nil } +//GetGitLabGroupMembers gets group members +func (s *Service) GetGitLabGroupMembers(ctx context.Context, groupID string) (*v2Models.GitlabGroupMembersList, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitLabGroupMembers", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "groupID": groupID, + } + groupMemberList := make([]*v2Models.GitlabGroupMember, 0) + gitlabOrg, err := s.GetGitLabOrganization(ctx, groupID) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to fetch gitlab details") + return nil, err + } + + if gitlabOrg != nil { + glClient, clientErr := gitlabApi.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warn("problem getting gitLabClient") + return nil, clientErr + } + + members, err := gitlabApi.ListGroupMembers(ctx, glClient, int(gitlabOrg.OrganizationExternalID)) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to get group members list") + return nil, err + } + + if len(members) > 0 { + for _, member := range members { + groupMemberList = append(groupMemberList, &v2Models.GitlabGroupMember{ + Name: member.Name, + ID: strconv.Itoa((member.ID)), + Username: member.Username, + }) + } + } + + } + + log.WithFields(f).Debugf("Members: %+v ", groupMemberList) + + return &v2Models.GitlabGroupMembersList{ + List: groupMemberList, + }, nil +} + // GetGitLabOrganizationsByProjectSFID returns a collection of GitLab organizations based on the specified project SFID value func (s *Service) GetGitLabOrganizationsByProjectSFID(ctx context.Context, projectSFID string) (*v2Models.GitlabProjectOrganizations, error) { f := logrus.Fields{ diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index 8dc40dc17..4575e66e4 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -123,7 +123,7 @@ def request_employee_signature(project_id, company_id, user_id, return_url_type, return_url) elif return_url_type is not None and (return_url_type.lower() == "github" or return_url_type.lower() == "gitlab"): cla.log.error(f'{fn} - return type is github - invoking: request_employee_signature') - return signing_service.request_employee_signature(str(project_id), str(company_id), str(user_id), return_url) + return signing_service.request_employee_signature(str(project_id), str(company_id), str(user_id), return_url, return_url_type=return_url_type) else: msg = (f'{fn} - unsupported return type {return_url_type} for ' diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 131ff996c..193281289 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -480,6 +480,21 @@ class Meta: organization_name_lower = UnicodeAttribute(hash_key=True) +class GitlabExternalGroupIDIndex(GlobalSecondaryIndex): + """ + This class represents a global secondary index for querying gitlab organizations by group ID + """ + + class Meta: + """Meta class for external ID for gitlab group id index""" + + index_name = "gitlab-external-group-id-index" + write_capacity_units = int(cla.conf["DYNAMO_WRITE_UNITS"]) + read_capacity_units = int(cla.conf["DYNAMO_READ_UNITS"]) + projection = AllProjection() + + external_gitlab_group_id = NumberAttribute(hash_key=True) + class GerritProjectIDIndex(GlobalSecondaryIndex): """ @@ -2094,7 +2109,19 @@ def is_approved(self, ccla_signature: Signature) -> bool: else: cla.log.debug(f'{fn} - users github_username is not defined - ' 'skipping github username approval list check') - + + if gitlab_username is not None : + cla.log.debug(f'{fn} fetching gitlab org approval list items to search by username: {gitlab_username}') + gitlab_org_approval_lists = ccla_signature.get_gitlab_org_approval_list() + if gitlab_org_approval_lists: + for gl_name in gitlab_org_approval_lists: + gl_org = GitlabOrg().search_organization_by_lower_name(gl_name) + cla.log.debugf(f"{fn} checking gitlab_username against approval list for company: {gl_org}") + gl_list = list(filter(lambda gl_user: gl_user.get_gitlab_username() == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) + if len(gl_list) > 0: + cla.models.debug(f'{fn} - found gitlab username in gitlab approval list') + return True + cla.log.debug(f'{fn} - unable to find user in any whitelist') return False @@ -3710,9 +3737,11 @@ class Meta: organization_name_lower = UnicodeAttribute(null=True) organization_sfid = UnicodeAttribute() project_sfid = UnicodeAttribute() + auth_info = UnicodeAttribute() organization_sfid_index = GitlabOrgSFIndex() project_sfid_organization_name_index = GitlabOrgProjectSfidOrganizationNameIndex() organization_name_lowe_index = GitlabOrganizationNameLowerIndex() + gitlab_external_group_id_index = GitlabExternalGroupIDIndex() auto_enabled = BooleanAttribute(null=True) auto_enabled_cla_group_id = UnicodeAttribute(null=True) branch_protection_enabled = BooleanAttribute(null=True) @@ -3909,7 +3938,7 @@ class GitlabOrg(model_interfaces.GitlabOrg): # pylint: disable=too-many-public- """ def __init__( - self, organization_id=None, organization_name=None, organization_sfid=None, + self, organization_id=None, organization_name=None, organization_sfid=None, auth_info=None, project_sfid=None, auto_enabled=False, branch_protection_enabled=False, note=None, enabled=True ): super(GitlabOrg).__init__() @@ -3928,6 +3957,7 @@ def __init__( self.model.branch_protection_enabled = branch_protection_enabled self.model.enabled = enabled self.model.note = note + self.model.auth_info = auth_info def __str__(self): return ( @@ -3937,7 +3967,8 @@ def __str__(self): f'auto_enabled: {self.model.auto_enabled},' f'branch_protection_enabled: {self.model.branch_protection_enabled},' f'enabled: {self.model.enabled},' - f'note: {self.model.note}' + f'note: {self.model.note}', + f'auth_info: {self.model.auth_info}' ) def to_dict(self): @@ -3988,6 +4019,9 @@ def get_note(self): :rtype: str """ return self.model.note + + def get_auth_info(self): + return self.model.auth_info def get_enabled(self): return self.model.enabled @@ -4017,6 +4051,17 @@ def set_note(self, note): def set_enabled(self, enabled): self.model.enabled = enabled + + def set_auth_info(self, auth_info): + self.model.auth_info = auth_info + + def get_organization_by_groupid(self, groupid): + org_generator = self.model.gitlab_external_group_id_index.query(groupid) + for org_model in org_generator: + org = GitlabOrg() + org.model = org_model + return org + return None def get_organization_by_sfid(self, sfid) -> List: organization_generator = self.model.organization_sfid_index.query(sfid) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 46853f153..dd2251e4f 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1496,6 +1496,16 @@ def lookup_github_organizations(github_username: str): return {'error': 'Could not get user github org: {}'.format(err)} return [github_org['login'] for github_org in r.json()] +def lookup_gitlab_org_members(organization_id): + # Use the v2 Endpoint thats a wrapper for Gitlab Group member query + try: + r = requests.get(f'{cla.config.PLATFORM_GATEWAY_URL}/cla-service/v4/gitlab/group/{organization_id}/members') + r.raise_for_status() + except requests.exceptions.HTTPError as err: + cla.log.warning(f'Could not fetch gitlab org users: {err}') + return {f'error: Could not get user gitlab group id: {organization_id} members: {err}'} + return r.json()['list'] + def update_github_username(github_user: dict, user: User): """ From 5b23540aadffc967e241ffde2cf1c9a6e3596993 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 7 Sep 2021 16:17:23 +0300 Subject: [PATCH 0493/1276] Bug/GitLab ECLA - Resolve gitlab org check for ecla flow Signed-off-by: Harold Wanyama --- cla-backend/cla/models/dynamo_models.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 193281289..611ea585e 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2104,23 +2104,27 @@ def is_approved(self, ccla_signature: Signature) -> bool: if gitlab_whitelist is not None: # case insensitive search if gitlab_username.lower() in (s.lower() for s in gitlab_whitelist): - cla.log.debug(f'{fn} - found github username in github approval list') + cla.log.debug(f'{fn} - found gitlab username in gitlab approval list') return True else: - cla.log.debug(f'{fn} - users github_username is not defined - ' - 'skipping github username approval list check') + cla.log.debug(f'{fn} - users gitlab_username is not defined - ' + 'skipping gitlab username approval list check') if gitlab_username is not None : cla.log.debug(f'{fn} fetching gitlab org approval list items to search by username: {gitlab_username}') gitlab_org_approval_lists = ccla_signature.get_gitlab_org_approval_list() + cla.log.debug(f'{fn} checking gitlab org approval list: {gitlab_org_approval_lists}') if gitlab_org_approval_lists: for gl_name in gitlab_org_approval_lists: - gl_org = GitlabOrg().search_organization_by_lower_name(gl_name) - cla.log.debugf(f"{fn} checking gitlab_username against approval list for company: {gl_org}") - gl_list = list(filter(lambda gl_user: gl_user.get_gitlab_username() == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) - if len(gl_list) > 0: - cla.models.debug(f'{fn} - found gitlab username in gitlab approval list') - return True + try: + gl_org = GitlabOrg().search_organization_by_lower_name(gl_name) + cla.log.debugf(f"{fn} checking gitlab_username against approval list for company: {gl_org}") + gl_list = list(filter(lambda gl_user: gl_user.get_gitlab_username() == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) + if len(gl_list) > 0: + cla.models.debug(f'{fn} - found gitlab username in gitlab approval list') + return True + except DoesNotExist as err: + cla.log.debugf(f'gitlab group : {gl_name} does not exist: {err}') cla.log.debug(f'{fn} - unable to find user in any whitelist') From 742c4f4e79315192aa8a79583ff6dbd7ba928dce Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Tue, 7 Sep 2021 18:36:55 +0300 Subject: [PATCH 0494/1276] gitlab branch protection dynamodb event handlers (#3246) --- .../cmd/dynamo_events_lambda/main.go | 4 + .../cmd/gitlab/branch_protection/main.go | 146 +++++++++++++++++ .../gitlab_api/branch_protection.go | 120 ++++++++++++++ cla-backend-go/go.mod | 1 + cla-backend-go/go.sum | 149 ++++++++++++++++++ .../dynamo_events/gitlab_branch_protection.go | 140 ++++++++++++++++ .../v2/dynamo_events/gitlab_webhooks.go | 2 +- cla-backend-go/v2/dynamo_events/service.go | 9 ++ 8 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 cla-backend-go/cmd/gitlab/branch_protection/main.go create mode 100644 cla-backend-go/gitlab_api/branch_protection.go create mode 100644 cla-backend-go/v2/dynamo_events/gitlab_branch_protection.go diff --git a/cla-backend-go/cmd/dynamo_events_lambda/main.go b/cla-backend-go/cmd/dynamo_events_lambda/main.go index 7226e2699..206936906 100644 --- a/cla-backend-go/cmd/dynamo_events_lambda/main.go +++ b/cla-backend-go/cmd/dynamo_events_lambda/main.go @@ -8,6 +8,8 @@ import ( "encoding/json" "os" + v2Repositories "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" gitlab "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" @@ -85,6 +87,7 @@ func init() { userRepo := user.NewDynamoRepository(awsSession, stage) companyRepo := company.NewRepository(awsSession, stage) projectClaGroupRepo := projects_cla_groups.NewRepository(awsSession, stage) + v2Repository := v2Repositories.NewRepository(awsSession, stage) repositoriesRepo := repositories.NewRepository(awsSession, stage) gerritRepo := gerrits.NewRepository(awsSession, stage) projectRepo := project.NewRepository(awsSession, stage, repositoriesRepo, gerritRepo, projectClaGroupRepo) @@ -142,6 +145,7 @@ func init() { eventsRepo, projectRepo, gitlabOrganizationRepo, + v2Repository, projectService, githubOrganizationsService, repositoriesService, diff --git a/cla-backend-go/cmd/gitlab/branch_protection/main.go b/cla-backend-go/cmd/gitlab/branch_protection/main.go new file mode 100644 index 000000000..0fd5edd7a --- /dev/null +++ b/cla-backend-go/cmd/gitlab/branch_protection/main.go @@ -0,0 +1,146 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "flag" + "fmt" + "os" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/xanzy/go-gitlab" +) + +const ( + possibleDefaultBranch = "main" +) + +var projectID = flag.Int("project", 0, "gitlab project id") + +func main() { + flag.Parse() + + if *projectID == 0 { + log.Fatalf("gitlab project id is missing") + } + + accessToken := os.Getenv("GITLAB_ACCESS_TOKEN") + if accessToken == "" { + log.Fatalf("GITLAB_ACCESS_TOKEN is required") + } + + gitlabClient, err := gitlab.NewOAuthClient(accessToken) + if err != nil { + log.Fatalf("creating client failed : %v", err) + } + + defaultBranch, err := getDefaultBranch(gitlabClient, *projectID) + if err != nil { + log.Fatalf("fetching the default branch failed : %v", err) + } + + log.Println("the default branch found is : ", defaultBranch.Name) + if err := setOrCreateProtection(gitlabClient, *projectID, defaultBranch.Name); err != nil { + log.Fatalf("setting branch protection for : %s failed : %v", defaultBranch.Name, err) + } + + log.Println("branch protection set for : ", defaultBranch.Name) +} + +func setOrCreateProtection(client *gitlab.Client, projectID int, protectionPattern string) error { + var err error + + protectedBranch, resp, err := client.ProtectedBranches.GetProtectedBranch(projectID, protectionPattern) + if err != nil && resp.StatusCode != 404 { + return fmt.Errorf("fetching existing branch failed : %v", err) + } + + if protectedBranch != nil { + if isProtectedBranchSet(protectedBranch) { + log.Println("branch protection already set, nothing to do") + return nil + } + //it's an existing one try to remove it first and re-create it + log.Println("removing old branch protection for string : ", protectionPattern) + _, err = client.ProtectedBranches.UnprotectRepositoryBranches(projectID, protectionPattern) + if err != nil { + return fmt.Errorf("removing protection for existing branch failed : %v", err) + } + } + + log.Println("re-creating branch protection for string ", protectionPattern) + if _, err = createBranchProtection(client, projectID, protectionPattern); err != nil { + return fmt.Errorf("recreating : %v", err) + } + return nil +} + +func createBranchProtection(client *gitlab.Client, projectID int, name string) (*gitlab.ProtectedBranch, error) { + protectedBranch, _, err := client.ProtectedBranches.ProtectRepositoryBranches(projectID, &gitlab.ProtectRepositoryBranchesOptions{ + Name: gitlab.String(name), + PushAccessLevel: gitlab.AccessLevel(gitlab.NoPermissions), + MergeAccessLevel: gitlab.AccessLevel(gitlab.MaintainerPermissions), + UnprotectAccessLevel: nil, + AllowForcePush: gitlab.Bool(false), + AllowedToPush: nil, + AllowedToMerge: nil, + AllowedToUnprotect: nil, + CodeOwnerApprovalRequired: nil, + }) + if err != nil { + return nil, fmt.Errorf("creating new branch protection failed : %v", err) + } + return protectedBranch, nil +} + +func isProtectedBranchSet(protectedBranch *gitlab.ProtectedBranch) bool { + //log.Println("checking branch protection for : ", spew.Sdump(protectedBranch)) + if protectedBranch.AllowForcePush { + return false + } + + if len(protectedBranch.PushAccessLevels) != 1 { + return false + } + + if protectedBranch.PushAccessLevels[0].AccessLevel != gitlab.NoPermissions { + return false + } + + if len(protectedBranch.MergeAccessLevels) != 1 { + return false + } + + if protectedBranch.MergeAccessLevels[0].AccessLevel != gitlab.MaintainerPermissions { + return false + } + + if len(protectedBranch.UnprotectAccessLevels) != 1 { + return false + } + + if protectedBranch.UnprotectAccessLevels[0].AccessLevel != gitlab.MaintainerPermissions { + return false + } + + return true +} + +// finds the default branch for the given project +func getDefaultBranch(client *gitlab.Client, projectID int) (*gitlab.Branch, error) { + project, _, err := client.Projects.GetProject(projectID, &gitlab.GetProjectOptions{}) + if err != nil { + return nil, fmt.Errorf("fetching project failed : %v", err) + } + + defaultBranch := project.DefaultBranch + + // first try with the possible option + branch, _, err := client.Branches.GetBranch(projectID, defaultBranch) + if err != nil { + return nil, fmt.Errorf("fetching default branch failed : %v", err) + } + + return branch, nil +} diff --git a/cla-backend-go/gitlab_api/branch_protection.go b/cla-backend-go/gitlab_api/branch_protection.go new file mode 100644 index 000000000..a3f90d398 --- /dev/null +++ b/cla-backend-go/gitlab_api/branch_protection.go @@ -0,0 +1,120 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package gitlab + +import ( + "context" + "fmt" + + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/sirupsen/logrus" + "github.com/xanzy/go-gitlab" +) + +// SetOrCreateBranchProtection sets the required parameters if existing pattern exists or creates a new one +func SetOrCreateBranchProtection(ctx context.Context, client *gitlab.Client, projectID int, protectionPattern string) error { + var err error + f := logrus.Fields{ + "functionName": "gitlab_api.SetOrCreateBranchProtection", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitlabProjectID": projectID, + "protectionPattern": protectionPattern, + } + + log.WithFields(f).Debugf("setting branch protection...") + + protectedBranch, resp, err := client.ProtectedBranches.GetProtectedBranch(projectID, protectionPattern) + if err != nil && resp.StatusCode != 404 { + return fmt.Errorf("fetching existing branch failed : %v", err) + } + + if protectedBranch != nil { + if isProtectedBranchSet(protectedBranch) { + log.WithFields(f).Debugf("branch protection already set nothing to do") + return nil + } + + //it's an existing one try to remove it first and re-create it + log.WithFields(f).Debugf("removing old branch protection") + _, err = client.ProtectedBranches.UnprotectRepositoryBranches(projectID, protectionPattern) + if err != nil { + return fmt.Errorf("removing protection for existing branch failed : %v", err) + } + } + + log.WithFields(f).Debugf("re-creating branch protection ") + if _, err = createBranchProtection(client, projectID, protectionPattern); err != nil { + return fmt.Errorf("recreating branch protection failed : %v", err) + } + return nil +} + +func createBranchProtection(client *gitlab.Client, projectID int, name string) (*gitlab.ProtectedBranch, error) { + protectedBranch, _, err := client.ProtectedBranches.ProtectRepositoryBranches(projectID, &gitlab.ProtectRepositoryBranchesOptions{ + Name: gitlab.String(name), + PushAccessLevel: gitlab.AccessLevel(gitlab.NoPermissions), + MergeAccessLevel: gitlab.AccessLevel(gitlab.MaintainerPermissions), + UnprotectAccessLevel: nil, + AllowForcePush: gitlab.Bool(false), + AllowedToPush: nil, + AllowedToMerge: nil, + AllowedToUnprotect: nil, + CodeOwnerApprovalRequired: nil, + }) + if err != nil { + return nil, fmt.Errorf("creating new branch protection failed : %v", err) + } + return protectedBranch, nil +} + +func isProtectedBranchSet(protectedBranch *gitlab.ProtectedBranch) bool { + if protectedBranch.AllowForcePush { + return false + } + + if len(protectedBranch.PushAccessLevels) != 1 { + return false + } + + if protectedBranch.PushAccessLevels[0].AccessLevel != gitlab.NoPermissions { + return false + } + + if len(protectedBranch.MergeAccessLevels) != 1 { + return false + } + + if protectedBranch.MergeAccessLevels[0].AccessLevel != gitlab.MaintainerPermissions { + return false + } + + if len(protectedBranch.UnprotectAccessLevels) != 1 { + return false + } + + if protectedBranch.UnprotectAccessLevels[0].AccessLevel != gitlab.MaintainerPermissions { + return false + } + + return true +} + +// GetDefaultBranch finds the default branch for the given project +func GetDefaultBranch(client *gitlab.Client, projectID int) (*gitlab.Branch, error) { + project, _, err := client.Projects.GetProject(projectID, &gitlab.GetProjectOptions{}) + if err != nil { + return nil, fmt.Errorf("fetching project failed : %v", err) + } + + defaultBranch := project.DefaultBranch + + // first try with the possible option + branch, _, err := client.Branches.GetBranch(projectID, defaultBranch) + if err != nil { + return nil, fmt.Errorf("fetching default branch failed : %v", err) + } + + return branch, nil +} diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 677c999d8..6c0c82a8f 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -56,6 +56,7 @@ require ( github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 github.com/tencentyun/scf-go-lib v0.0.0-20200116145541-9a6ea1bf75b8 + github.com/ugorji/go v1.2.6 // indirect github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a github.com/xanzy/go-gitlab v0.50.1 go.uber.org/ratelimit v0.1.0 diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index 0e95067da..e800131fe 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -17,28 +17,38 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5 h1:7tNlRGC3pUEPKS3DwgX5L0s+cBloaq/JBoi9ceN1MCM= github.com/Bowery/prompt v0.0.0-20190419144237-972d0ceb96f5/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2 h1:ZLAgTj9+H3RTmjbRpUamMO8SWS1m4ZKJGGeh9lT985U= github.com/LF-Engineering/aws-lambda-go-api-proxy v0.3.2/go.mod h1:LQj48zwkRwdjVmDCqtPlviW/7IFaSKzz2gDhxRwVrA4= @@ -46,17 +56,23 @@ github.com/LF-Engineering/lfx-kit v0.1.25 h1:Bb3Snc72ppBmbS5CMoLBGFg1Tt7ZhZktZLJ github.com/LF-Engineering/lfx-kit v0.1.25/go.mod h1:B+pko2SqvGNSG9hWDC35JNZ38nTPt+r5KB6k75xM5vY= github.com/LF-Engineering/lfx-models v0.6.44 h1:a4/6+Hc05caUCzd9eQnZIioZUhWxtgpfgVRuf/M2SRY= github.com/LF-Engineering/lfx-models v0.6.44/go.mod h1:AaV7psgE2IPXhaLXYXoFviobYoh09XJ2P/ALOU11OuE= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.1 h1:NL3G1X7/7xduQtA2sJLpVpfHTNBALVNSjob6KEjPXNQ= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -70,32 +86,46 @@ github.com/aws/aws-sdk-go v1.36.27 h1:wc3xLJJHog2SwiqlLnrLUuct/n+dBjB45QhuZw2psV github.com/aws/aws-sdk-go v1.36.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.4 h1:w/jqZtC9YD4DS/Vp9GhWfWcCpuAL58oTnLoI8vE9YHU= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -107,13 +137,17 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fnproject/fdk-go v0.0.2 h1:nebofQYAY8SbcjqmoaBo6KLNTwUrJq6lGdi7RCbq/EA= github.com/fnproject/fdk-go v0.0.2/go.mod h1:9m+nEyku9SqJAVJQsfZOZBQzFkCs+jvmbZJhvgDX4ts= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -122,10 +156,14 @@ github.com/gin-gonic/gin v0.0.0-20180126034611-783c7ee9c14e/go.mod h1:7cKuhb5qV2 github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a h1:l4yNPeA/3kNJwE0uDBVXtFX8hfiHrlqkXBLPOrchWzk= github.com/go-chi/chi v0.0.0-20180202194135-e223a795a06a/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -206,38 +244,55 @@ github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3 h1:3GQ53z7E3o00C/yy7Ko8VXqQXoJGLkrTQCLTF1EjoXU= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211 h1:mSVZ4vj4khv+oThUfS+SQU3UuFIZ5Zo6UNcvK8E8Mz8= github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/dep v0.5.4 h1:WfV5qbGwsBNUDhk+pfI6emWm7SdDFsnSWkqCMNG3BRs= github.com/golang/dep v0.5.4/go.mod h1:6RZ2Wai7dSWk7qL55sDYk+8UPFqcW7all2KDBraPPFA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -267,8 +322,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -289,9 +346,12 @@ github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3 github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -303,8 +363,11 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -313,41 +376,61 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979 h1:UsXWMy9j+GSCN/I1/Oyc4wGaeW2CDYqeqAkEvWPu+cs= github.com/gorilla/mux v0.0.0-20180120075819-c0091a029979/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk= github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ= @@ -355,6 +438,7 @@ github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94= github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= @@ -364,6 +448,7 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= +github.com/jmank88/nuts v0.4.0 h1:3rHp+7YcvtkTPohGBA++MwneB9OlX/rpORvleiRivMQ= github.com/jmank88/nuts v0.4.0/go.mod h1:TKOSbm0p73pdAzgQ7lcZheG2cinZiXqy60KM5ooL3j8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -371,12 +456,14 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v0.0.0-20180128142709-bca911dae073/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -384,25 +471,35 @@ github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f h1:a3Vd00a20dTKLpyS2h github.com/juju/mempool v0.0.0-20160205104927-24974d6c264f/go.mod h1:+7K7MqWi5xWI+s1LyB2g0Di71jZo27y+XOlmhNtV1Y0= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2 h1:McU3wXjBrKfJcOt2Pali5qEir9NLrqOh4EECzdWHknM= github.com/juju/zip v0.0.0-20160205105221-f6b1e93fa2e2/go.mod h1:3mJ64RiWU2x9U6IigvcoVLra6LZQTOwMuHpk02OtOJc= +github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc= github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -417,9 +514,12 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -428,11 +528,17 @@ github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1y github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -445,6 +551,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/request v0.8.0 h1:TbXeQUdBWr1J1df5Z+lQczDFzX9JD71kTCl7Zu/9rNM= github.com/mozillazg/request v0.8.0/go.mod h1:weoQ/mVFNbWgRBtivCGF1tUT9lwneFesues+CleXMWc= @@ -452,12 +559,15 @@ github.com/myitcv/gobin v0.0.14 h1:YkTUz0IeRspEJlly/+AXRBMA3GN7ArRVbsLJ1uYFwRk= github.com/myitcv/gobin v0.0.14/go.mod h1:GvHEiYCWroKI2KrMT+xQkHC3FC551wigVWeR4Sgg5P4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd h1:b2wg8HW/u55DT7Y/vamdEn/jdvtsGkxzl+0+iHa5YmE= github.com/onsi/ginkgo v0.0.0-20180119174237-747514b53ddd/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.3.0 h1:yPHEatyQC4jN3vdfvqJXG7O9vfC6LhaAV1NEdYpP+h0= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc h1:JI2yIEkVFpe4eYIM/fTNtlIayTiGj4m+iku5JLx8uOY= github.com/pdfcpu/pdfcpu v0.3.5-0.20200802160406-be1e0eb55afc/go.mod h1:3wwz3xi60q88WM0kKZeOJvdQ4YgW4Og7whEiodseWs8= @@ -470,30 +580,40 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429 h1:W/FQ2o7cG+X0Wkb8NefNCTRDEodfo6MtfH9BaO8ncMA= github.com/savaki/dynastore v0.0.0-20171109173440-28d8558bb429/go.mod h1:fK0DIsn9VGLYVur3nQ54Yz4LSLLCyDil0gzq5Y8Yzls= +github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 h1:tnWWLf0nI2TI62Wd/ZOea4XYqE+y1sf2pdm+VItsc0c= github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353/go.mod h1:5HStXbIikwtDAgAIqiQIqVgMn7mlvZa6PTpwiAVYGYg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA= github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU= github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -520,6 +640,7 @@ github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -541,23 +662,33 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862 h1:eg5xqGZGatsyRpVnFJkdeUWSFk46lDgkXLvOryv5ySg= github.com/urfave/negroni v0.0.0-20180130044549-22c5532ea862/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a h1:Mt+KWT4h97wIDQahX1eD3OLkmc/fGbLy7EndiE85kMQ= github.com/verdverm/frisby v0.0.0-20170604211311-b16556248a9a/go.mod h1:Z+jvFzFlZ6eHAKMfi8PZZphUtg4S0gc2EZYOL9UnWgA= github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ= github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -570,12 +701,15 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -601,6 +735,7 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -619,8 +754,10 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -630,6 +767,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -758,6 +896,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -832,6 +971,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -859,6 +999,7 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -909,6 +1050,7 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -929,6 +1071,7 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -948,7 +1091,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -971,7 +1116,11 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cla-backend-go/v2/dynamo_events/gitlab_branch_protection.go b/cla-backend-go/v2/dynamo_events/gitlab_branch_protection.go new file mode 100644 index 000000000..8f9cf5f89 --- /dev/null +++ b/cla-backend-go/v2/dynamo_events/gitlab_branch_protection.go @@ -0,0 +1,140 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package dynamo_events + +import ( + "context" + "fmt" + "strconv" + + "github.com/aws/aws-lambda-go/events" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/communitybridge/easycla/cla-backend-go/v2/common" + "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" +) + +// GitLabOrgUpdatedEvent handles branch protection functionality +func (s *service) GitLabOrgUpdatedEvent(event events.DynamoDBEventRecord) error { + ctx := utils.NewContext() + f := logrus.Fields{ + "functionName": "dynamodb_events.gitlab_organization.GitLabOrgUpdatedEvent", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "eventName": event.EventName, + "eventSource": event.EventSource, + "eventID": event.EventID, + } + + log.WithFields(f).Debug("processing event") + var newGitLabOrg, oldGitLabOrg common.GitLabOrganization + err := unmarshalStreamImage(event.Change.NewImage, &newGitLabOrg) + if err != nil { + log.WithFields(f).Warnf("problem unmarshalling the new gitlab organization model from the updated event, error: %+v", err) + return err + } + err = unmarshalStreamImage(event.Change.OldImage, &oldGitLabOrg) + if err != nil { + log.WithFields(f).Warnf("problem unmarshalling the old gitlab organization model from the updated event, error: %+v", err) + return err + } + + f["gitlabOrgID"] = newGitLabOrg.OrganizationID + f["gitlabOrgName"] = newGitLabOrg.OrganizationName + + if !newGitLabOrg.Enabled { + log.WithFields(f).Debugf("gitlab org is not enabled, nothing to do this time") + return nil + } + + // If the branch protection value was updated from false to true.... + if !oldGitLabOrg.BranchProtectionEnabled && newGitLabOrg.BranchProtectionEnabled { + log.WithFields(f).Debug("transition of branchProtectionEnabled false => true - processing...") + return s.enableBranchProtectionForGitLabOrg(ctx, newGitLabOrg) + } + + // it might be a new gitlab org that was just authenticated + if oldGitLabOrg.AuthInfo != newGitLabOrg.AuthInfo && newGitLabOrg.BranchProtectionEnabled { + log.WithFields(f).Debug("auth info was set for the org, processing the branch protection") + return s.enableBranchProtectionForGitLabOrg(ctx, newGitLabOrg) + } + + log.WithFields(f).Debug("no transition of branchProtectionEnabled false => true - ignoring...") + return nil +} + +func (s *service) enableBranchProtectionForGitLabOrg(ctx context.Context, newGitLabOrg common.GitLabOrganization) error { + f := logrus.Fields{ + "functionName": "dynamo_events.gitlab_organization.enableBranchProtectionForGitLabOrg", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectSFID": newGitLabOrg.ProjectSFID, + "organizationName": newGitLabOrg.OrganizationName, + "organizationSFID": newGitLabOrg.OrganizationSFID, + "autoEnabled": newGitLabOrg.AutoEnabled, + "branchProtectionEnabled": newGitLabOrg.BranchProtectionEnabled, + } + + gitlabOrg, err := s.gitLabOrgRepo.GetGitLabOrganizationByName(ctx, newGitLabOrg.OrganizationName) + if err != nil { + return fmt.Errorf("fetching gitlab org : %s failed : %v", newGitLabOrg.OrganizationName, err) + } + + log.WithFields(f).Debugf("creating a new gitlab client object for org: %s...", newGitLabOrg.OrganizationName) + gitLabClient, err := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + if err != nil { + return fmt.Errorf("initializing GitLab client failed : %v", err) + } + + // Locate the repositories already saved under this organization + log.WithFields(f).Debugf("loading repositories under the organization : %s", newGitLabOrg.OrganizationName) + repos, err := s.v2Repository.GitLabGetRepositoriesByOrganizationName(context.Background(), newGitLabOrg.OrganizationName) + if err != nil { + log.WithFields(f).Warnf("problem locating repositories by organization name, error: %+v", err) + return err + } + + var eg errgroup.Group + // a pool of 5 concurrent workers + var workerTokens = make(chan struct{}, 5) + for _, repo := range repos { + // this is for goroutine local variables + repo := repo + // acquire a worker token to create a new goroutine + workerTokens <- struct{}{} + // Update the branch protection in a go routine... + eg.Go(func() error { + defer func() { + <-workerTokens // release the workerToken + }() + log.WithFields(f).Debugf("enabling branch protection for repository: %s", repo.RepositoryName) + + repositoryExternalIDInt, err := strconv.Atoi(repo.RepositoryExternalID) + if err != nil { + return fmt.Errorf("parsing external repository id failed : %v", err) + } + + gitlabDefaultBranch, err := gitlab_api.GetDefaultBranch(gitLabClient, repositoryExternalIDInt) + if err != nil { + return fmt.Errorf("fetching default branch failed : %v", err) + } + + err = gitlab_api.SetOrCreateBranchProtection(ctx, gitLabClient, repositoryExternalIDInt, gitlabDefaultBranch.Name) + if err != nil { + return fmt.Errorf("enabling branch protection for pattern : %s, failed : %v", gitlabDefaultBranch.Name, err) + } + return nil + }) + } + + // Wait for the go routines to finish + log.WithFields(f).Debugf("waiting for %d repositories to complete...", len(repos)) + var branchProtectionErr error + if loadErr := eg.Wait(); loadErr != nil { + log.WithFields(f).Warnf("encountered branch protection setup error: %+v", loadErr) + branchProtectionErr = loadErr + } + + return branchProtectionErr +} diff --git a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go index cc1d07696..4463bd416 100644 --- a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go +++ b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go @@ -62,7 +62,7 @@ func (s *service) GitLabRepoAddedWebhookEventHandler(event events.DynamoDBEventR repositoryExternalIDInt, err := strconv.Atoi(repositoryExternalID) if err != nil { - return fmt.Errorf("parding external repository id failed : %v", err) + return fmt.Errorf("parsing external repository id failed : %v", err) } conf := config.GetConfig() diff --git a/cla-backend-go/v2/dynamo_events/service.go b/cla-backend-go/v2/dynamo_events/service.go index ce2d01794..63a324e70 100644 --- a/cla-backend-go/v2/dynamo_events/service.go +++ b/cla-backend-go/v2/dynamo_events/service.go @@ -11,6 +11,8 @@ import ( "strings" "sync" + v2Repositories "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" @@ -60,6 +62,7 @@ type service struct { projectsClaGroupRepo projects_cla_groups.Repository eventsRepo claevent.Repository gitLabOrgRepo gitlab_organizations.RepositoryInterface + v2Repository v2Repositories.RepositoryInterface projectRepo project.ProjectRepository projectService project.Service githubOrgService github_organizations.ServiceInterface @@ -85,6 +88,7 @@ func NewService(stage string, eventsRepo claevent.Repository, projectRepo project.ProjectRepository, gitLabOrgRepo gitlab_organizations.RepositoryInterface, + v2Repository v2Repositories.RepositoryInterface, projService project.Service, githubOrgService github_organizations.ServiceInterface, repositoryService repositories.Service, @@ -98,6 +102,7 @@ func NewService(stage string, projectsCLAGroupsTable := fmt.Sprintf("cla-%s-projects-cla-groups", stage) githubOrgTableName := fmt.Sprintf("cla-%s-github-orgs", stage) repositoryTableName := fmt.Sprintf("cla-%s-repositories", stage) + gitlabOrgTableName := fmt.Sprintf("cla-%s-gitlab-orgs", stage) // gerritTableName := fmt.Sprintf("cla-%s-gerrit-instances", stage) claGroupsTable := fmt.Sprintf("cla-%s-projects", stage) @@ -110,6 +115,7 @@ func NewService(stage string, eventsRepo: eventsRepo, projectRepo: projectRepo, gitLabOrgRepo: gitLabOrgRepo, + v2Repository: v2Repository, projectService: projService, githubOrgService: githubOrgService, repositoryService: repositoryService, @@ -153,6 +159,9 @@ func NewService(stage string, s.registerCallback(repositoryTableName, Modify, s.GitlabRepoModifiedWebhookEventHandler) s.registerCallback(repositoryTableName, Remove, s.GitLabRepoRemovedWebhookEventHandler) + // gitlab org updates handled like branch protection and etc. + s.registerCallback(gitlabOrgTableName, Modify, s.GitLabOrgUpdatedEvent) + // Check and enable/disable the branch protection when a project s.registerCallback(repositoryTableName, Insert, s.EnableBranchProtectionServiceHandler) s.registerCallback(repositoryTableName, Remove, s.DisableBranchProtectionServiceHandler) From fead3d8e5d16dd982e9aeccf1427516ebb9824a8 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 7 Sep 2021 08:46:27 -0700 Subject: [PATCH 0495/1276] Bug/ECLA request employee check (#3248) --- cla-backend/cla/models/docusign_models.py | 4 ++-- cla-backend/cla/models/dynamo_models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 92dbba862..ba593ec63 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -735,11 +735,11 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url # signature metadata. if not project.get_project_ccla_requires_icla_signature(): cla.log.info(f'{fn} - cla group does not require a separate ICLA signature from the employee - updating PR') - github_repository_id = signature_metadata['repository_id'] - change_request_id = signature_metadata['pull_request_id'] if return_url_type.lower() == "github": # Get repository + github_repository_id = signature_metadata['repository_id'] + change_request_id = signature_metadata['pull_request_id'] installation_id = cla.utils.get_installation_id_from_github_repository(github_repository_id) if installation_id is None: return {'errors': {'github_repository_id': 'The given github repository ID does not exist. '}} diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 611ea585e..d0c21a5b1 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2118,7 +2118,7 @@ def is_approved(self, ccla_signature: Signature) -> bool: for gl_name in gitlab_org_approval_lists: try: gl_org = GitlabOrg().search_organization_by_lower_name(gl_name) - cla.log.debugf(f"{fn} checking gitlab_username against approval list for company: {gl_org}") + cla.log.debug(f"{fn} checking gitlab_username against approval list for company: {gl_org}") gl_list = list(filter(lambda gl_user: gl_user.get_gitlab_username() == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) if len(gl_list) > 0: cla.models.debug(f'{fn} - found gitlab username in gitlab approval list') From 6093d124c217c0970bc00df4366135d6e753bba7 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 7 Sep 2021 09:59:24 -0700 Subject: [PATCH 0496/1276] Bug/Gitlab ECLA (#3249) --- cla-backend/cla/models/dynamo_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index d0c21a5b1..16bd30da4 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2118,8 +2118,8 @@ def is_approved(self, ccla_signature: Signature) -> bool: for gl_name in gitlab_org_approval_lists: try: gl_org = GitlabOrg().search_organization_by_lower_name(gl_name) - cla.log.debug(f"{fn} checking gitlab_username against approval list for company: {gl_org}") - gl_list = list(filter(lambda gl_user: gl_user.get_gitlab_username() == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) + cla.log.debug(f"{fn} checking gitlab_username against approval list for company: {gl_org.get_organization_name()}") + gl_list = list(filter(lambda gl_user: gl_user.get('username') == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) if len(gl_list) > 0: cla.models.debug(f'{fn} - found gitlab username in gitlab approval list') return True From bcdb621b26f1e611564d15eb078594932acb78ef Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 7 Sep 2021 20:54:25 +0300 Subject: [PATCH 0497/1276] Bug/GitLab Approval (#3250) Co-authored-by: Harold Wanyama --- cla-backend/cla/models/dynamo_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 16bd30da4..412f6e735 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2121,10 +2121,10 @@ def is_approved(self, ccla_signature: Signature) -> bool: cla.log.debug(f"{fn} checking gitlab_username against approval list for company: {gl_org.get_organization_name()}") gl_list = list(filter(lambda gl_user: gl_user.get('username') == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) if len(gl_list) > 0: - cla.models.debug(f'{fn} - found gitlab username in gitlab approval list') + cla.log.debug(f'{fn} - found gitlab username in gitlab approval list') return True except DoesNotExist as err: - cla.log.debugf(f'gitlab group : {gl_name} does not exist: {err}') + cla.log.debug(f'gitlab group : {gl_name} does not exist: {err}') cla.log.debug(f'{fn} - unable to find user in any whitelist') From b7e54e296c31563c40389b667d57b6080aefd444 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 7 Sep 2021 13:01:37 -0700 Subject: [PATCH 0498/1276] Serverless Cleanup + Resolved Python Session Issues (#3251) --- cla-backend/cla/models/github_models.py | 15 +- cla-backend/serverless.yml | 315 ++++++++++++------------ 2 files changed, 170 insertions(+), 160 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 1165b56eb..efe8cbaee 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -97,7 +97,15 @@ def sign_request(self, installation_id, github_repository_id, change_request_id, # Not sure if we need a different token for each installation ID... cla.log.debug(f'{fn} - Loading session from request: {request}...') session = self._get_request_session(request) - cla.log.debug(f'{fn} - Adding github details to session...') + cla.log.debug(f'{fn} - Adding github details to session: {session} which is type: {type(session)}...') + + # Ensure session is a dict - getting issue where session is a string + if isinstance(session, str): + # convert string to a dict + cla.log.debug(f'{fn} - session is type: {type(session)} - converting to dict...') + session = json.loads(session) + cla.log.debug(f'{fn} - session is now type: {type(session)}...') + session['github_installation_id'] = installation_id session['github_repository_id'] = github_repository_id session['github_change_request_id'] = change_request_id @@ -124,7 +132,7 @@ def sign_request(self, installation_id, github_repository_id, change_request_id, cla.log.debug(f'{fn} - GitHub OAuth2 request with state {state} - sending user to {authorization_url}') raise falcon.HTTPFound(authorization_url) - def _get_request_session(self, request): # pylint: disable=no-self-use + def _get_request_session(self, request) -> dict: # pylint: disable=no-self-use """ Mockable method used to get the current user session. """ @@ -132,6 +140,7 @@ def _get_request_session(self, request): # pylint: disable=no-self-use session = request.context.get('session') if session is None: cla.log.warning(f'Session is empty for request: {request}') + cla.log.debug(f'loaded session: {session}') return session def get_authorization_url_and_state(self, installation_id, github_repository_id, pull_request_number, scope): @@ -845,7 +854,7 @@ def handle_commit_from_user(project, commit_sha, author_info, signed, missing): f'user: {author_username}, ' f'email {author_email}) is on the approved list, ' 'but not affiliated with a company') - + list_author_info.append(True) break missing.append((commit_sha, list_author_info)) diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index af804ba41..5861962d4 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -3,6 +3,7 @@ service: cla-backend frameworkVersion: '^2.57.0' +variablesResolutionMode: 20210326 package: # Exclude all first - selectively add in lambda functions @@ -27,7 +28,7 @@ package: - '.serverless-wsgi' custom: - allowed_origins: ${file(./env.json):cla-allowed-origins-${opt:stage}, ssm:/cla-allowed-origins-${opt:stage}} + allowed_origins: ${file(./env.json):cla-allowed-origins-${sls:stage}, ssm:/cla-allowed-origins-${sls:stage}} wsgi: app: cla.routes.__hug_wsgi__ pythonBin: python @@ -37,7 +38,7 @@ custom: prune: automatic: true number: 3 - userEventsSNSTopicARN: arn:aws:sns:us-east-2:${aws:accountId}:userservice-triggers-${self:provider.stage}-user-sns-topic + userEventsSNSTopicARN: arn:aws:sns:us-east-2:${aws:accountId}:userservice-triggers-${sls:stage}-user-sns-topic certificate: arn: @@ -72,26 +73,26 @@ custom: customDomains: # https://github.com/amplify-education/serverless-domain-manager - primaryDomain: - domainName: ${self:custom.product.domain.name.${opt:stage}, self:custom.product.domain.name.other} - stage: ${opt:stage} + domainName: ${self:custom.product.domain.name.${sls:stage}, self:custom.product.domain.name.other} + stage: ${sls:stage} basePath: '' # a value of '/' will not work securityPolicy: tls_1_2 apiType: rest - certificateArn: ${self:custom.certificate.arn.${opt:stage}, self:custom.certificate.arn.other} + certificateArn: ${self:custom.certificate.arn.${sls:stage}, self:custom.certificate.arn.other} protocols: - https enabled: true - alternateDomain: - domainName: ${self:custom.product.domain.alt.${opt:stage}, self:custom.product.domain.alt.other} - stage: ${opt:stage} + domainName: ${self:custom.product.domain.alt.${sls:stage}, self:custom.product.domain.alt.other} + stage: ${sls:stage} basePath: '' # a value of '/' will not work securityPolicy: tls_1_2 apiType: rest - certificateArn: ${self:custom.certificate.arn.${opt:stage}, self:custom.certificate.arn.other} + certificateArn: ${self:custom.certificate.arn.${sls:stage}, self:custom.certificate.arn.other} protocols: - https - enabled: ${self:custom.product.domain.alt.enabled.${opt:stage}, self:custom.product.domain.alt.enabled.other} + enabled: ${self:custom.product.domain.alt.enabled.${sls:stage}, self:custom.product.domain.alt.enabled.other} ses_from_email: dev: admin@dev.lfcla.com @@ -101,7 +102,7 @@ custom: provider: name: aws runtime: python3.7 - stage: ${opt:stage} + stage: prod region: us-east-1 timeout: 60 # optional, in seconds, default is 6 logRetentionInDays: 14 @@ -153,19 +154,19 @@ provider: - s3:DeleteObject - s3:PutObjectAcl Resource: - - "arn:aws:s3:::cla-signature-files-${self:provider.stage}/*" - - "arn:aws:s3:::cla-project-logo-${self:provider.stage}/*" + - "arn:aws:s3:::cla-signature-files-${sls:stage}/*" + - "arn:aws:s3:::cla-project-logo-${sls:stage}/*" - Effect: Allow Action: - s3:ListBucket Resource: - - "arn:aws:s3:::cla-signature-files-${self:provider.stage}" - - "arn:aws:s3:::cla-project-logo-${self:provider.stage}" + - "arn:aws:s3:::cla-signature-files-${sls:stage}" + - "arn:aws:s3:::cla-project-logo-${sls:stage}" - Effect: Allow Action: - lambda:InvokeFunction Resource: - - "arn:aws:lambda:${self:provider.region}:${aws:accountId}:function:cla-backend-${opt:stage}-zipbuilder-lambda" + - "arn:aws:lambda:${self:provider.region}:${aws:accountId}:function:cla-backend-${sls:stage}-zipbuilder-lambda" - Effect: Allow Action: - ssm:GetParameter @@ -179,7 +180,7 @@ provider: - "*" Condition: StringEquals: - ses:FromAddress: ${self:custom.ses_from_email.${opt:stage}} + ses:FromAddress: ${self:custom.ses_from_email.${sls:stage}} - Effect: Allow Action: - sns:Publish @@ -205,140 +206,140 @@ provider: - dynamodb:DescribeStream - dynamodb:ListStreams Resource: - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-company-invites" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-session-store" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-store" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-user-permissions" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-metrics" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-ccla-whitelist-requests" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-cla-manager-requests" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-companies" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-company-invites" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gerrit-instances" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-github-orgs" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-session-store" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-store" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-user-permissions" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-metrics" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects-cla-groups" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs" - Effect: Allow Action: - dynamodb:Query Resource: - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/company-id-project-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-username-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/gitlab-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/gitlab-username-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/github-user-external-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/lf-username-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-users/index/lf-email-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gerrit-instances/index/gerrit-project-sfid-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-date-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/reference-signature-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-reference-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-user-ccla-company-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/project-signature-external-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-company-signatory-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/reference-signature-search-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-type-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-company-initial-manager-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/external-company-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/company-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-companies/index/company-signing-entity-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/external-project-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/project-name-search-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/project-name-lower-search-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects/index/foundation-sfid-project-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-repository-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-organization-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/external-repository-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/sfdc-repository-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-organization-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/project-sfid-repository-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-repositories/index/repository-type-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/github-org-sfid-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/project-sfid-organization-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-github-orgs/index/organization-name-lower-search-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-company-invites/index/requested-company-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-type-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/user-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-id-external-project-id-event-epoch-time-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-cla-group-id-event-time-epoch-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-sfid-project-id-event-time-epoch-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/company-id-event-type-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-events/index/event-foundation-sfid-event-time-epoch-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-metrics/index/metric-type-salesforce-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-cla-manager-requests/index/cla-manager-requests-project-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups/index/cla-group-id-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-projects-cla-groups/index/foundation-sfid-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-sfid-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-ccla-whitelist-requests/index/company-id-project-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-ccla-whitelist-requests/index/ccla-approval-list-request-project-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/github-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/github-username-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/gitlab-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/gitlab-username-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/github-user-external-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/lf-username-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-users/index/lf-email-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gerrit-instances/index/gerrit-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gerrit-instances/index/gerrit-project-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gerrit-instances/index/gerrit-project-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/project-signature-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/project-signature-date-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/reference-signature-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/signature-project-reference-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/signature-user-ccla-company-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/project-signature-external-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/signature-company-signatory-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/reference-signature-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/signature-project-id-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/signature-company-initial-manager-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-signatures/index/signature-project-id-sigtype-signed-approved-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-companies/index/external-company-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-companies/index/company-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-companies/index/company-signing-entity-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects/index/external-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects/index/project-name-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects/index/project-name-lower-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects/index/foundation-sfid-project-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/project-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/repository-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/repository-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/external-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/sfdc-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/project-sfid-repository-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/project-sfid-repository-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-repositories/index/repository-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-github-orgs/index/github-org-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-github-orgs/index/project-sfid-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-github-orgs/index/organization-name-lower-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-company-invites/index/requested-company-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/event-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/user-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/company-id-external-project-id-event-epoch-time-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/event-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/event-cla-group-id-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/event-date-and-contains-pii-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/company-sfid-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/company-sfid-project-id-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/company-id-event-type-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-events/index/event-foundation-sfid-event-time-epoch-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-metrics/index/metric-type-salesforce-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-cla-manager-requests/index/cla-manager-requests-company-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-cla-manager-requests/index/cla-manager-requests-external-company-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-cla-manager-requests/index/cla-manager-requests-project-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects-cla-groups/index/cla-group-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-projects-cla-groups/index/foundation-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-org-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-project-sfid-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-organization-name-lower-search-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-full-path-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-external-group-id-index" environment: - STAGE: ${self:provider.stage} + STAGE: ${sls:stage} HOME: /tmp REGION: us-east-1 DYNAMODB_AWS_REGION: us-east-1 - GH_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}} - GH_APP_ID: ${file(./env.json):gh-app-id, ssm:/cla-gh-app-id-${opt:stage}} - GH_OAUTH_CLIENT_ID: ${file(./env.json):gh-oauth-client-id, ssm:/cla-gh-oauth-client-id-${opt:stage}} - GH_OAUTH_SECRET: ${file(./env.json):gh-oauth-secret, ssm:/cla-gh-oauth-secret-${opt:stage}} - GITHUB_OAUTH_TOKEN: ${file(./env.json):gh-access-token, ssm:/cla-gh-access-token-${opt:stage}} - GITHUB_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${opt:stage}} + GH_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${sls:stage}} + GH_APP_ID: ${file(./env.json):gh-app-id, ssm:/cla-gh-app-id-${sls:stage}} + GH_OAUTH_CLIENT_ID: ${file(./env.json):gh-oauth-client-id, ssm:/cla-gh-oauth-client-id-${sls:stage}} + GH_OAUTH_SECRET: ${file(./env.json):gh-oauth-secret, ssm:/cla-gh-oauth-secret-${sls:stage}} + GITHUB_OAUTH_TOKEN: ${file(./env.json):gh-access-token, ssm:/cla-gh-access-token-${sls:stage}} + GITHUB_APP_WEBHOOK_SECRET: ${file(./env.json):gh-app-webhook-secret, ssm:/cla-gh-app-webhook-secret-${sls:stage}} GH_STATUS_CTX_NAME: "EasyCLA" - AUTH0_DOMAIN: ${file(./env.json):auth0-domain, ssm:/cla-auth0-domain-${opt:stage}} - AUTH0_CLIENT_ID: ${file(./env.json):auth0-clientId, ssm:/cla-auth0-clientId-${opt:stage}} - AUTH0_USERNAME_CLAIM: ${file(./env.json):auth0-username-claim, ssm:/cla-auth0-username-claim-${opt:stage}} - AUTH0_ALGORITHM: ${file(./env.json):auth0-algorithm, ssm:/cla-auth0-algorithm-${opt:stage}} - SF_INSTANCE_URL: ${file(./env.json):sf-instance-url, ssm:/cla-sf-instance-url-${opt:stage}} - SF_CLIENT_ID: ${file(./env.json):sf-client-id, ssm:/cla-sf-consumer-key-${opt:stage}} - SF_CLIENT_SECRET: ${file(./env.json):sf-client-secret, ssm:/cla-sf-consumer-secret-${opt:stage}} - SF_USERNAME: ${file(./env.json):sf-username, ssm:/cla-sf-username-${opt:stage}} - SF_PASSWORD: ${file(./env.json):sf-password, ssm:/cla-sf-password-${opt:stage}} - DOCRAPTOR_API_KEY: ${file(./env.json):doc-raptor-api-key, ssm:/cla-doc-raptor-api-key-${opt:stage}} - DOCUSIGN_ROOT_URL: ${file(./env.json):docusign-root-url, ssm:/cla-docusign-root-url-${opt:stage}} - DOCUSIGN_USERNAME: ${file(./env.json):docusign-username, ssm:/cla-docusign-username-${opt:stage}} - DOCUSIGN_PASSWORD: ${file(./env.json):docusign-password, ssm:/cla-docusign-password-${opt:stage}} - DOCUSIGN_INTEGRATOR_KEY: ${file(./env.json):docusign-integrator-key, ssm:/cla-docusign-integrator-key-${opt:stage}} - CLA_API_BASE: ${file(./env.json):cla-api-base, ssm:/cla-api-base-${opt:stage}} - CLA_CONTRIBUTOR_BASE: ${file(./env.json):cla-contributor-base, ssm:/cla-contributor-base-${opt:stage}} - CLA_CONTRIBUTOR_V2_BASE: ${file(./env.json):cla-contributor-v2-base, ssm:/cla-contributor-v2-base-${opt:stage}} - CLA_CORPORATE_BASE: ${file(./env.json):cla-corporate-base, ssm:/cla-corporate-base-${opt:stage}} - CLA_CORPORATE_V2_BASE: ${file(./env.json):cla-corporate-v2-base, ssm:/cla-corporate-v2-base-${opt:stage}} - CLA_LANDING_PAGE: ${file(./env.json):cla-landing-page, ssm:/cla-landing-page-${opt:stage}} - CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${opt:stage}} - CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${opt:stage}} - SES_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-ses-sender-email-address, ssm:/cla-ses-sender-email-address-${opt:stage}} - SMTP_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-smtp-sender-email-address, ssm:/cla-smtp-sender-email-address-${opt:stage}} - LF_GROUP_CLIENT_ID: ${file(./env.json):lf-group-client-id, ssm:/cla-lf-group-client-id-${opt:stage}} - LF_GROUP_CLIENT_SECRET: ${file(./env.json):lf-group-client-secret, ssm:/cla-lf-group-client-secret-${opt:stage}} - LF_GROUP_REFRESH_TOKEN: ${file(./env.json):lf-group-refresh-token, ssm:/cla-lf-group-refresh-token-${opt:stage}} - LF_GROUP_CLIENT_URL: ${file(./env.json):lf-group-client-url, ssm:/cla-lf-group-client-url-${opt:stage}} - SNS_EVENT_TOPIC_ARN: ${file(./env.json):sns-event-topic-arn, ssm:/cla-sns-event-topic-arn-${opt:stage}} - PLATFORM_AUTH0_URL: ${file(./env.json):cla-auth0-platform-url, ssm:/cla-auth0-platform-url-${opt:stage}} - PLATFORM_AUTH0_CLIENT_ID: ${file(./env.json):cla-auth0-platform-client-id, ssm:/cla-auth0-platform-client-id-${opt:stage}} - PLATFORM_AUTH0_CLIENT_SECRET: ${file(./env.json):cla-auth0-platform-client-secret, ssm:/cla-auth0-platform-client-secret-${opt:stage}} - PLATFORM_AUTH0_AUDIENCE: ${file(./env.json):cla-auth0-platform-audience, ssm:/cla-auth0-platform-audience-${opt:stage}} - PLATFORM_GATEWAY_URL: ${file(./env.json):platform-gateway-url, ssm:/cla-auth0-platform-api-gw-${opt:stage}} - PLATFORM_MAINTAINERS: ${file(./env.json):platform-maintainers, ssm:/cla-lf-platform-maintainers-${opt:stage}} + AUTH0_DOMAIN: ${file(./env.json):auth0-domain, ssm:/cla-auth0-domain-${sls:stage}} + AUTH0_CLIENT_ID: ${file(./env.json):auth0-clientId, ssm:/cla-auth0-clientId-${sls:stage}} + AUTH0_USERNAME_CLAIM: ${file(./env.json):auth0-username-claim, ssm:/cla-auth0-username-claim-${sls:stage}} + AUTH0_ALGORITHM: ${file(./env.json):auth0-algorithm, ssm:/cla-auth0-algorithm-${sls:stage}} + SF_INSTANCE_URL: ${file(./env.json):sf-instance-url, ssm:/cla-sf-instance-url-${sls:stage}} + SF_CLIENT_ID: ${file(./env.json):sf-client-id, ssm:/cla-sf-consumer-key-${sls:stage}} + SF_CLIENT_SECRET: ${file(./env.json):sf-client-secret, ssm:/cla-sf-consumer-secret-${sls:stage}} + SF_USERNAME: ${file(./env.json):sf-username, ssm:/cla-sf-username-${sls:stage}} + SF_PASSWORD: ${file(./env.json):sf-password, ssm:/cla-sf-password-${sls:stage}} + DOCRAPTOR_API_KEY: ${file(./env.json):doc-raptor-api-key, ssm:/cla-doc-raptor-api-key-${sls:stage}} + DOCUSIGN_ROOT_URL: ${file(./env.json):docusign-root-url, ssm:/cla-docusign-root-url-${sls:stage}} + DOCUSIGN_USERNAME: ${file(./env.json):docusign-username, ssm:/cla-docusign-username-${sls:stage}} + DOCUSIGN_PASSWORD: ${file(./env.json):docusign-password, ssm:/cla-docusign-password-${sls:stage}} + DOCUSIGN_INTEGRATOR_KEY: ${file(./env.json):docusign-integrator-key, ssm:/cla-docusign-integrator-key-${sls:stage}} + CLA_API_BASE: ${file(./env.json):cla-api-base, ssm:/cla-api-base-${sls:stage}} + CLA_CONTRIBUTOR_BASE: ${file(./env.json):cla-contributor-base, ssm:/cla-contributor-base-${sls:stage}} + CLA_CONTRIBUTOR_V2_BASE: ${file(./env.json):cla-contributor-v2-base, ssm:/cla-contributor-v2-base-${sls:stage}} + CLA_CORPORATE_BASE: ${file(./env.json):cla-corporate-base, ssm:/cla-corporate-base-${sls:stage}} + CLA_CORPORATE_V2_BASE: ${file(./env.json):cla-corporate-v2-base, ssm:/cla-corporate-v2-base-${sls:stage}} + CLA_LANDING_PAGE: ${file(./env.json):cla-landing-page, ssm:/cla-landing-page-${sls:stage}} + CLA_SIGNATURE_FILES_BUCKET: ${file(./env.json):cla-signature-files-bucket, ssm:/cla-signature-files-bucket-${sls:stage}} + CLA_BUCKET_LOGO_URL: ${file(./env.json):cla-logo-url, ssm:/cla-logo-url-${sls:stage}} + SES_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-ses-sender-email-address, ssm:/cla-ses-sender-email-address-${sls:stage}} + SMTP_SENDER_EMAIL_ADDRESS: ${file(./env.json):cla-smtp-sender-email-address, ssm:/cla-smtp-sender-email-address-${sls:stage}} + LF_GROUP_CLIENT_ID: ${file(./env.json):lf-group-client-id, ssm:/cla-lf-group-client-id-${sls:stage}} + LF_GROUP_CLIENT_SECRET: ${file(./env.json):lf-group-client-secret, ssm:/cla-lf-group-client-secret-${sls:stage}} + LF_GROUP_REFRESH_TOKEN: ${file(./env.json):lf-group-refresh-token, ssm:/cla-lf-group-refresh-token-${sls:stage}} + LF_GROUP_CLIENT_URL: ${file(./env.json):lf-group-client-url, ssm:/cla-lf-group-client-url-${sls:stage}} + SNS_EVENT_TOPIC_ARN: ${file(./env.json):sns-event-topic-arn, ssm:/cla-sns-event-topic-arn-${sls:stage}} + PLATFORM_AUTH0_URL: ${file(./env.json):cla-auth0-platform-url, ssm:/cla-auth0-platform-url-${sls:stage}} + PLATFORM_AUTH0_CLIENT_ID: ${file(./env.json):cla-auth0-platform-client-id, ssm:/cla-auth0-platform-client-id-${sls:stage}} + PLATFORM_AUTH0_CLIENT_SECRET: ${file(./env.json):cla-auth0-platform-client-secret, ssm:/cla-auth0-platform-client-secret-${sls:stage}} + PLATFORM_AUTH0_AUDIENCE: ${file(./env.json):cla-auth0-platform-audience, ssm:/cla-auth0-platform-audience-${sls:stage}} + PLATFORM_GATEWAY_URL: ${file(./env.json):platform-gateway-url, ssm:/cla-auth0-platform-api-gw-${sls:stage}} + PLATFORM_MAINTAINERS: ${file(./env.json):platform-maintainers, ssm:/cla-lf-platform-maintainers-${sls:stage}} # Set to true for verbose API logging - useful when Debugging API calls for Core Platform Services or other external services # LOG_DEVEL: debug # default is debug # DEBUG: false # default is false @@ -353,7 +354,7 @@ provider: stackTags: Name: ${self:service} - stage: ${self:provider.stage} + stage: ${sls:stage} Project: "EasyCLA" Product: "EasyCLA" ManagedBy: "Serverless CloudFormation" @@ -364,7 +365,7 @@ provider: Owner: "David Deal" tags: Name: ${self:service} - stage: ${self:provider.stage} + stage: ${sls:stage} Project: "EasyCLA" Product: "EasyCLA" ManagedBy: "Serverless CloudFormation" @@ -398,7 +399,7 @@ functions: - 'auth/bin/**' api-v3-lambda: - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-api-v3-lambda + name: ${self:service}-${sls:stage, 'dev'}-api-v3-lambda description: "EasyCLA Golang API handler for the /v3 endpoints" runtime: go1.x handler: 'bin/backend-aws-lambda' @@ -414,7 +415,7 @@ functions: - 'bin/backend-aws-lambda' dynamo-projects-events-lambda: - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-projects-lambda + name: ${self:service}-${sls:stage, 'dev'}-dynamo-projects-lambda description: "EasyCLA DynamoDB stream events handler for the projects table" handler: 'bin/dynamo-events-lambda' runtime: go1.x @@ -426,7 +427,7 @@ functions: dynamo-signatures-events-lambda: handler: 'bin/dynamo-events-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-signatures-events-lambda + name: ${self:service}-${sls:stage, 'dev'}-dynamo-signatures-events-lambda description: "EasyCLA DynamoDB stream events handler for the signatures table" runtime: go1.x package: @@ -437,7 +438,7 @@ functions: dynamo-events-events-lambda: handler: 'bin/dynamo-events-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-events-events-lambda + name: ${self:service}-${sls:stage, 'dev'}-dynamo-events-events-lambda description: "EasyCLA DynamoDB stream events handler for the events table" runtime: go1.x package: @@ -448,7 +449,7 @@ functions: dynamo-repositories-events-lambda: handler: 'bin/dynamo-events-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-repositories-events-lambda + name: ${self:service}-${sls:stage, 'dev'}-dynamo-repositories-events-lambda description: "EasyCLA DynamoDB stream events handler for the repositories table" runtime: go1.x package: @@ -459,7 +460,7 @@ functions: dynamo-projects-cla-groups-events-lambda: handler: 'bin/dynamo-events-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-projects-cla-groups-events-lambda + name: ${self:service}-${sls:stage, 'dev'}-dynamo-projects-cla-groups-events-lambda description: "EasyCLA DynamoDB stream events handler for the projects-cla-groups table" runtime: go1.x package: @@ -470,7 +471,7 @@ functions: dynamo-github-orgs-events-lambda: handler: 'bin/dynamo-events-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-dynamo-github-orgs-events-lambda + name: ${self:service}-${sls:stage, 'dev'}-dynamo-github-orgs-events-lambda description: "EasyCLA DynamoDB stream events handler for cla--github-orgs the table" runtime: go1.x package: @@ -480,7 +481,7 @@ functions: - 'bin/dynamo-events-lambda' save-metrics-lambda: - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-save-metrics-lambda + name: ${self:service}-${sls:stage, 'dev'}-save-metrics-lambda description: "EasyCLA Save Metrics API handler" runtime: go1.x handler: 'bin/metrics-aws-lambda' @@ -497,7 +498,7 @@ functions: - 'bin/metrics-aws-lambda' report-metrics-lambda: - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-report-metrics-lambda + name: ${self:service}-${sls:stage, 'dev'}-report-metrics-lambda description: "EasyCLA Report Metrics API handler" runtime: go1.x handler: 'bin/metrics-report-lambda' @@ -514,7 +515,7 @@ functions: - 'bin/metrics-report-lambda' zip-builder-scheduler-lambda: - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-zip-builder-scheduler-lambda + name: ${self:service}-${sls:stage, 'dev'}-zip-builder-scheduler-lambda description: "call zipbuilder-lambda for all cla groups periodically" handler: 'bin/zipbuilder-scheduler-lambda' runtime: go1.x @@ -532,7 +533,7 @@ functions: zip-builder-lambda: handler: 'bin/zipbuilder-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-zip-builder-lambda + name: ${self:service}-${sls:stage, 'dev'}-zip-builder-lambda description: "build zip of signed signature pdf for cla group" runtime: go1.x timeout: 900 # maximum time allowed @@ -545,7 +546,7 @@ functions: gitlab-repository-check-lambda: handler: 'bin/gitlab-repository-check-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-gitlab-repository-check-lambda + name: ${self:service}-${sls:stage, 'dev'}-gitlab-repository-check-lambda description: "routine to periodically check the GitLab repository list for auto-enabled GitLab Groups" runtime: go1.x timeout: 900 # maximum time allowed @@ -564,7 +565,7 @@ functions: # User Subscribe event for dynamodb cla-stage-users table. easycla-user-event-handler-lambda: handler: 'bin/user-subscribe-lambda' - name: ${self:service}-${opt:stage, self:provider.stage, 'dev'}-user-event-handler-lambda + name: ${self:service}-${sls:stage, 'dev'}-user-event-handler-lambda runtime: go1.x description: Update easycla user data to user object in dynamodb package: @@ -651,7 +652,7 @@ resources: ApiGatewayRestApi: Type: AWS::ApiGateway::RestApi Properties: - Name: ${self:service}-${self:provider.stage} + Name: ${self:service}-${sls:stage} Description: EasyCLA API Gateway GatewayResponse: @@ -668,9 +669,9 @@ resources: Type: AWS::CertificateManager::Certificate Condition: ShouldGenerateCertificate Properties: - DomainName: ${self:custom.product.domain.name.${opt:stage}, self:custom.product.domain.name.other} + DomainName: ${self:custom.product.domain.name.${sls:stage}, self:custom.product.domain.name.other} SubjectAlternativeNames: - - ${self:custom.product.domain.alt.${opt:stage}, self:custom.product.domain.alt.other} + - ${self:custom.product.domain.alt.${sls:stage}, self:custom.product.domain.alt.other} ValidationMethod: DNS Outputs: From 5dba01a1d1ca68ded1a1817286a928db9a579f99 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 7 Sep 2021 13:51:00 -0700 Subject: [PATCH 0499/1276] Resolved Additional GitHub Session Issues (#3252) --- .circleci/config.yml | 197 ++++++++++++++++-------- cla-backend/cla/models/github_models.py | 46 +++--- 2 files changed, 160 insertions(+), 83 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ddb6dbc1..656e8efca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -283,8 +283,8 @@ jobs: AWS_REGION: us-east-1 DYNAMODB_AWS_REGION: us-east-1 - # Deploys - deployBackend: &deployBackendAnchor + # Deploys Old API + deployV1Backend: &deployV1BackendAnchor docker: - image: circleci/python:3.7.9-node steps: @@ -345,50 +345,18 @@ jobs: if [[ ! -f serverless.yml ]]; then echo "Missing serverless.yml file. Exiting..."; exit 1; fi if [[ ! -f serverless-authorizer.yml ]]; then echo "Missing serverless-authorizer.yml file. Exiting..."; exit 1; fi yarn sls deploy --force --stage ${STAGE} --region us-east-1 - - run: - name: Deploy EasyCLA v2 - command: | - echo "Using AWS profile: ${AWS_PROFILE}" - echo "Stage is: ${STAGE}" - - # -------------------------------------------------------------- - ## Debug to confirm the binary files were restored - echo "Directory: ~/" - ls -alF ~/ - echo "Directory: ~/cla-backend-go/" - ls -alF ~/cla-backend-go/ - ## End Debug - # -------------------------------------------------------------- - - mkdir -p ~/project/cla-backend-go/bin - cp ~/cla-backend-go/bin/backend-aws-lambda ~/project/cla-backend-go/bin/ - cp ~/cla-backend-go/bin/user-subscribe-lambda ~/project/cla-backend-go/bin/ - echo "Directory: ~/project/cla-backend-go/bin/" - ls -alF ~/project/cla-backend-go/bin/ - pushd ~/project/cla-backend-go - echo "Directory: $(pwd)" - if [[ ! -f bin/backend-aws-lambda ]]; then echo "Missing bin/backend-aws-lambda binary file. Exiting..."; exit 1; fi - if [[ ! -f bin/user-subscribe-lambda ]]; then echo "Missing bin/user-subscribe-lambda binary file. Exiting..."; exit 1; fi - yarn install - - # Deploy to us-east-2 - if [[ ! -f serverless.yml ]]; then echo "Missing serverless.yml file in $(pwd). Exiting..."; exit 1; fi - yarn sls deploy --force --stage ${STAGE} --region us-east-2 - run: name: Service Check command: | sudo apt-get install -y curl v2_url='' v3_url='' - v4_url='' if [[ "${STAGE}" == "prod" ]]; then v2_url=https://api.easycla.lfx.linuxfoundation.org/v2/health v3_url=https://api.easycla.lfx.linuxfoundation.org/v3/ops/health - v4_url=https://api-gw.platform.linuxfoundation.org/cla-service/v4/ops/health else v2_url=https://api.lfcla.${STAGE}.platform.linuxfoundation.org/v2/health v3_url=https://api.lfcla.${STAGE}.platform.linuxfoundation.org/v3/ops/health - v4_url=https://api-gw.${STAGE}.platform.linuxfoundation.org/cla-service/v4/ops/health fi echo "Validating v2 backend using endpoint: ${v2_url}" @@ -418,6 +386,65 @@ jobs: exit ${exit_code} fi + deployV2Backend: &deployV2BackendAnchor + docker: + - image: circleci/python:3.7.9-node + steps: + - attach_workspace: + at: ~/ + - checkout + - add_ssh_keys: + fingerprints: + - "e9:13:85:f1:b1:a1:25:bf:f5:44:34:66:82:1e:31:59" + - *setup_aws + - run: echo 'export NVM_DIR=${HOME}/.nvm' >> $BASH_ENV + - *install-node-12 + - run: + name: Install Top Level Dependencies + command: | + echo "Node version is: $(node --version)" + echo "Running top level install..." + yarn install + - run: + name: Deploy EasyCLA v2 + command: | + echo "Using AWS profile: ${AWS_PROFILE}" + echo "Stage is: ${STAGE}" + + # -------------------------------------------------------------- + ## Debug to confirm the binary files were restored + echo "Directory: ~/" + ls -alF ~/ + echo "Directory: ~/cla-backend-go/" + ls -alF ~/cla-backend-go/ + ## End Debug + # -------------------------------------------------------------- + + mkdir -p ~/project/cla-backend-go/bin + cp ~/cla-backend-go/bin/backend-aws-lambda ~/project/cla-backend-go/bin/ + cp ~/cla-backend-go/bin/user-subscribe-lambda ~/project/cla-backend-go/bin/ + echo "Directory: ~/project/cla-backend-go/bin/" + ls -alF ~/project/cla-backend-go/bin/ + pushd ~/project/cla-backend-go + echo "Directory: $(pwd)" + if [[ ! -f bin/backend-aws-lambda ]]; then echo "Missing bin/backend-aws-lambda binary file. Exiting..."; exit 1; fi + if [[ ! -f bin/user-subscribe-lambda ]]; then echo "Missing bin/user-subscribe-lambda binary file. Exiting..."; exit 1; fi + yarn install + + # Deploy to us-east-2 + if [[ ! -f serverless.yml ]]; then echo "Missing serverless.yml file in $(pwd). Exiting..."; exit 1; fi + yarn sls deploy --force --stage ${STAGE} --region us-east-2 + - run: + name: Service Check + command: | + sudo apt-get install -y curl + v4_url='' + if [[ "${STAGE}" == "prod" ]]; then + v4_url=https://api-gw.platform.linuxfoundation.org/cla-service/v4/ops/health + else + v4_url=https://api-gw.${STAGE}.platform.linuxfoundation.org/cla-service/v4/ops/health + fi + echo "Validating v4 backend using endpoint: ${v4_url}" curl --fail -XGET ${v4_url} exit_code=$? @@ -435,8 +462,41 @@ jobs: exit ${exit_code} fi - deployBackendDev: - <<: *deployBackendAnchor + deployV1BackendDev: + <<: *deployV1BackendAnchor + environment: + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 + STAGE: dev + ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org + PRODUCT_DOMAIN: dev.lfcla.com + + deployV1BackendStaging: + <<: *deployV1BackendAnchor + environment: + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 + STAGE: staging + ROOT_DOMAIN: lfcla.staging.platform.linuxfoundation.org + PRODUCT_DOMAIN: staging.lfcla.com + + deployV1BackendProd: + <<: *deployV1BackendAnchor + environment: + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 + STAGE: prod + ROOT_DOMAIN: lfcla.platform.linuxfoundation.org + PRODUCT_DOMAIN: lfcla.com + + deployV2BackendDev: + <<: *deployV2BackendAnchor environment: AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV @@ -446,8 +506,8 @@ jobs: ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org PRODUCT_DOMAIN: dev.lfcla.com - deployBackendStaging: - <<: *deployBackendAnchor + deployV2BackendStaging: + <<: *deployV2BackendAnchor environment: AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING @@ -457,8 +517,8 @@ jobs: ROOT_DOMAIN: lfcla.staging.platform.linuxfoundation.org PRODUCT_DOMAIN: staging.lfcla.com - deployBackendProd: - <<: *deployBackendAnchor + deployV2BackendProd: + <<: *deployV2BackendAnchor environment: AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD @@ -936,7 +996,7 @@ jobs: workflows: version: 2.1 build_and_deploy: - jobs: + jobs: - buildBackendDev: filters: tags: @@ -958,10 +1018,19 @@ workflows: tags: only: /.*/ - # Deploy Dev - - deployBackendDev: + # Deploy v1 Dev + - deployV1BackendDev: requires: - buildBackendDev + filters: + tags: + ignore: /.*/ + branches: + only: + - main + # Deploy v2 Dev + - deployV1BackendDev: + requires: - buildGoBackendCommon filters: tags: @@ -999,7 +1068,8 @@ workflows: - main - functionalTestsGoDev: requires: - - deployBackendDev + - deployV1BackendDev + - deployV2BackendDev filters: tags: ignore: /.*/ @@ -1008,7 +1078,8 @@ workflows: - main - functionalTestsTavernDev: requires: - - deployBackendDev + - deployv1BackendDev + - deployv2BackendDev filters: tags: ignore: /.*/ @@ -1024,13 +1095,6 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ -# - buildGoBackendStaging: -# filters: -# branches: -# ignore: /.*/ -# tags: -# # see semver examples https://regex101.com/r/Ly7O1x/201/ -# only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - approve_staging: type: approval requires: @@ -1042,10 +1106,19 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployBackendStaging: + - deployV1BackendStaging: requires: - approve_staging - buildBackendStaging + filters: + branches: + ignore: /.*/ + tags: + # see semver examples https://regex101.com/r/Ly7O1x/201/ + only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + - deployV2BackendStaging: + requires: + - approve_staging - buildGoBackendCommon filters: branches: @@ -1098,13 +1171,6 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ -# - buildGoBackendProd: -# filters: -# branches: -# ignore: /.*/ -# tags: -# # see semver examples https://regex101.com/r/Ly7O1x/201/ -# only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - approve_prod: type: approval requires: @@ -1116,10 +1182,19 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployBackendProd: + - deployV1BackendProd: requires: - approve_prod - buildBackendProd + filters: + branches: + ignore: /.*/ + tags: + # see semver examples https://regex101.com/r/Ly7O1x/201/ + only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + - deployV2BackendProd: + requires: + - approve_prod - buildGoBackendCommon filters: branches: diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index efe8cbaee..cf1ea7eba 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -98,14 +98,6 @@ def sign_request(self, installation_id, github_repository_id, change_request_id, cla.log.debug(f'{fn} - Loading session from request: {request}...') session = self._get_request_session(request) cla.log.debug(f'{fn} - Adding github details to session: {session} which is type: {type(session)}...') - - # Ensure session is a dict - getting issue where session is a string - if isinstance(session, str): - # convert string to a dict - cla.log.debug(f'{fn} - session is type: {type(session)} - converting to dict...') - session = json.loads(session) - cla.log.debug(f'{fn} - session is now type: {type(session)}...') - session['github_installation_id'] = installation_id session['github_repository_id'] = github_repository_id session['github_change_request_id'] = change_request_id @@ -136,11 +128,19 @@ def _get_request_session(self, request) -> dict: # pylint: disable=no-self-use """ Mockable method used to get the current user session. """ - # return request.context['session'] + fn = 'cla.models.github_models._get_request_session' session = request.context.get('session') if session is None: cla.log.warning(f'Session is empty for request: {request}') cla.log.debug(f'loaded session: {session}') + + # Ensure session is a dict - getting issue where session is a string + if isinstance(session, str): + # convert string to a dict + cla.log.debug(f'{fn} - session is type: {type(session)} - converting to dict...') + session = json.loads(session) + cla.log.debug(f'{fn} - session: {session} which is now type: {type(session)}...') + return session def get_authorization_url_and_state(self, installation_id, github_repository_id, pull_request_number, scope): @@ -483,31 +483,33 @@ def get_or_create_user(self, request): :param request: The hug request object for this API call. :type request: Request """ + fn = 'github_models.get_or_create_user' session = self._get_request_session(request) github_user = self.get_user_data(session, os.environ['GH_OAUTH_CLIENT_ID']) if 'error' in github_user: # Could not get GitHub user data - maybe user revoked CLA app permissions? session = self._get_request_session(request) + del session['github_oauth2_state'] del session['github_oauth2_token'] - cla.log.warning('Deleted OAuth2 session data - retrying token exchange next time') + cla.log.warning(f'{fn} - Deleted OAuth2 session data - retrying token exchange next time') raise falcon.HTTPError('400 Bad Request', 'github_oauth2_token', 'Token permissions have been rejected, please try again') emails = self.get_user_emails(session, os.environ['GH_OAUTH_CLIENT_ID']) if len(emails) < 1: - cla.log.warning('GitHub user has no verified email address: %s (%s)', + cla.log.warning(f'{fn} - GitHub user has no verified email address: %s (%s)', github_user['name'], github_user['login']) raise falcon.HTTPError( '412 Precondition Failed', 'email', 'Please verify at least one email address with GitHub') - cla.log.debug('Trying to load GitHub user by GitHub ID: %s', github_user['id']) + cla.log.debug(f'{fn} - Trying to load GitHub user by GitHub ID: %s', github_user['id']) users = cla.utils.get_user_instance().get_user_by_github_id(github_user['id']) if users is not None: # Users search can return more than one match - so it's an array - we set the first record value for now?? user = users[0] - cla.log.debug('Loaded GitHub user by GitHub ID: %s - %s (%s)', + cla.log.debug(f'{fn} - Loaded GitHub user by GitHub ID: %s - %s (%s)', user.get_user_name(), user.get_user_emails(), user.get_user_github_id()) @@ -520,7 +522,7 @@ def get_or_create_user(self, request): return user # User not found by GitHub ID, trying by email. - cla.log.debug('Could not find GitHub user by GitHub ID: %s', github_user['id']) + cla.log.debug(f'{fn} - Could not find GitHub user by GitHub ID: %s', github_user['id']) # TODO: This is very slow and needs to be improved - may need a DB schema change. users = None user = cla.utils.get_user_instance() @@ -540,12 +542,12 @@ def get_or_create_user(self, request): user.set_user_emails(emails) user.save() - cla.log.debug(f'Loaded GitHub user by email: {user}') + cla.log.debug(f'{fn} - Loaded GitHub user by email: {user}') return user # User not found, create. - cla.log.debug(f'Could not find GitHub user by email: {emails}') - cla.log.debug(f'Creating new GitHub user {github_user["name"]} - ' + cla.log.debug(f'{fn} - Could not find GitHub user by email: {emails}') + cla.log.debug(f'{fn} - Creating new GitHub user {github_user["name"]} - ' f'({github_user["id"]}/{github_user["login"]}), ' f'emails: {emails}') user = cla.utils.get_user_instance() @@ -577,7 +579,7 @@ def get_user_data(self, session, client_id): # pylint: disable=no-self-use return {'error': 'Could not get user data: %s' % github_user['message']} return github_user - def get_user_emails(self, session, client_id) -> Union[List[str], dict]: # pylint: disable=no-self-use + def get_user_emails(self, session: dict, client_id: str) -> Union[List[str], dict]: # pylint: disable=no-self-use """ Mockable method to get all user emails based on OAuth2 session. @@ -609,7 +611,7 @@ def get_primary_user_email(self, request) -> Union[Optional[str], dict]: """ fn = 'github_models.get_primary_user_email' try: - cla.log.debug(f'{fn} - Fetching Github primary email') + cla.log.debug(f'{fn} - fetching Github primary email') session = self._get_request_session(request) client_id = os.environ['GH_OAUTH_CLIENT_ID'] emails = self._fetch_github_emails(session=session, client_id=client_id) @@ -624,7 +626,7 @@ def get_primary_user_email(self, request) -> Union[Optional[str], dict]: return None return None - def _fetch_github_emails(self, session, client_id) -> Union[List[dict], dict]: + def _fetch_github_emails(self, session: dict, client_id: str) -> Union[List[dict], dict]: """ Method is responsible for fetching the user emails from /user/emails endpoint :param session: @@ -1181,7 +1183,7 @@ def _get_authorization_url_and_state(self, client_id, redirect_uri, scope, autho def _fetch_token(self, client_id, state, token_url, client_secret, code): # pylint: disable=too-many-arguments return 'random-token' - def _get_request_session(self, request): + def _get_request_session(self, request) -> dict: if self.oauth2_token: return {'github_oauth2_token': 'random-token', 'github_oauth2_state': 'random-state', @@ -1189,7 +1191,7 @@ def _get_request_session(self, request): 'github_installation_id': 1} return {} - def get_user_data(self, session, client_id): + def get_user_data(self, session, client_id) -> dict: return {'email': 'test@user.com', 'name': 'Test User', 'id': 123} def get_user_emails(self, session, client_id): From 5f181b33300d052b813a6be6e81ec6a253c0a3dd Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 7 Sep 2021 13:59:00 -0700 Subject: [PATCH 0500/1276] Resolved Additional GitHub Session Issues (#3253) --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 656e8efca..e82ce8be0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1029,7 +1029,7 @@ workflows: only: - main # Deploy v2 Dev - - deployV1BackendDev: + - deployV2BackendDev: requires: - buildGoBackendCommon filters: @@ -1078,8 +1078,8 @@ workflows: - main - functionalTestsTavernDev: requires: - - deployv1BackendDev - - deployv2BackendDev + - deployV1BackendDev + - deployV2BackendDev filters: tags: ignore: /.*/ From a69e97fbced9d7ac718acf1b91b1575ae259541b Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 7 Sep 2021 14:02:00 -0700 Subject: [PATCH 0501/1276] Resolved CI/CD Deployment Config Issue (#3254) --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e82ce8be0..273991bca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1022,6 +1022,7 @@ workflows: - deployV1BackendDev: requires: - buildBackendDev + - buildGoBackendCommon filters: tags: ignore: /.*/ @@ -1110,6 +1111,7 @@ workflows: requires: - approve_staging - buildBackendStaging + - buildGoBackendCommon filters: branches: ignore: /.*/ @@ -1186,6 +1188,7 @@ workflows: requires: - approve_prod - buildBackendProd + - buildGoBackendCommon filters: branches: ignore: /.*/ From ec564d7d15ddda37322785c24bfda8e530afe1ef Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 7 Sep 2021 15:33:01 -0700 Subject: [PATCH 0502/1276] Resolved CI/CD Deployment Config Issue (#3255) --- cla-backend/cla/models/github_models.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index cf1ea7eba..28047b12f 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -132,13 +132,15 @@ def _get_request_session(self, request) -> dict: # pylint: disable=no-self-use session = request.context.get('session') if session is None: cla.log.warning(f'Session is empty for request: {request}') - cla.log.debug(f'loaded session: {session}') + cla.log.debug(f'{fn} - loaded session: {session}') # Ensure session is a dict - getting issue where session is a string if isinstance(session, str): # convert string to a dict cla.log.debug(f'{fn} - session is type: {type(session)} - converting to dict...') session = json.loads(session) + # Reset the session now that we have converted it to a dict + request.context['session'] = session cla.log.debug(f'{fn} - session: {session} which is now type: {type(session)}...') return session @@ -167,7 +169,7 @@ def get_authorization_url_and_state(self, installation_id, github_repository_id, github_oauth_client_id = os.environ['GH_OAUTH_CLIENT_ID'] cla.log.debug(f'{fn} - Directing user to the github authorization url: {github_oauth_url} via ' - f'our github installation flow: {redirect_uri}' + f'our github installation flow: {redirect_uri} ' f'using the github oauth client id: {github_oauth_client_id[0:5]} ' f'with scope: {scope}') @@ -569,13 +571,18 @@ def get_user_data(self, session, client_id): # pylint: disable=no-self-use :param client_id: The GitHub OAuth2 client ID. :type client_id: string """ - token = session['github_oauth2_token'] + fn = 'cla.models.github_models.get_user_data' + token = session.get('github_oauth2_token') + if token is None: + cla.log.error(f'{fn} - unable to load github_oauth2_token from session, session is: {session}') + return {'error': 'could not get user data from session'} + oauth2 = OAuth2Session(client_id, token=token) request = oauth2.get('https://api.github.com/user') github_user = request.json() - cla.log.debug('GitHub user data: %s', github_user) + cla.log.debug(f'{fn} - GitHub user data: %s', github_user) if 'message' in github_user: - cla.log.error('Could not get user data with OAuth2 token: %s', github_user['message']) + cla.log.error(f'{fn} - Could not get user data with OAuth2 token: {github_user["message"]}') return {'error': 'Could not get user data: %s' % github_user['message']} return github_user From e38f0aabfec0012de657b3b43235c149c6d0014a Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 8 Sep 2021 13:09:36 +0300 Subject: [PATCH 0503/1276] [$2344] Bug/Gitlab Group ECLA check - Resolved bug for gitlab org approval list check - Updated staging in serverless config Signed-off-by: Harold Wanyama --- cla-backend/cla/models/dynamo_models.py | 8 ++++---- cla-backend/serverless.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 412f6e735..f57d35ead 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -3744,7 +3744,7 @@ class Meta: auth_info = UnicodeAttribute() organization_sfid_index = GitlabOrgSFIndex() project_sfid_organization_name_index = GitlabOrgProjectSfidOrganizationNameIndex() - organization_name_lowe_index = GitlabOrganizationNameLowerIndex() + organization_name_lower_index = GitlabOrganizationNameLowerIndex() gitlab_external_group_id_index = GitlabExternalGroupIDIndex() auto_enabled = BooleanAttribute(null=True) auto_enabled_cla_group_id = UnicodeAttribute(null=True) @@ -4077,14 +4077,14 @@ def get_organization_by_sfid(self, sfid) -> List: return organizations def search_organization_by_lower_name(self, organization_name): - organizations = list(filter(lambda org: org.get_organization_name_lower() == organization_name, self.all())) + organizations = list(filter(lambda org: org.get_organization_name_lower() == organization_name.lower(), self.all())) if organizations: return organizations[0] - raise cla.models.DoesNotExist("Gitlab Org does not exist") + raise cla.models.DoesNotExist(f"Gitlab Org : {organization_name} does not exist") def get_organization_by_lower_name(self, organization_name): organization_name = organization_name.lower() - organization_generator = self.model.organization_name_lowe_index.query(organization_name) + organization_generator = self.model.organization_name_lower_index.query(organization_name) organizations = [] for org_model in organization_generator: org = GitlabOrg() diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 5861962d4..289e3621f 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -102,7 +102,7 @@ custom: provider: name: aws runtime: python3.7 - stage: prod + stage: ${env:STAGE} region: us-east-1 timeout: 60 # optional, in seconds, default is 6 logRetentionInDays: 14 From 9e4e04b752064c48e56a2b194ace5df8540a98a1 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 8 Sep 2021 16:23:37 +0200 Subject: [PATCH 0504/1276] [Snyk] Upgrade auth0-js from 9.14.0 to 9.16.0 (#2966) --- cla-landing-page/package.json | 2 +- cla-landing-page/yarn.lock | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 258ac2fbf..803b3459f 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -51,7 +51,7 @@ "@types/auth0-js": "^8.11.7", "@types/node": "^12.11.1", "@ng-bootstrap/ng-bootstrap": "^6.1.0", - "auth0-js": "^9.13.2", + "auth0-js": "^9.16.0", "aws-sdk": "^2.885.0", "bootstrap": "^4.4.0", "query-string": "^6.13.6", diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 5f7bbf533..0f5e1d3eb 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -2421,13 +2421,13 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -auth0-js@^9.13.2: - version "9.14.0" - resolved "https://registry.yarnpkg.com/auth0-js/-/auth0-js-9.14.0.tgz#8401095a99bc53756425f9e52ed5220b4be4b369" - integrity sha512-40gIBUejmYAYse06ck6sxdNO0KU0pX+KDIQsWAkcyFtI0HU6dY5aeHxZfVYkYjtbArKr5s13LuZFdKrUiGyCqQ== +auth0-js@^9.16.0: + version "9.16.2" + resolved "https://registry.yarnpkg.com/auth0-js/-/auth0-js-9.16.2.tgz#7c4ca32add3d8f7419ce33deb8f4839179606779" + integrity sha512-cF1nRjmMDezmhJ+ZwwYp23F0gPqU0zNmF/VvTpcwvCrEMl9lAvkCd4iburN1I7G8SYaaIYEfcGedCphpDZw6OQ== dependencies: base64-js "^1.3.0" - idtoken-verifier "^2.0.3" + idtoken-verifier "^2.1.2" js-cookie "^2.2.0" qs "^6.7.0" superagent "^5.3.1" @@ -3887,7 +3887,7 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@^3.2.1: +crypto-js@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== @@ -6217,13 +6217,13 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" -idtoken-verifier@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-2.1.0.tgz#e61ea083be596390012aff6d9f12c2599af4847b" - integrity sha512-X0423UM4Rc5bFb39Ai0YHr35rcexlu4oakKdYzSGZxtoPy84P86hhAbzlpgbgomcLOFRgzgKRvhY7YjO5g8OPA== +idtoken-verifier@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-2.1.2.tgz#185ec29b70400b47a1d48b068e7b95d1bbf7dcef" + integrity sha512-YMHiP9zAMjB+pWreV4EHnIj3XCQ168+InWirVRFeRtlsMQIK61S+LLnyLGI8EL0wtlk/v7ya69Gjfio3P9/7Gw== dependencies: base64-js "^1.3.0" - crypto-js "^3.2.1" + crypto-js "3.3.0" es6-promise "^4.2.8" jsbn "^1.1.0" unfetch "^4.1.0" From ac6d7d3a6acfa7b0552625223972171d8099c36f Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 8 Sep 2021 08:39:18 -0700 Subject: [PATCH 0505/1276] Bug/ICLA email (#3258) --- cla-backend/cla/controllers/signing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py index 4575e66e4..dfe0e2951 100644 --- a/cla-backend/cla/controllers/signing.py +++ b/cla-backend/cla/controllers/signing.py @@ -37,7 +37,7 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur if return_url_type is not None and return_url_type.lower() == "gerrit": return signing_service.request_individual_signature_gerrit(str(project_id), str(user_id), return_url) elif return_url_type is not None and (return_url_type.lower() == "github" or return_url_type.lower() == "gitlab"): - if return_url_type == "github": + if return_url_type.lower() == "github": # fetching the primary for the account github = get_repository_service("github") primary_user_email = github.get_primary_user_email(request) From b996dfd1b22bb0fc0ba7899198f4c5b5b2824e90 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 8 Sep 2021 11:03:34 -0700 Subject: [PATCH 0506/1276] Updated Activity Log Details for GitLab (#3260) --- cla-backend-go/events/event_data.go | 110 +++++++++--------- cla-backend-go/signatures/service.go | 16 +-- .../v2/gitlab_organizations/handlers.go | 10 +- 3 files changed, 68 insertions(+), 68 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 6ef709283..70dd7d429 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -198,22 +198,22 @@ type GitHubOrganizationUpdatedEventData struct { BranchProtectionEnabled bool } -// GitlabOrganizationAddedEventData data model -type GitlabOrganizationAddedEventData struct { - GitlabOrganizationName string +// GitLabOrganizationAddedEventData data model +type GitLabOrganizationAddedEventData struct { + GitLabOrganizationName string AutoEnabled bool AutoEnabledClaGroupID string BranchProtectionEnabled bool } -// GitlabOrganizationDeletedEventData data model -type GitlabOrganizationDeletedEventData struct { - GitlabOrganizationName string +// GitLabOrganizationDeletedEventData data model +type GitLabOrganizationDeletedEventData struct { + GitLabOrganizationName string } -// GitlabOrganizationUpdatedEventData data model -type GitlabOrganizationUpdatedEventData struct { - GitlabOrganizationName string +// GitLabOrganizationUpdatedEventData data model +type GitLabOrganizationUpdatedEventData struct { + GitLabOrganizationName string GitLabGroupID int64 AutoEnabled bool AutoEnabledClaGroupID string @@ -335,24 +335,24 @@ type CLAApprovalListRemoveGitHubOrgData struct { ApprovalListGitHubOrg string } -// CLAApprovalListAddGitlabUsernameData data model -type CLAApprovalListAddGitlabUsernameData struct { - ApprovalListGitlabUsername string +// CLAApprovalListAddGitLabUsernameData data model +type CLAApprovalListAddGitLabUsernameData struct { + ApprovalListGitLabUsername string } -// CLAApprovalListRemoveGitlabUsernameData data model -type CLAApprovalListRemoveGitlabUsernameData struct { - ApprovalListGitlabUsername string +// CLAApprovalListRemoveGitLabUsernameData data model +type CLAApprovalListRemoveGitLabUsernameData struct { + ApprovalListGitLabUsername string } -// CLAApprovalListAddGitlabOrgData data model -type CLAApprovalListAddGitlabOrgData struct { - ApprovalListGitlabOrg string +// CLAApprovalListAddGitLabGroupData data model +type CLAApprovalListAddGitLabGroupData struct { + ApprovalListGitLabGroup string } -// CLAApprovalListRemoveGitlabOrgData data model -type CLAApprovalListRemoveGitlabOrgData struct { - ApprovalListGitlabOrg string +// CLAApprovalListRemoveGitLabGroupData data model +type CLAApprovalListRemoveGitLabGroupData struct { + ApprovalListGitLabGroup string } // ApprovalListGitHubOrganizationAddedEventData data model @@ -737,9 +737,9 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventDetailsString(args *LogEve } // GetEventDetailsString returns the details string for this event -func (ed *GitlabOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("Gitlab Organization: %s was added with auto-enabled: %t, with branch protection enabled: %t", - ed.GitlabOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) +func (ed *GitLabOrganizationAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("GitLab Group: %s was added with auto-enabled: %t, with branch protection enabled: %t", + ed.GitLabOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group: %s", ed.AutoEnabledClaGroupID) } @@ -751,8 +751,8 @@ func (ed *GitlabOrganizationAddedEventData) GetEventDetailsString(args *LogEvent } // GetEventDetailsString returns the details string for this event -func (ed *GitlabOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("GitHub Organization: %s was deleted ", ed.GitlabOrganizationName) +func (ed *GitLabOrganizationDeletedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("GitLab Group: %s was deleted ", ed.GitLabOrganizationName) if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -761,10 +761,10 @@ func (ed *GitlabOrganizationDeletedEventData) GetEventDetailsString(args *LogEve } // GetEventDetailsString returns the details string for this event -func (ed *GitlabOrganizationUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := "GitHub Organization" // nolint - if ed.GitlabOrganizationName != "" { - data = fmt.Sprintf("%s with name: %s", data, ed.GitlabOrganizationName) +func (ed *GitLabOrganizationUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := "GitLab Group" // nolint + if ed.GitLabOrganizationName != "" { + data = fmt.Sprintf("%s with name: %s", data, ed.GitLabOrganizationName) } if ed.GitLabGroupID > 0 { data = fmt.Sprintf("%s with group ID: %d", data, ed.GitLabGroupID) @@ -1065,8 +1065,8 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventDetailsString(args *LogEve } // GetEventDetailsString returns the details string for this event -func (ed *CLAApprovalListAddGitlabUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab username %s was added to the approval list", ed.ApprovalListGitlabUsername) +func (ed *CLAApprovalListAddGitLabUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab username %s was added to the approval list", ed.ApprovalListGitLabUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1087,8 +1087,8 @@ func (ed *CLAApprovalListAddGitlabUsernameData) GetEventDetailsString(args *LogE } // GetEventDetailsString returns the details string for this event -func (ed *CLAApprovalListRemoveGitlabUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab username %s was removed from the approval list", ed.ApprovalListGitlabUsername) +func (ed *CLAApprovalListRemoveGitLabUsernameData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab username %s was removed from the approval list", ed.ApprovalListGitLabUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1109,8 +1109,8 @@ func (ed *CLAApprovalListRemoveGitlabUsernameData) GetEventDetailsString(args *L } // GetEventDetailsString returns the details string for this event -func (ed *CLAApprovalListAddGitlabOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab organization %s was added to the approval list", ed.ApprovalListGitlabOrg) +func (ed *CLAApprovalListAddGitLabGroupData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab group %s was added to the approval list", ed.ApprovalListGitLabGroup) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1131,8 +1131,8 @@ func (ed *CLAApprovalListAddGitlabOrgData) GetEventDetailsString(args *LogEventA } // GetEventDetailsString returns the details string for this event -func (ed *CLAApprovalListRemoveGitlabOrgData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab organization %s was removed from the approval list", ed.ApprovalListGitlabOrg) +func (ed *CLAApprovalListRemoveGitLabGroupData) GetEventDetailsString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab group %s was removed from the approval list", ed.ApprovalListGitLabGroup) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1814,9 +1814,9 @@ func (ed *GitHubOrganizationUpdatedEventData) GetEventSummaryString(args *LogEve } // GetEventSummaryString returns the summary string for this event -func (ed *GitlabOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab organization %s was added with auto-enabled set to %t with branch protection enabled set to %t", - ed.GitlabOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) +func (ed *GitLabOrganizationAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab group %s was added with auto-enabled set to %t with branch protection enabled set to %t", + ed.GitLabOrganizationName, ed.AutoEnabled, ed.BranchProtectionEnabled) if ed.AutoEnabledClaGroupID != "" { data = data + fmt.Sprintf(" with auto-enabled-cla-group set to %s", ed.AutoEnabledClaGroupID) } @@ -1834,8 +1834,8 @@ func (ed *GitlabOrganizationAddedEventData) GetEventSummaryString(args *LogEvent } // GetEventSummaryString returns the summary string for this event -func (ed *GitlabOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab organization %s was deleted", ed.GitlabOrganizationName) +func (ed *GitLabOrganizationDeletedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab group %s was deleted", ed.GitLabOrganizationName) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for CLA Group %s", args.CLAGroupName) } @@ -1850,10 +1850,10 @@ func (ed *GitlabOrganizationDeletedEventData) GetEventSummaryString(args *LogEve } // GetEventSummaryString returns the summary string for this event -func (ed *GitlabOrganizationUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := "GitHub Organization" // nolint - if ed.GitlabOrganizationName != "" { - data = fmt.Sprintf("%s with name: %s", data, ed.GitlabOrganizationName) +func (ed *GitLabOrganizationUpdatedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := "The GitLab group" // nolint + if ed.GitLabOrganizationName != "" { + data = fmt.Sprintf("%s with name: %s", data, ed.GitLabOrganizationName) } if ed.GitLabGroupID > 0 { data = fmt.Sprintf("%s with group ID: %d", data, ed.GitLabGroupID) @@ -2165,8 +2165,8 @@ func (ed *CLAApprovalListRemoveGitHubOrgData) GetEventSummaryString(args *LogEve } // GetEventSummaryString returns the summary string for this event -func (ed *CLAApprovalListAddGitlabUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab username %s was added to the approval list", ed.ApprovalListGitlabUsername) +func (ed *CLAApprovalListAddGitLabUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab username %s was added to the approval list", ed.ApprovalListGitLabUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -2184,8 +2184,8 @@ func (ed *CLAApprovalListAddGitlabUsernameData) GetEventSummaryString(args *LogE } // GetEventSummaryString returns the summary string for this event -func (ed *CLAApprovalListRemoveGitlabUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab username %s was removed from the approval list", ed.ApprovalListGitlabUsername) +func (ed *CLAApprovalListRemoveGitLabUsernameData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab username %s was removed from the approval list", ed.ApprovalListGitLabUsername) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -2203,8 +2203,8 @@ func (ed *CLAApprovalListRemoveGitlabUsernameData) GetEventSummaryString(args *L } // GetEventSummaryString returns the summary string for this event -func (ed *CLAApprovalListAddGitlabOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab organization %s was added to the approval list", ed.ApprovalListGitlabOrg) +func (ed *CLAApprovalListAddGitLabGroupData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab group %s was added to the approval list", ed.ApprovalListGitLabGroup) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -2222,8 +2222,8 @@ func (ed *CLAApprovalListAddGitlabOrgData) GetEventSummaryString(args *LogEventA } // GetEventSummaryString returns the summary string for this event -func (ed *CLAApprovalListRemoveGitlabOrgData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The Gitlab organization %s was removed from the approval list", ed.ApprovalListGitlabOrg) +func (ed *CLAApprovalListRemoveGitLabGroupData) GetEventSummaryString(args *LogEventArgs) (string, bool) { + data := fmt.Sprintf("The GitLab group %s was removed from the approval list", ed.ApprovalListGitLabGroup) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index b78bf2b6e..8daeea497 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -845,8 +845,8 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserID: userModel.UserID, UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, - EventData: &events.CLAApprovalListAddGitlabUsernameData{ - ApprovalListGitlabUsername: value, + EventData: &events.CLAApprovalListAddGitLabUsernameData{ + ApprovalListGitLabUsername: value, }, }) } @@ -862,8 +862,8 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserID: userModel.UserID, UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, - EventData: &events.CLAApprovalListRemoveGitlabUsernameData{ - ApprovalListGitlabUsername: value, + EventData: &events.CLAApprovalListRemoveGitLabUsernameData{ + ApprovalListGitLabUsername: value, }, }) } @@ -879,8 +879,8 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserID: userModel.UserID, UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, - EventData: &events.CLAApprovalListAddGitlabOrgData{ - ApprovalListGitlabOrg: value, + EventData: &events.CLAApprovalListAddGitLabGroupData{ + ApprovalListGitLabGroup: value, }, }) } @@ -897,8 +897,8 @@ func (s service) createEventLogEntries(ctx context.Context, companyModel *models UserID: userModel.UserID, UserModel: userModel, ProjectSFID: claGroupModel.ProjectExternalID, - EventData: &events.CLAApprovalListRemoveGitlabOrgData{ - ApprovalListGitlabOrg: value, + EventData: &events.CLAApprovalListRemoveGitLabGroupData{ + ApprovalListGitLabGroup: value, }, }) } diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 55fa5cd69..84ce7823c 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -218,8 +218,8 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic LfUsername: authUser.UserName, EventType: events.GitlabOrganizationAdded, ProjectSFID: params.ProjectSFID, - EventData: &events.GitlabOrganizationAddedEventData{ - GitlabOrganizationName: group.OrganizationName, + EventData: &events.GitLabOrganizationAddedEventData{ + GitLabOrganizationName: group.OrganizationName, }, }) } @@ -317,7 +317,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic CLAGroupID: params.Body.AutoEnabledClaGroupID, LfUsername: authUser.UserName, UserName: authUser.UserName, - EventData: &events.GitlabOrganizationUpdatedEventData{ + EventData: &events.GitLabOrganizationUpdatedEventData{ GitLabGroupID: params.GitLabGroupID, AutoEnabledClaGroupID: params.Body.AutoEnabledClaGroupID, AutoEnabled: params.Body.AutoEnabled, @@ -384,8 +384,8 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic LfUsername: authUser.UserName, EventType: events.GitlabOrganizationDeleted, ProjectSFID: params.ProjectSFID, - EventData: &events.GitlabOrganizationDeletedEventData{ - GitlabOrganizationName: params.OrganizationFullPath, + EventData: &events.GitLabOrganizationDeletedEventData{ + GitLabOrganizationName: params.OrganizationFullPath, }, }) From a04c0b4c5aec553668afd93b4a6d393ddf0c08ff Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 8 Sep 2021 12:51:13 -0700 Subject: [PATCH 0507/1276] [#2344] Bug/Company Affiliation (#3261) --- cla-backend/cla/models/docusign_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index ba593ec63..21fe4c6a8 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -748,8 +748,8 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url elif return_url_type.lower() == "gitlab": gitlab_repository_id = signature_metadata['repository_id'] - merge_request_id = signature_metadata['merge_request_id'] - organization_id = cla.utils.get_organization_id_from_gitlab_repository(gitlab_repository_id) + merge_request_id = int(signature_metadata['merge_request_id']) + organization_id = int(cla.utils.get_organization_id_from_gitlab_repository(gitlab_repository_id)) self._update_gitlab_mr(organization_id, gitlab_repository_id, merge_request_id) if organization_id is None: From 62c320012f39ed9cdefdd07aa5795e5ce880dd79 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Thu, 9 Sep 2021 13:56:06 -0700 Subject: [PATCH 0508/1276] Feature/GitLab URL criteria (#3264) --- cla-backend/cla/models/dynamo_models.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index f57d35ead..19fad4826 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2117,14 +2117,14 @@ def is_approved(self, ccla_signature: Signature) -> bool: if gitlab_org_approval_lists: for gl_name in gitlab_org_approval_lists: try: - gl_org = GitlabOrg().search_organization_by_lower_name(gl_name) - cla.log.debug(f"{fn} checking gitlab_username against approval list for company: {gl_org.get_organization_name()}") + gl_org = GitlabOrg().search_organization_by_group_url(gl_name) + cla.log.debug(f"{fn} checking gitlab_username against approval list for gitlab group: {gl_name}") gl_list = list(filter(lambda gl_user: gl_user.get('username') == gitlab_username, cla.utils.lookup_gitlab_org_members(gl_org.get_organization_id()))) if len(gl_list) > 0: cla.log.debug(f'{fn} - found gitlab username in gitlab approval list') return True except DoesNotExist as err: - cla.log.debug(f'gitlab group : {gl_name} does not exist: {err}') + cla.log.debug(f'gitlab group with full path: {gl_name} does not exist: {err}') cla.log.debug(f'{fn} - unable to find user in any whitelist') @@ -3738,6 +3738,7 @@ class Meta: organization_id = UnicodeAttribute(hash_key=True) organization_name = UnicodeAttribute(null=True) + organization_url = UnicodeAttribute(null=True) organization_name_lower = UnicodeAttribute(null=True) organization_sfid = UnicodeAttribute() project_sfid = UnicodeAttribute() @@ -3967,6 +3968,7 @@ def __str__(self): return ( f'organization id:{self.model.organization_id}, ' f'organization name:{self.model.organization_name}, ' + f'organization url : {self.model.organization_url}, ' f'organization SFID: {self.model.organization_sfid}, ' f'auto_enabled: {self.model.auto_enabled},' f'branch_protection_enabled: {self.model.branch_protection_enabled},' @@ -3997,6 +3999,9 @@ def delete(self): def get_organization_id(self): return self.model.organization_id + + def get_organization_url(self): + return self.model.organization_url def get_organization_name(self): return self.model.organization_name @@ -4034,6 +4039,9 @@ def set_organization_name(self, organization_name): self.model.organization_name = organization_name if self.model.organization_name: self.model.organization_name_lower = self.model.organization_name.lower() + + def set_organization_url(self, organization_url): + self.model.organization_url = organization_url def set_organization_sfid(self, organization_sfid): self.model.organization_sfid = organization_sfid @@ -4081,6 +4089,12 @@ def search_organization_by_lower_name(self, organization_name): if organizations: return organizations[0] raise cla.models.DoesNotExist(f"Gitlab Org : {organization_name} does not exist") + + def search_organization_by_group_url(self, group_url): + organizations = list(filter(lambda org: org.get_organization_url() == group_url.strip(), self.all())) + if organizations: + return organizations[0] + raise cla.models.DoesNotExist(f"Gitlab Org : {group_url} does not exist") def get_organization_by_lower_name(self, organization_name): organization_name = organization_name.lower() From 84754ed5efcf49a27a8f6fe686b85ab03d2e2b06 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 10 Sep 2021 15:52:39 +0300 Subject: [PATCH 0509/1276] Feature/Gitlab URL - Edited regex for gitlab org approval list update to handle gitlab.com domain Signed-off-by: Harold Wanyama --- cla-backend-go/utils/validators.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/utils/validators.go b/cla-backend-go/utils/validators.go index a8a42312c..d43334780 100644 --- a/cla-backend-go/utils/validators.go +++ b/cla-backend-go/utils/validators.go @@ -145,7 +145,7 @@ func ValidGitlabOrg(gitlabOrg string) (string, bool) { return "gitlab organization must be 3 or more characters", false } - re := regexp.MustCompile("^[a-zA-Z0-9._-]*$") + re := regexp.MustCompile(`^(?:http(s)?:\/\/)?(?:www\.)?(\w+[\w-]+\w+\.)?gitlab\.com[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]{3,100}$`) valid := re.MatchString(strings.TrimSpace(gitlabOrg)) if !valid { return fmt.Sprintf("invalid Gitlab organization: %s", gitlabOrg), false From 1a64961486b21fe4665bbec36ed40ba080948382 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 13 Sep 2021 13:23:10 +0300 Subject: [PATCH 0510/1276] [#2344,#2371] Gitlab URL Criteria - Resolved search for gitlab url with cases with missing 'groups' in url Signed-off-by: Harold Wanyama --- cla-backend/cla/models/dynamo_models.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 19fad4826..7aa174a9c 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -4091,9 +4091,26 @@ def search_organization_by_lower_name(self, organization_name): raise cla.models.DoesNotExist(f"Gitlab Org : {organization_name} does not exist") def search_organization_by_group_url(self, group_url): - organizations = list(filter(lambda org: org.get_organization_url() == group_url.strip(), self.all())) + # first check for match.. could be in the format https://gitlab.com/groups/ + groups = self.all() + organizations = list(filter(lambda org: org.get_organization_url() == group_url.strip(), groups)) if organizations: return organizations[0] + # also cater for potentially missing groups in url + pattern = re.compile(r"(?P\bhttps://gitlab.com/\b)(?P\bgroups\/\b)?(?P\w+)") + match = pattern.search(group_url) + updated_url = '' + if match and not match.group('group') : + cla.log.debug(f'{group_url} missing groups in url. Inserting groups to url ') + parse_url_list = list(match.groups()) + parse_url_list[1] = 'groups/' + updated_url = ''.join(parse_url_list) + if updated_url: + cla.log.debug(f'Updated group_url to : {updated_url}') + organizations = list(filter(lambda org: org.get_organization_url() == updated_url.strip(), groups)) + if organizations: + return organizations[0] + raise cla.models.DoesNotExist(f"Gitlab Org : {group_url} does not exist") def get_organization_by_lower_name(self, organization_name): From 3b7cadfca985d07b3492b89ecaaaa498a5526209 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 13 Sep 2021 17:31:00 +0300 Subject: [PATCH 0511/1276] [#3244,#3271] Bug/Gitlab Employee ack - Resolved issue raised on MR update for new employee flow Signed-off-by: Harold Wanyama --- cla-backend/cla/models/docusign_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 21fe4c6a8..8764a42c4 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -747,9 +747,9 @@ def request_employee_signature(self, project_id, company_id, user_id, return_url update_repository_provider(installation_id, github_repository_id, change_request_id) elif return_url_type.lower() == "gitlab": - gitlab_repository_id = signature_metadata['repository_id'] + gitlab_repository_id = int(signature_metadata['repository_id']) merge_request_id = int(signature_metadata['merge_request_id']) - organization_id = int(cla.utils.get_organization_id_from_gitlab_repository(gitlab_repository_id)) + organization_id = cla.utils.get_organization_id_from_gitlab_repository(gitlab_repository_id) self._update_gitlab_mr(organization_id, gitlab_repository_id, merge_request_id) if organization_id is None: From 3e883c18c2f3421c2fabba5beb97a23325b8aa88 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 13 Sep 2021 19:59:27 +0300 Subject: [PATCH 0512/1276] Bug/ Gitlan Org search - Refactored gitlab org id finding that previously returned easycla ID rather than gitlab Signed-off-by: Harold Wanyama --- cla-backend/cla/models/dynamo_models.py | 8 ++++++++ cla-backend/cla/utils.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 19fad4826..a2be139af 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -3741,6 +3741,7 @@ class Meta: organization_url = UnicodeAttribute(null=True) organization_name_lower = UnicodeAttribute(null=True) organization_sfid = UnicodeAttribute() + external_gitlab_group_id = NumberAttribute() project_sfid = UnicodeAttribute() auth_info = UnicodeAttribute() organization_sfid_index = GitlabOrgSFIndex() @@ -3975,6 +3976,7 @@ def __str__(self): f'enabled: {self.model.enabled},' f'note: {self.model.note}', f'auth_info: {self.model.auth_info}' + f'external_gitlab_group_id: {self.model.external_gitlab_group_id}' ) def to_dict(self): @@ -3996,6 +3998,9 @@ def load(self, organization_id: str): def delete(self): self.model.delete() + + def get_external_gitlab_group_id(self): + return self.model.external_gitlab_group_id def get_organization_id(self): return self.model.organization_id @@ -4034,6 +4039,9 @@ def get_auth_info(self): def get_enabled(self): return self.model.enabled + + def set_external_gitlab_group_id(self, external_gitlab_group_id): + self.model.external_gitlab_group_id = external_gitlab_group_id def set_organization_name(self, organization_name): self.model.organization_name = organization_name diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index dd2251e4f..c44ef63b9 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1254,7 +1254,7 @@ def get_organization_id_from_gitlab_repository(gitlab_repository_id): return None #return GitLab organization ID - return gitLabOrg.get_organization_id() + return str(gitLabOrg.get_external_gitlab_group_id()) def get_project_id_from_github_repository(github_repository_id): From 1d957bf40e4337014a28322c92843d533d89950c Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 13 Sep 2021 12:00:01 -0700 Subject: [PATCH 0513/1276] Updated Organization Service Swagger Client (#3275) --- .../v2/organization-service/client.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index 967de77a7..8fde244e5 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -578,25 +578,13 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName } clientAuth := runtimeClient.BearerToken(tok) - description := "No Description" - f["description"] = description - companyType := "Customer" - f["type"] = companyType - f["companyType"] = companyType - companySource := "No Source" - industry := "No Industry" - f["industry"] = industry logoURL := linuxFoundation[0].LogoURL f["logoURL"] = logoURL params := &organizations.CreateOrgParams{ Org: &models.CreateOrg{ - Description: &description, Name: &companyName, Website: &companyWebsite, - Industry: &industry, - Source: &companySource, - Type: &companyType, LogoURL: logoURL, SigningEntityName: []string{signingEntityName}, }, @@ -604,18 +592,14 @@ func (osc *Client) CreateOrg(ctx context.Context, companyName, signingEntityName } log.WithFields(f).Debugf("Creating organization with params: %+v", models.CreateOrg{ - Description: &description, Name: &companyName, Website: &companyWebsite, - Industry: &industry, - Source: &companySource, - Type: &companyType, LogoURL: logoURL, SigningEntityName: []string{signingEntityName}, }) result, err := osc.cl.Organizations.CreateOrg(params, clientAuth) if err != nil { - log.WithFields(f).WithError(err).Warnf("Failed to create salesforce Company :%s , err: %+v ", companyName, err) + log.WithFields(f).WithError(err).Warnf("Failed to create salesforce Company: %s , err: %+v ", companyName, err) return nil, err } log.WithFields(f).Infof("Company: %s successfuly created ", companyName) From 24f2f6e4c5d2db55d22f6ccc4983b2c629f311c7 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 14 Sep 2021 11:10:48 +0300 Subject: [PATCH 0514/1276] [#3271] Update MR - Resolved update MR for ecla flow Signed-off-by: Harold Wanyama --- cla-backend/cla/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index c44ef63b9..dd2251e4f 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1254,7 +1254,7 @@ def get_organization_id_from_gitlab_repository(gitlab_repository_id): return None #return GitLab organization ID - return str(gitLabOrg.get_external_gitlab_group_id()) + return gitLabOrg.get_organization_id() def get_project_id_from_github_repository(github_repository_id): From fc3dc614f40b52f68e71403851d808da38447912 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 14 Sep 2021 15:48:10 +0300 Subject: [PATCH 0515/1276] [#2371] Feature/GitLab URL approval criteria - Updated MR trigger for handling gitlab org url approval criteria checks Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab-activity/service.go | 81 ++++++++++++++++++- .../v2/gitlab_organizations/repository.go | 51 ++++++++++++ .../v2/gitlab_organizations/service.go | 21 +++++ 3 files changed, 149 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 09b6a01f2..0a251141a 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -408,8 +408,7 @@ func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUse log.WithFields(f).Errorf(msg) return false, fmt.Errorf(msg) } - - if !IsUserApprovedForSignature(f, corporateSignature, userModel, gitlabUser) { + if !s.IsUserApprovedForSignature(ctx, f, corporateSignature, userModel, gitlabUser) { log.WithFields(f).Debugf("user is not approved in signature : %s", corporateSignature.SignatureID) return false, missingCompanyApproval } @@ -464,7 +463,7 @@ func (s service) findUserModelForGitlabUser(f logrus.Fields, gitlabUser *gitlab. return userModel, false, nil } -func IsUserApprovedForSignature(f logrus.Fields, corporateSignature *models.Signature, user *models.User, gitlabUser *gitlab.User) bool { +func (s service) IsUserApprovedForSignature(ctx context.Context, f logrus.Fields, corporateSignature *models.Signature, user *models.User, gitlabUser *gitlab.User) bool { log.WithFields(f).Debugf("checking if user : %s is approved for corporate signature : %s", user.UserID, corporateSignature.SignatureID) userEmails := user.Emails if string(user.LfEmail) != "" { @@ -523,8 +522,82 @@ func IsUserApprovedForSignature(f logrus.Fields, corporateSignature *models.Sign log.WithFields(f).Warnf("no match found for gitlabUser : %s in gitlab approval list : %+v", gitlabUserName, gitlabUsernameApprovalList) } - // todo check user against the gitlab org approval list + gitlabGroupApprovalList := corporateSignature.GitlabOrgApprovalList + if gitlabUserName != "" && len(gitlabGroupApprovalList) > 0 { + log.WithFields(f).Debugf("checking gitlab username : %s for gitlab org approval list : %+v ", gitlabUserName, gitlabGroupApprovalList) + + for _, gitlabGroupApproval := range gitlabGroupApprovalList { + isApproved, err := s.checkGitLabGroupApproval(ctx, gitlabUserName, gitlabGroupApproval) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to get username") + break + } + if isApproved == true { + log.WithFields(f).Debug(" found gitlab username : %s in gitlab org approval list : %+v", gitlabUserName, gitlabGroupApprovalList) + return true + } + } + } + log.WithFields(f).Errorf("unable to find user in any approval list") return false } + +/** + * Parses url with the given regular expression and returns the + * group values defined in the expression. + * + */ +func getParams(regEx, url string) (paramsMap map[string]string) { + + var compRegEx = regexp.MustCompile(regEx) + match := compRegEx.FindStringSubmatch(url) + + paramsMap = make(map[string]string) + for i, name := range compRegEx.SubexpNames() { + if i > 0 && i <= len(match) { + paramsMap[name] = match[i] + } + } + return paramsMap +} + +func (s service) checkGitLabGroupApproval(ctx context.Context, userName, URL string) (bool, error) { + f := logrus.Fields{ + "functionName": "checkGitLabGroupApproval", + "userName": userName, + "group_url": URL, + } + + log.WithFields(f).Debugf("checking approval list gitlab org criteria : %s for user: %s ", URL, userName) + var searchURL = URL + params := getParams(`(?P\bhttps://gitlab.com/\b)(?P\bgroups\/\b)?(?P\w+)`, URL) + if params[`group`] == "" { + params[`group`] = "groups/" + updated := fmt.Sprintf("%s%s%s", params[`base`], params[`group`], params[`name`]) + log.WithFields(f).Debugf("updating url : %s to %s for easycla search purporses ", searchURL, updated) + searchURL = updated + } + gitlabOrg, _ := s.gitlabRepository.GetGitLabOrganizationByURL(ctx, searchURL) + if gitlabOrg != nil { + gitlabClient, clientErr := gitlab_api.NewGitlabOauthClient(gitlabOrg.AuthInfo, s.gitLabApp) + if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warnf("problem getting gitLabClient for org: %s ", gitlabOrg.OrganizationName) + return false, clientErr + } + members, err := gitlab_api.ListGroupMembers(ctx, gitlabClient, int(gitlabOrg.ExternalGroupID)) + if err != nil { + log.WithFields(f).WithError(err).Warn("problem getting gitlab group members") + return false, err + } + for _, member := range members { + if userName == member.Username { + log.WithFields(f).Debugf("%s is a member of group: %s ", userName, URL) + return true, nil + } + } + } + + return false, nil +} diff --git a/cla-backend-go/v2/gitlab_organizations/repository.go b/cla-backend-go/v2/gitlab_organizations/repository.go index ca2aa9749..9d08b0637 100644 --- a/cla-backend-go/v2/gitlab_organizations/repository.go +++ b/cla-backend-go/v2/gitlab_organizations/repository.go @@ -38,6 +38,8 @@ const ( GitLabExternalIDIndex = "gitlab-external-group-id-index" // GitLabFullPathIndex the index for the full path GitLabFullPathIndex = "gitlab-full-path-index" + // GitlabOrgURLIndex the index for the org url + GitlabOrgURLIndex = "gitlab-org-url-index" ) // RepositoryInterface is interface for gitlab org data model @@ -51,6 +53,7 @@ type RepositoryInterface interface { GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*common.GitLabOrganization, error) GetGitLabOrganizationByExternalID(ctx context.Context, gitLabGroupID int64) (*common.GitLabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) + GetGitLabOrganizationByURL(ctx context.Context, url string) (*common.GitLabOrganization, error) UpdateGitLabOrganizationAuth(ctx context.Context, organizationID string, gitLabGroupID int, authInfo, groupName, groupFullPath, organizationURL string) error UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization, enabled bool) error DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID, gitlabOrgFullPath string) error @@ -377,6 +380,54 @@ func (repo *Repository) GetGitLabOrganizationByExternalID(ctx context.Context, g return resultOutput[0], nil } +// GetGitLabOrganizationByURL loads the organization based on the url +func (repo *Repository) GetGitLabOrganizationByURL(ctx context.Context, url string) (*common.GitLabOrganization, error) { + f := logrus.Fields{ + "functionName": "v1.gitlab_organizations.repository. GetGitLabOrganizationByURL", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "URL": url, + } + + condition := expression.Key(GitLabOrganizationsOrganizationURLColumn).Equal(expression.Value(url)) + builder := expression.NewBuilder().WithKeyCondition(condition) + // Use the nice builder to create the expression + expr, err := builder.Build() + if err != nil { + return nil, err + } + + // Assemble the query input parameters + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + ProjectionExpression: expr.Projection(), + FilterExpression: expr.Filter(), + TableName: aws.String(repo.gitlabOrgTableName), + IndexName: aws.String(GitlabOrgURLIndex), + } + + log.WithFields(f).Debugf("querying for GitLab group by url: %s...", url) + results, err := repo.dynamoDBClient.Query(queryInput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("error retrieving GitLab group by url: %s", url) + return nil, err + } + if len(results.Items) == 0 { + log.WithFields(f).Debugf("Unable to find GitLab group by url: %s - no results", url) + return nil, nil + } + + var resultOutput []*common.GitLabOrganization + err = dynamodbattribute.UnmarshalListOfMaps(results.Items, &resultOutput) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem decoding database results, error: %+v", err) + return nil, err + } + + return resultOutput[0], nil +} + // GetGitLabOrganizationByFullPath loads the organization based on the full path value func (repo *Repository) GetGitLabOrganizationByFullPath(ctx context.Context, groupFullPath string) (*common.GitLabOrganization, error) { f := logrus.Fields{ diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 0f37a564a..7a0e69188 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -39,6 +39,7 @@ type ServiceInterface interface { GetGitLabOrganizationByID(ctx context.Context, gitLabOrganizationID string) (*common.GitLabOrganization, error) GetGitLabOrganizationByName(ctx context.Context, gitLabOrganizationName string) (*v2Models.GitlabOrganization, error) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrganizationFullPath string) (*v2Models.GitlabOrganization, error) + GetGitLabOrganizationByURL(ctx context.Context, url string) (*v2Models.GitlabOrganization, error) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*v2Models.GitlabOrganization, error) GetGitLabOrganizations(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) GetGitLabOrganizationsEnabled(ctx context.Context) (*v2Models.GitlabProjectOrganizations, error) @@ -219,6 +220,26 @@ func (s *Service) GetGitLabOrganizationByFullPath(ctx context.Context, gitLabOrg return common.ToModel(dbModel), nil } +// GetGitLabOrganizationByURL returns the GitLab group/organization using the specified full path +func (s *Service) GetGitLabOrganizationByURL(ctx context.Context, URL string) (*v2Models.GitlabOrganization, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.GetGitLabOrganizationByURL", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabOrganizationFullPath": URL, + } + + log.WithFields(f).Debugf("fetching gitlab group/organization using url: %s", URL) + dbModel, err := s.repo.GetGitLabOrganizationByURL(ctx, URL) + if err != nil { + return nil, err + } + + if dbModel == nil { + return nil, nil + } + return common.ToModel(dbModel), nil +} + // GetGitLabOrganizationByGroupID returns the GitLab group/organization using the specified group ID func (s *Service) GetGitLabOrganizationByGroupID(ctx context.Context, gitLabGroupID int64) (*v2Models.GitlabOrganization, error) { f := logrus.Fields{ From 18dd7541ef684949d58a2acfda371c2b1cfce680 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 14 Sep 2021 16:44:08 +0300 Subject: [PATCH 0516/1276] Bug/Gitlab Activity - Resolved gitlab_activity test cases and linting issue Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab-activity/service.go | 3 ++- cla-backend-go/v2/gitlab-activity/service_test.go | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 0a251141a..e2dd05e00 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -60,6 +60,7 @@ type gatedGitlabUser struct { type Service interface { ProcessMergeOpenedActivity(ctx context.Context, secretToken string, mergeEvent *gitlab.MergeEvent) error ProcessMergeActivity(ctx context.Context, secretToken string, input *ProcessMergeActivityInput) error + IsUserApprovedForSignature(ctx context.Context, f logrus.Fields, corporateSignature *models.Signature, user *models.User, gitlabUser *gitlab.User) bool } type service struct { @@ -533,7 +534,7 @@ func (s service) IsUserApprovedForSignature(ctx context.Context, f logrus.Fields break } if isApproved == true { - log.WithFields(f).Debug(" found gitlab username : %s in gitlab org approval list : %+v", gitlabUserName, gitlabGroupApprovalList) + log.WithFields(f).Debugf(" found gitlab username : %s in gitlab org approval list : %+v", gitlabUserName, gitlabGroupApprovalList) return true } } diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index 02e37bfe1..23812cc43 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -4,6 +4,7 @@ package gitlab_activity import ( + "context" "fmt" "strings" "testing" @@ -93,10 +94,11 @@ func TestIsUserApprovedForSignature(t *testing.T) { expected: true, }, } + activityService := NewService(nil, nil, nil, nil, nil, nil, nil, nil) for _, tc := range testCases { t.Run(tc.name, func(tt *testing.T) { - result := IsUserApprovedForSignature(logrus.Fields{}, tc.signature, userModel, gitlabUser) + result := activityService.IsUserApprovedForSignature(context.Background(),logrus.Fields{}, tc.signature, userModel, gitlabUser) if tc.expected { assert.True(tt, result) From 0957df826f80a73a81c5c9ed0c07c757c517abb2 Mon Sep 17 00:00:00 2001 From: Amol Sontakke <56335654+amolsontakke3576@users.noreply.github.com> Date: Tue, 14 Sep 2021 19:54:58 +0530 Subject: [PATCH 0517/1276] Remove v1/v2 Options From Landing Page (#3278) --- .../cla-console-section.component.html | 41 ------------ .../cla-console-section.component.ts | 66 ++----------------- .../src/app/config/app-settings.ts | 2 - .../src/app/core/services/auth.service.ts | 4 +- 4 files changed, 8 insertions(+), 105 deletions(-) diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html index 2115f109d..e51a0df1e 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.html @@ -47,44 +47,3 @@
  • - -
    - -
    -
    diff --git a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts index 9d0d38c99..4c544eeb8 100644 --- a/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts +++ b/cla-landing-page/src/app/components/cla-console-section/cla-console-section.component.ts @@ -1,17 +1,13 @@ // Copyright The Linux Foundation and each contributor to CommunityBridge. // SPDX-License-Identifier: MIT -import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { AppSettings } from 'src/app/config/app-settings'; import { StorageService } from 'src/app/core/services/storage.service'; import { EnvConfig } from 'src/app/config/cla-env-utils'; -import { ActivatedRoute } from '@angular/router'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { LandingPageService } from 'src/app/service/landing-page.service'; import { environment } from 'src/environments/environment'; -declare let process: any; - @Component({ selector: 'app-cla-console-section', templateUrl: './cla-console-section.component.html', @@ -19,10 +15,8 @@ declare let process: any; }) export class ClaConsoleSectionComponent implements OnInit { @Input() consoleMetadata: any; - @ViewChild('versionModal') versionModal: TemplateRef; version: string; links: any[]; - modelRef: NgbModalRef; selectedVersion: string; error: string; consoleType: string; @@ -31,8 +25,6 @@ export class ClaConsoleSectionComponent implements OnInit { constructor( private storageService: StorageService, - private router: ActivatedRoute, - private modalService: NgbModal, private landingPageService: LandingPageService, ) { if (!this.landingPageService.hasEventInitilize) { @@ -49,15 +41,7 @@ export class ClaConsoleSectionComponent implements OnInit { } ngOnInit() { - this.version = this.router.snapshot.queryParamMap.get('version'); const element: any = document.getElementById('lfx-header'); - let projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK] + '#/login'; - let corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK] + '#/login'; - if (this.version === '2') { - // Set redirect URL to new V2 console. - projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK_V2]; - corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK_V2]; - } this.links = [ { title: 'Project Login', @@ -78,60 +62,22 @@ export class ClaConsoleSectionComponent implements OnInit { onClickProceed(type: string) { this.consoleType = type; this.storageService.setItem('type', type); - if (this.version === '1' || this.version === '2') { - this.redirectAsPerTypeAndVersion(this.consoleType, this.version); - } else { - // Show version dialog - this.openDialog(); - return false; - } - } - - openDialog() { - this.error = ''; - this.selectedVersion = ''; - this.modelRef = this.modalService.open(this.versionModal, { - centered: true, - backdrop: 'static', - keyboard: false, - }); + this.redirectAsPerTypeAndVersion(this.consoleType); } onClickLearnMore() { window.open(AppSettings.CONTRIBUTORS_LEARN_MORE, '_blank'); } - onClickVersion(version) { - this.selectedVersion = version; - } - - onClickVersionProceed() { - if (this.selectedVersion === '') { - this.error = 'Please select a EasyCLA Version.'; - } else { - this.redirectAsPerTypeAndVersion(this.consoleType, this.selectedVersion); - } - } - - redirectAsPerTypeAndVersion(type: string, version: string) { - let projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK] + '#/login'; - let corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK] + '#/login'; - - if (version === '2') { - projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK_V2]; - corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK_V2]; - } - + redirectAsPerTypeAndVersion(type: string) { + const projectConsoleUrl = EnvConfig.default[AppSettings.PROJECT_CONSOLE_LINK_V2]; + const corporateConsoleUrl = EnvConfig.default[AppSettings.CORPORATE_CONSOLE_LINK_V2]; + if (type === 'Projects') { window.open(projectConsoleUrl, '_self'); } - if (type === 'Organizations') { window.open(corporateConsoleUrl, '_self'); } } - - onClickClose() { - this.modalService.dismissAll(); - } } diff --git a/cla-landing-page/src/app/config/app-settings.ts b/cla-landing-page/src/app/config/app-settings.ts index b9697eb11..0e62c4b04 100644 --- a/cla-landing-page/src/app/config/app-settings.ts +++ b/cla-landing-page/src/app/config/app-settings.ts @@ -13,8 +13,6 @@ export class AppSettings { public static USER_EMAIL = 'user_email'; public static USER_NAME = 'user_name'; public static CONSOLE_TYPE = 'consoleType'; - public static PROJECT_CONSOLE_LINK = 'proj-console-link'; - public static CORPORATE_CONSOLE_LINK = 'corp-console-link'; public static PROJECT_CONSOLE_LINK_V2 = 'admin-v2-base'; public static CORPORATE_CONSOLE_LINK_V2 = 'corporate-v2-base'; public static REQUEST_ACCESS_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLSdnTk_9xjk7YoiX_FcPEqsFytsLMcT8OzYUbK6TsYopR1XhdA/viewform'; diff --git a/cla-landing-page/src/app/core/services/auth.service.ts b/cla-landing-page/src/app/core/services/auth.service.ts index 2fa47f5fd..048d063eb 100644 --- a/cla-landing-page/src/app/core/services/auth.service.ts +++ b/cla-landing-page/src/app/core/services/auth.service.ts @@ -134,7 +134,7 @@ export class AuthService { this.auth0Client$.subscribe((client: Auth0Client) => { // Call method to log in const type = JSON.parse(this.storageService.getItem('type')); - const redirectConsole = (type === 'Projects') ? AppSettings.PROJECT_CONSOLE_LINK : AppSettings.CORPORATE_CONSOLE_LINK; + const redirectConsole = (type === 'Projects') ? AppSettings.PROJECT_CONSOLE_LINK_V2 : AppSettings.CORPORATE_CONSOLE_LINK_V2; client.loginWithRedirect({ redirect_uri: EnvConfig.default[redirectConsole], appState: { target: redirectPath }, @@ -201,7 +201,7 @@ export class AuthService { // this.router.navigate([targetRoute]); if (targetRoute !== '/') { const type = JSON.parse(this.storageService.getItem('type')); - const redirectConsole = (type === 'Projects') ? AppSettings.PROJECT_CONSOLE_LINK : AppSettings.CORPORATE_CONSOLE_LINK; + const redirectConsole = (type === 'Projects') ? AppSettings.PROJECT_CONSOLE_LINK_V2 : AppSettings.CORPORATE_CONSOLE_LINK_V2; window.open(EnvConfig.default[redirectConsole] + REDIRECT_AUTH_ROUTE, '_self'); } else { this.router.navigate([targetRoute]); From 34a630f241f79bf19f7375f176eb6549e8f55dc8 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 14 Sep 2021 09:10:09 -0700 Subject: [PATCH 0518/1276] Added GitLab Org URL Index Lambda Permissions (#3281) --- cla-backend-go/serverless.yml | 1 + cla-backend/serverless.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/cla-backend-go/serverless.yml b/cla-backend-go/serverless.yml index b3b3926f5..1fab47ae1 100644 --- a/cla-backend-go/serverless.yml +++ b/cla-backend-go/serverless.yml @@ -206,6 +206,7 @@ provider: - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-full-path-index" - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + - "arn:aws:dynamodb:${self:custom.dynamodb.region}:${aws:accountId}:table/cla-${opt:stage}-gitlab-orgs/index/gitlab-org-url-index" environment: STAGE: ${self:provider.stage} diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index 289e3621f..c2164613b 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -292,6 +292,7 @@ provider: - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-project-sfid-organization-name-index" - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-full-path-index" - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-external-group-id-index" + - "arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/cla-${sls:stage}-gitlab-orgs/index/gitlab-org-url-index" environment: STAGE: ${sls:stage} From 73da51194fefa1a5c9b40bb76809f6e51d4ebfae Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 15 Sep 2021 15:36:04 +0300 Subject: [PATCH 0519/1276] Bug/Gitlab Activity - Resolved gitlab activity for projects with no ccla Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab-activity/service.go | 7 +++++++ cla-backend-go/v2/gitlab-activity/service_test.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index e2dd05e00..4b0dcc4e8 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -397,6 +397,13 @@ func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUse log.WithFields(f).Errorf(msg) return false, fmt.Errorf(msg) } + + if corporateSignature == nil { + msg := fmt.Sprintf("no ccla signature record for company : %s ", companyID) + log.WithFields(f).Errorf(msg) + return false, fmt.Errorf(msg) + } + log.WithFields(f).Debugf("loaded signature id : %s for claGroupID : %s and companyID : %s", corporateSignature.SignatureID, claGroupID, companyID) approvalCriteria := &signatures.ApprovalCriteria{} diff --git a/cla-backend-go/v2/gitlab-activity/service_test.go b/cla-backend-go/v2/gitlab-activity/service_test.go index 23812cc43..761840bf1 100644 --- a/cla-backend-go/v2/gitlab-activity/service_test.go +++ b/cla-backend-go/v2/gitlab-activity/service_test.go @@ -98,7 +98,7 @@ func TestIsUserApprovedForSignature(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(tt *testing.T) { - result := activityService.IsUserApprovedForSignature(context.Background(),logrus.Fields{}, tc.signature, userModel, gitlabUser) + result := activityService.IsUserApprovedForSignature(context.Background(), logrus.Fields{}, tc.signature, userModel, gitlabUser) if tc.expected { assert.True(tt, result) From bcb74e3a9e41ef8581aa0bb2fcb8c512e9163fe1 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Thu, 16 Sep 2021 19:24:54 +0300 Subject: [PATCH 0520/1276] enable gitlab pipeline mr protection (#3289) --- .../cmd/gitlab/gitlaborgevents/main.go | 69 +++++++++++++++++++ .../cmd/gitlab/project_settings/main.go | 39 +++++++++++ cla-backend-go/cmd/gitlab/repoevents/main.go | 55 +++++++++++++++ cla-backend-go/gitlab_api/client_projects.go | 32 +++++++++ cla-backend-go/go.sum | 1 + .../v2/dynamo_events/gitlab_webhooks.go | 9 ++- 6 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 cla-backend-go/cmd/gitlab/gitlaborgevents/main.go create mode 100644 cla-backend-go/cmd/gitlab/project_settings/main.go create mode 100644 cla-backend-go/cmd/gitlab/repoevents/main.go diff --git a/cla-backend-go/cmd/gitlab/gitlaborgevents/main.go b/cla-backend-go/cmd/gitlab/gitlaborgevents/main.go new file mode 100644 index 000000000..c082b5ac5 --- /dev/null +++ b/cla-backend-go/cmd/gitlab/gitlaborgevents/main.go @@ -0,0 +1,69 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "encoding/json" + "os" + + "github.com/aws/aws-lambda-go/events" + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) + +// This function is useful for generating dynamodb events for GitlabOrg testing, the output of this function is used as +// ./bin/dynamo-events-lambda-mac {OUTPUT_OF_THIS_FUNCTION} +func main() { + // authInfo needed for creating the gitlab client + authInfo := os.Getenv("GITLAB_AUTH_INFO") + + event := events.DynamoDBEvent{ + Records: []events.DynamoDBEventRecord{ + { + EventSourceArn: "aws:dynamodb/cla-dev-gitlab-orgs", + EventName: "MODIFY", + EventSource: "aws:dynamodb", + Change: events.DynamoDBStreamRecord{ + OldImage: map[string]events.DynamoDBAttributeValue{ + "organization_id": events.NewStringAttribute("4ace6f9f-0518-4621-ae86-d0dacc75af83"), + "organization_full_path": events.NewStringAttribute("penguinsoft"), + "organization_sfid": events.NewStringAttribute("a092M00001If9uZQAR"), + "auto_enabled_cla_group_id": events.NewStringAttribute("40af3652-e8bf-489d-a917-cb2214a89640"), + "external_gitlab_group_id": events.NewNumberAttribute("12700028"), + "auth_state": events.NewStringAttribute("18eb90d1-8c36-4962-ba91-e264ccbcab3a"), + "organization_url": events.NewStringAttribute("https://gitlab.com/groups/penguinsoft"), + "auth_info": events.NewStringAttribute(authInfo), + "organization_name_lower": events.NewStringAttribute("penguinsoft"), + "project_sfid": events.NewStringAttribute("a092M00001If9uZQAR"), + "organization_name": events.NewStringAttribute("penguinsoft"), + "enabled": events.NewBooleanAttribute(false), + "auto_enabled": events.NewBooleanAttribute(false), + "branch_protection_enabled": events.NewBooleanAttribute(false), + }, + NewImage: map[string]events.DynamoDBAttributeValue{ + "organization_id": events.NewStringAttribute("4ace6f9f-0518-4621-ae86-d0dacc75af83"), + "organization_full_path": events.NewStringAttribute("penguinsoft"), + "organization_sfid": events.NewStringAttribute("a092M00001If9uZQAR"), + "auto_enabled_cla_group_id": events.NewStringAttribute("40af3652-e8bf-489d-a917-cb2214a89640"), + "external_gitlab_group_id": events.NewNumberAttribute("12700028"), + "auth_state": events.NewStringAttribute("18eb90d1-8c36-4962-ba91-e264ccbcab3a"), + "organization_url": events.NewStringAttribute("https://gitlab.com/groups/penguinsoft"), + "auth_info": events.NewStringAttribute(authInfo), + "organization_name_lower": events.NewStringAttribute("penguinsoft"), + "project_sfid": events.NewStringAttribute("a092M00001If9uZQAR"), + "organization_name": events.NewStringAttribute("penguinsoft"), + "enabled": events.NewBooleanAttribute(true), + "auto_enabled": events.NewBooleanAttribute(false), + "branch_protection_enabled": events.NewBooleanAttribute(true), + }, + }}, + }, + } + + b, err := json.Marshal(event) + if err != nil { + log.Fatalf("marshall : %v", err) + } + + log.Println(string(b)) +} diff --git a/cla-backend-go/cmd/gitlab/project_settings/main.go b/cla-backend-go/cmd/gitlab/project_settings/main.go new file mode 100644 index 000000000..62bfffadd --- /dev/null +++ b/cla-backend-go/cmd/gitlab/project_settings/main.go @@ -0,0 +1,39 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "context" + "flag" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/xanzy/go-gitlab" + "os" +) + +var projectID = flag.Int("project", 0, "gitlab project id") + +func main() { + flag.Parse() + + if *projectID == 0 { + log.Fatalf("gitlab project id is missing") + } + + accessToken := os.Getenv("GITLAB_ACCESS_TOKEN") + if accessToken == "" { + log.Fatalf("GITLAB_ACCESS_TOKEN is required") + } + + gitlabClient, err := gitlab.NewOAuthClient(accessToken) + if err != nil { + log.Fatalf("creating client failed : %v", err) + } + + if err := gitlab_api.EnableMergePipelineProtection(context.Background(), gitlabClient, *projectID); err != nil { + log.Fatalf("enabling merge pipeline protection failed : %v", err) + } + + log.Println("merge pipeline protection enabled successfully") +} diff --git a/cla-backend-go/cmd/gitlab/repoevents/main.go b/cla-backend-go/cmd/gitlab/repoevents/main.go new file mode 100644 index 000000000..fb4e4245c --- /dev/null +++ b/cla-backend-go/cmd/gitlab/repoevents/main.go @@ -0,0 +1,55 @@ +// Copyright The Linux Foundation and each contributor to CommunityBridge. +// SPDX-License-Identifier: MIT + +package main + +import ( + "encoding/json" + + "github.com/aws/aws-lambda-go/events" + log "github.com/communitybridge/easycla/cla-backend-go/logging" +) + +// This function is useful for generating dynamodb events for Repository testing, the output of this function is used as +// ./bin/dynamo-events-lambda-mac {OUTPUT_OF_THIS_FUNCTION} +func main() { + event := events.DynamoDBEvent{ + Records: []events.DynamoDBEventRecord{ + { + EventSourceArn: "aws:dynamodb/cla-dev-repositories", + EventName: "MODIFY", + EventSource: "aws:dynamodb", + Change: events.DynamoDBStreamRecord{ + OldImage: map[string]events.DynamoDBAttributeValue{ + "repository_id": events.NewStringAttribute("1fa3de39-8274-4750-ba7c-242d5d659dd1"), + "repository_name": events.NewStringAttribute("easycla-gitlab-test"), + "repository_organization_name": events.NewStringAttribute("penguinsoft"), + "repository_project_id": events.NewStringAttribute("40af3652-e8bf-489d-a917-cb2214a89640"), + "repository_sfdc_id": events.NewStringAttribute("a092M00001If9uZQAR"), + "project_sfid": events.NewStringAttribute("a092M00001If9uZQAR"), + "repository_external_id": events.NewNumberAttribute("28893091"), + "repository_type": events.NewStringAttribute("gitlab"), + "enabled": events.NewBooleanAttribute(false), + }, + NewImage: map[string]events.DynamoDBAttributeValue{ + "repository_id": events.NewStringAttribute("1fa3de39-8274-4750-ba7c-242d5d659dd1"), + "repository_name": events.NewStringAttribute("easycla-gitlab-test"), + "repository_organization_name": events.NewStringAttribute("penguinsoft"), + "repository_project_id": events.NewStringAttribute("40af3652-e8bf-489d-a917-cb2214a89640"), + "repository_sfdc_id": events.NewStringAttribute("a092M00001If9uZQAR"), + "project_sfid": events.NewStringAttribute("a092M00001If9uZQAR"), + "repository_external_id": events.NewNumberAttribute("28893091"), + "repository_type": events.NewStringAttribute("gitlab"), + "enabled": events.NewBooleanAttribute(true), + }, + }}, + }, + } + + b, err := json.Marshal(event) + if err != nil { + log.Fatalf("marshall : %v", err) + } + + log.Println(string(b)) +} diff --git a/cla-backend-go/gitlab_api/client_projects.go b/cla-backend-go/gitlab_api/client_projects.go index 5cb7cbf49..ec8fcd4a6 100644 --- a/cla-backend-go/gitlab_api/client_projects.go +++ b/cla-backend-go/gitlab_api/client_projects.go @@ -112,3 +112,35 @@ func GetProjectByID(ctx context.Context, client *goGitLab.Client, gitLabProjectI return project, nil } + +// EnableMergePipelineProtection enables the pipeline protection on given project, by default it's +// turned off and when a new MR is raised users can merge requests bypassing the pipelines. With this +// setting gitlab disables the Merge button if any of the pipelines are failing +func EnableMergePipelineProtection(ctx context.Context, gitlabClient *goGitLab.Client, projectID int) error { + f := logrus.Fields{ + "functionName": "gitlab.client.EnableMergePipelineProtection", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "gitLabProjectID": projectID, + } + + project, _, err := gitlabClient.Projects.GetProject(projectID, &goGitLab.GetProjectOptions{}) + if err != nil { + return fmt.Errorf("fetching project failed : %v", err) + } + + log.WithFields(f).Debugf("Merge if Pipeline is succeeds flag enabled : %v", project.OnlyAllowMergeIfPipelineSucceeds) + if project.OnlyAllowMergeIfPipelineSucceeds { + return nil + } + + project.OnlyAllowMergeIfPipelineSucceeds = true + log.WithFields(f).Debugf("Enabling Merge Pipeline protection") + _, _, err = gitlabClient.Projects.EditProject(projectID, &goGitLab.EditProjectOptions{ + OnlyAllowMergeIfPipelineSucceeds: goGitLab.Bool(true), + }) + + if err != nil { + return fmt.Errorf("editing project : %d failed : %v", projectID, err) + } + return nil +} diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index e800131fe..d9ff15622 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -575,6 +575,7 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go index 4463bd416..7c13fe5d6 100644 --- a/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go +++ b/cla-backend-go/v2/dynamo_events/gitlab_webhooks.go @@ -69,9 +69,10 @@ func (s *service) GitLabRepoAddedWebhookEventHandler(event events.DynamoDBEventR if err := gitlab_api.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { log.WithFields(f).Errorf("adding gitlab webhook failed : %v", err) } - log.WithFields(f).Debugf("gitlab webhhok added succesfully for repository") - return nil + + log.WithFields(f).Debugf("enabling gitlab pipeline protection if not alreasy") + return gitlab_api.EnableMergePipelineProtection(ctx, gitLabClient, repositoryExternalIDInt) } func (s *service) GitlabRepoModifiedWebhookEventHandler(event events.DynamoDBEventRecord) error { @@ -140,6 +141,10 @@ func (s *service) GitlabRepoModifiedWebhookEventHandler(event events.DynamoDBEve if err := gitlab_api.SetWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt, gitlabOrg.AuthState); err != nil { log.WithFields(f).Errorf("adding gitlab webhook failed : %v", err) } + log.WithFields(f).Debugf("enabling gitlab pipeline protection if not alreasy") + if err := gitlab_api.EnableMergePipelineProtection(ctx, gitLabClient, repositoryExternalIDInt); err != nil { + return err + } } else { if err := gitlab_api.RemoveWebHook(gitLabClient, conf.Gitlab.WebHookURI, repositoryExternalIDInt); err != nil { log.WithFields(f).Errorf("removing gitlab webhook failed : %v", err) From 9da30690bff4319241e9d5f0889c3d37fa5131ee Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Thu, 16 Sep 2021 16:53:29 -0700 Subject: [PATCH 0521/1276] [#3243] Bug/GitLab Sign Request (#3292) --- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/gitlab_api/auth.go | 60 +++++++++++- cla-backend-go/swagger/cla.v2.yaml | 40 +++++++- cla-backend-go/v2/gitlab-activity/handlers.go | 94 ++++++++++++++++++- cla-backend-go/v2/gitlab_sign/handlers.go | 78 ++++++++++++--- cla-backend-go/v2/gitlab_sign/service.go | 22 ++--- 6 files changed, 265 insertions(+), 33 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index a32ceac31..40e2317af 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -350,8 +350,8 @@ func server(localMode bool) http.Handler { github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) - gitlab_sign.Configure(v2API, gitlabSignService, eventsService, configFile.CLAContributorv2Base) - gitlab_activity.Configure(v2API, gitlabActivityService, gitlabOrganizationRepo, eventsService, gitlabApp) + gitlab_sign.Configure(v2API, gitlabSignService, eventsService, configFile.CLAContributorv2Base, sessionStore) + gitlab_activity.Configure(v2API, gitlabActivityService, gitlabOrganizationRepo, eventsService, gitlabApp, gitlabSignService, configFile.CLAContributorv2Base, sessionStore) v1Repositories.Configure(api, v1RepositoriesService, eventsService) v2Repositories.Configure(v2API, v2RepositoriesService, eventsService) gerrits.Configure(api, gerritService, v1ProjectService, eventsService) diff --git a/cla-backend-go/gitlab_api/auth.go b/cla-backend-go/gitlab_api/auth.go index d18fce41e..afa1b93eb 100644 --- a/cla-backend-go/gitlab_api/auth.go +++ b/cla-backend-go/gitlab_api/auth.go @@ -13,6 +13,11 @@ import ( "github.com/sirupsen/logrus" ) +const ( + // GitLabTokenURL for gitlab oauth flow + GitLabTokenURL = "https://gitlab.com/oauth/token" //nolint +) + // FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { gitLabConfig := config.GetConfig().Gitlab @@ -44,18 +49,65 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { //"redirect_uri": "http://localhost:8080/v4/gitlab/oauth/callback", } - url := "https://gitlab.com/oauth/token" resp, err := client.R(). SetQueryParams(params). SetResult(&OauthSuccessResponse{}). - Post(url) + Post(GitLabTokenURL) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", GitLabTokenURL) + return nil, err + } + + if resp.StatusCode() < 200 || resp.StatusCode() > 299 { + msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", GitLabTokenURL, resp.StatusCode(), string(resp.Body())) + log.WithFields(f).Warn(msg) + return nil, errors.New(msg) + } + + return resp.Result().(*OauthSuccessResponse), nil +} + +// FetchUserOauthCredentials is responsible for fetching the user credentials from gitlab for alredy started Oauth process (access_token, refresh_token) +func FetchUserOauthCredentials(code string) (*OauthSuccessResponse, error) { + gitLabConfig := config.GetConfig().Gitlab + f := logrus.Fields{ + "functionName": "gitlab.auth.FetchUserOauthCredentials", + "code": code, + "redirectURI": "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback", + } + + if len(gitLabConfig.AppClientID) > 4 { + f["gitLabClientID"] = fmt.Sprintf("%s...%s", gitLabConfig.AppClientID[0:4], gitLabConfig.AppClientID[len(gitLabConfig.AppClientID)-4:]) + } else { + return nil, errors.New("gitlab application client ID value is not set - value is empty or malformed") + } + if len(gitLabConfig.AppClientSecret) > 4 { + f["gitLabClientSecret"] = fmt.Sprintf("%s...%s", gitLabConfig.AppClientSecret[0:4], gitLabConfig.AppClientSecret[len(gitLabConfig.AppClientSecret)-4:]) + } else { + return nil, errors.New("gitlab application client secret value is not set - value is empty or malformed") + } + + // For info on this authorization flow, see: https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow + client := resty.New() + params := map[string]string{ + "client_id": gitLabConfig.AppClientID, + "client_secret": gitLabConfig.AppClientSecret, + "code": code, + "grant_type": "authorization_code", + "redirect_uri": "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback", + } + + resp, err := client.R(). + SetQueryParams(params). + SetResult(&OauthSuccessResponse{}). + Post(GitLabTokenURL) if err != nil { - log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", url) + log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", GitLabTokenURL) return nil, err } if resp.StatusCode() < 200 || resp.StatusCode() > 299 { - msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", url, resp.StatusCode(), string(resp.Body())) + msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", GitLabTokenURL, resp.StatusCode(), string(resp.Body())) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index b05dfc76a..d3973557b 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3968,7 +3968,45 @@ paths: $ref: '#/responses/internal-server-error' tags: - gitlab-activity - + + /gitlab/user/oauth/callback: + get: + summary: The endpoint is called after user is authorized for the sign flow + description: The endpoint handles storing the OAuth2 session information for this user and initiate the signing workflow + security: [ ] + operationId: gitlabUserOauthCallback + parameters: + - $ref: "#/parameters/x-request-id" + - name: code + description: oauth code used to fetch the access token + in: query + type: string + required: true + - name: state + description: state is used to find the gitlab user + in: query + type: string + required: true + responses: + '200': + description: 'Success' + headers: + x-request-id: + type: string + description: The unique request ID value - assigned/set by the API Gateway based on the session + schema: + $ref: '#/definitions/success-response' + '400': + $ref: '#/responses/invalid-request' + '403': + $ref: '#/responses/forbidden' + '404': + $ref: '#/responses/not-found' + '500': + $ref: '#/responses/internal-server-error' + tags: + - gitlab-activity + /gitlab/activity: post: summary: Gitlab Activity Callback Handler diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index ee08cec23..211d0ac15 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -7,22 +7,31 @@ import ( "context" "errors" "fmt" + "net/http" gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations" + "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_sign" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_activity" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" + "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" + "github.com/savaki/dynastore" "github.com/sirupsen/logrus" gitlabsdk "github.com/xanzy/go-gitlab" ) -func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab_organizations.RepositoryInterface, eventService events.Service, gitLabApp *gitlab_api.App) { +const ( + // SessionStoreKey for cla-gitlab + SessionStoreKey = "cla-gitlab" +) + +func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab_organizations.RepositoryInterface, eventService events.Service, gitLabApp *gitlab_api.App, signService gitlab_sign.Service, contributorConsoleV2Base string, sessionStore *dynastore.Store) { api.GitlabActivityGitlabTriggerHandler = gitlab_activity.GitlabTriggerHandlerFunc(func(params gitlab_activity.GitlabTriggerParams) middleware.Responder { requestID, _ := uuid.NewV4() @@ -173,4 +182,87 @@ func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab return gitlab_activity.NewGitlabActivityOK() }) + api.GitlabActivityGitlabUserOauthCallbackHandler = gitlab_activity.GitlabUserOauthCallbackHandlerFunc( + func(guocp gitlab_activity.GitlabUserOauthCallbackParams) middleware.Responder { + reqID := utils.GetRequestID(guocp.XREQUESTID) + ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) + f := logrus.Fields{ + "functionName": "gitlab_activity.handler.GitlabActivityGitlabUserOauthCallbackHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "code": guocp.Code, + "state": guocp.State, + } + + return middleware.ResponderFunc( + func(rw http.ResponseWriter, p runtime.Producer) { + session, err := sessionStore.Get(guocp.HTTPRequest, SessionStoreKey) + if err != nil { + log.WithFields(f).WithError(err).Warn("error with session store lookup") + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + state, ok := session.Values["gitlab_oauth2_state"].(string) + if !ok { + log.WithFields(f).Warn("Error getting session state - missing from session object") + http.Error(rw, "no session state", http.StatusInternalServerError) + return + } + + gitlabOriginURL, ok := session.Values["gitlab_origin_url"].(string) + if !ok { + log.WithFields(f).Warn("Error getting gitlab_origin_url - missing from session object") + http.Error(rw, "no return url", http.StatusInternalServerError) + return + } + + repositoryID, ok := session.Values["gitab_repository_id"].(string) + if !ok { + log.WithFields(f).Warn("Error getting gitlab_repository_id - missing from session object") + http.Error(rw, "no return url", http.StatusInternalServerError) + return + } + + mergeRequestID, ok := session.Values["gitlab_merge_request_id"].(string) + if !ok { + log.WithFields(f).Warn("Error getting gitlab_merge_request_id - missing from session object") + http.Error(rw, "no return url", http.StatusInternalServerError) + return + } + + if guocp.State != state { + msg := fmt.Sprintf("mismatch state, received: %s from callback, but loaded our state as: %s", + guocp.State, state) + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + log.WithFields(f).Debug("Fetching access token for user...") + token, err := gitlab_api.FetchUserOauthCredentials(guocp.Code) + if err != nil { + msg := fmt.Sprint("unable to fetch access token for user") + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + session.Values["gitlab_oauth2_token"] = token.AccessToken + session.Save(guocp.HTTPRequest, rw) + + // Get client + gitlabClient, err := gitlab_api.NewGitlabOauthClientFromAccessToken(token.AccessToken) + if err != nil { + msg := fmt.Sprintf("unable to create gitlab client from token : %s ", token.AccessToken) + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + consoleURL, err := signService.InitiateSignRequest(ctx, guocp.HTTPRequest, gitlabClient, repositoryID, mergeRequestID, gitlabOriginURL, contributorConsoleV2Base, eventService) + log.WithFields(f).Debugf("redirecting to :%s ", *consoleURL) + http.Redirect(rw, guocp.HTTPRequest, *consoleURL, http.StatusSeeOther) + }) + }) + } diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index 317fc8c58..4392747ef 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -7,19 +7,30 @@ import ( "context" "fmt" "net/http" + "net/url" + "github.com/communitybridge/easycla/cla-backend-go/config" "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_sign" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/gofrs/uuid" + "github.com/savaki/dynastore" "github.com/sirupsen/logrus" + "github.com/xanzy/go-gitlab" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) -func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service, contributorConsoleV2Base string) { +const ( + // SessionStoreKey for cla-gitlab session + SessionStoreKey = "cla-gitlab" +) + +func Configure(api *operations.EasyclaAPI, service Service, eventService events.Service, contributorConsoleV2Base string, sessionStore *dynastore.Store) { api.GitlabSignSignRequestHandler = gitlab_sign.SignRequestHandlerFunc( func(srp gitlab_sign.SignRequestParams) middleware.Responder { reqID := utils.GetRequestID(srp.XREQUESTID) @@ -33,19 +44,64 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. "mergeRequestID": srp.MergeRequestID, } - log.WithFields(f).Debugf("Initiating Gitlab sign request for : %+v ", srp) + return middleware.ResponderFunc(func(rw http.ResponseWriter, pr runtime.Producer) { + session, err := sessionStore.Get(srp.HTTPRequest, SessionStoreKey) + if err != nil { + log.WithFields(f).WithError(err).Warn("error with session store lookup") + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + config := config.GetConfig() - consoleURL, err := service.GitlabSignRequest(ctx, srp.HTTPRequest, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID, contributorConsoleV2Base, eventService) + log.WithFields(f).Debugf("Loading session : %+v", session) - if err != nil { - msg := fmt.Sprintf("problem initiating sign request for :%+v", srp) - log.WithFields(f).Debugf(msg) - return gitlab_sign.NewSignRequestBadRequest().WithPayload( - utils.ErrorResponseBadRequestWithError(reqID, msg, err)) - } + log.WithFields(f).Debug("Initiating sign request..") - return middleware.ResponderFunc(func(rw http.ResponseWriter, pr runtime.Producer) { - http.Redirect(rw, srp.HTTPRequest, *consoleURL, http.StatusSeeOther) + session.Values["gitlab_installation_id"] = srp.OrganizationID + session.Values["gitlab_repository_id"] = srp.GitlabRepositoryID + session.Values["gitlab_merge_request_id"] = srp.MergeRequestID + + gitlabAuthToken := session.Values["gitlab_oauth2_token"].(string) + if gitlabAuthToken != "" { + session.Save(srp.HTTPRequest, rw) + log.WithFields(f).Debugf("using existing Gitlab Ouath2 Token: %s ", gitlabAuthToken) + gitlabClient, err := gitlab.NewClient(gitlabAuthToken) + + if err != nil { + msg := fmt.Sprintf("problem creating gitlab client with token : %s ", gitlabAuthToken) + log.WithFields(f).Debug(msg) + http.Error(rw, msg, http.StatusInternalServerError) + } + + log.WithFields(f).Debugf("Initiating Gitlab sign request for : %+v ", srp) + + originURL, err := service.GetOriginURL(ctx, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID) + + consoleURL, err := service.InitiateSignRequest(ctx, srp.HTTPRequest, gitlabClient, srp.GitlabRepositoryID, srp.MergeRequestID, *originURL, contributorConsoleV2Base, eventService) + + if err != nil { + msg := fmt.Sprintf("problem initiating sign request for :%+v", srp) + log.WithFields(f).Debugf(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + http.Redirect(rw, srp.HTTPRequest, *consoleURL, http.StatusSeeOther) + } + log.WithFields(f).Debugf("No existing GitLab Oauth2 Token ") + + log.WithFields(f).Debug("initiating gitlab sign request ...") + state, err := uuid.NewV4() + session.Values["gitlab_oauth2_state"] = state + session.Save(srp.HTTPRequest, rw) + params := url.Values{} + params.Add("client_id", config.Gitlab.AppClientID) + params.Add("state", state.String()) + params.Add("response_type", "code") + params.Add("redirect_uri", "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback") + params.Add("scope", "profile email") + authURL := strfmt.URI("https://gitlab.com/oauth/authorize?" + params.Encode()) + http.Redirect(rw, srp.HTTPRequest, string(authURL), http.StatusSeeOther) }) }) diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go index cb6835108..602c9553e 100644 --- a/cla-backend-go/v2/gitlab_sign/service.go +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -38,7 +38,8 @@ type service struct { } type Service interface { - GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) (*string, error) + InitiateSignRequest(ctx context.Context, req *http.Request, gitlabClient *gitlab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) (*string, error) + GetOriginURL(ctx context.Context, organizationID, repositoryID, mergeRequestID string) (*string, error) } func NewService(gitlabRepositoryService repositories.ServiceInterface, gitlabOrgRepository gitlab_organizations.RepositoryInterface, userService users.Service, storeRepo store.Repository, gitlabApp *gitlab_api.App) Service { @@ -51,14 +52,12 @@ func NewService(gitlabRepositoryService repositories.ServiceInterface, gitlabOrg } } -func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organizationID, repositoryID, mergeRequestID, contributorConsoleV2Base string, eventService events.Service) (*string, error) { +// GetOriginURL Gets Origin URL for the newly created MR +func (s service) GetOriginURL(ctx context.Context, organizationID, repositoryID, mergeRequestID string) (*string, error) { f := logrus.Fields{ - "functionName": "v2.gitlab_sign.service.GitlabSignRequest", + "functionName": "v2.gitlab_sign.service.GetOriginURL", "organizationID": organizationID, - "repositoryID": repositoryID, - "mergeRequestID": mergeRequestID, } - organization, err := s.gitlabOrgRepo.GetGitLabOrganization(ctx, organizationID) if err != nil { log.WithFields(f).Debugf("unable to get gitlab organiztion by ID: %s, error: %+v ", organizationID, err) @@ -92,16 +91,11 @@ func (s service) GitlabSignRequest(ctx context.Context, req *http.Request, organ originURL := mergeRequest.WebURL log.WithFields(f).Debugf("Return URL from the inbound request is : %s ", originURL) - consoleURL, err := s.redirectToConsole(ctx, req, gitlabClient, repositoryID, mergeRequestID, originURL, contributorConsoleV2Base, eventService) - if err != nil { - log.WithFields(f).Debug("unable to redirect to contributor console") - return nil, err - } - - return consoleURL, nil + return &originURL, nil } -func (s service) redirectToConsole(ctx context.Context, req *http.Request, gitlabClient *gitlab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) (*string, error) { +// InitiateSignRequest initiates sign request and returns easy cla redirect url +func (s service) InitiateSignRequest(ctx context.Context, req *http.Request, gitlabClient *gitlab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) (*string, error) { f := logrus.Fields{ "functionName": "v2.gitlab_sign.service.redirectToConsole", "repositoryID": repositoryID, From fcc937de3f0fd186bfa525759da3cd8719931106 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 17 Sep 2021 10:49:05 +0300 Subject: [PATCH 0522/1276] [#3243] Bug/GitLab Oauth - Resolved session check for gitlab oauth sign flow Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab_sign/handlers.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index 4392747ef..840e4c8e3 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -61,8 +61,8 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. session.Values["gitlab_repository_id"] = srp.GitlabRepositoryID session.Values["gitlab_merge_request_id"] = srp.MergeRequestID - gitlabAuthToken := session.Values["gitlab_oauth2_token"].(string) - if gitlabAuthToken != "" { + gitlabAuthToken, ok := session.Values["gitlab_oauth2_token"].(string) + if ok { session.Save(srp.HTTPRequest, rw) log.WithFields(f).Debugf("using existing Gitlab Ouath2 Token: %s ", gitlabAuthToken) gitlabClient, err := gitlab.NewClient(gitlabAuthToken) @@ -88,6 +88,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. http.Redirect(rw, srp.HTTPRequest, *consoleURL, http.StatusSeeOther) } + log.WithFields(f).Debugf("No existing GitLab Oauth2 Token ") log.WithFields(f).Debug("initiating gitlab sign request ...") From 9036093d8b10018b98ca272ada2bb8b790071d8d Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 17 Sep 2021 13:08:31 +0300 Subject: [PATCH 0523/1276] [#3473#] Bug/GitLab Redirect URI - Resolved redirect uri issue for gitlab sign request flow Signed-off-by: Harold Wanyama --- .../cmd/gitlab/project_settings/main.go | 3 +- cla-backend-go/v2/gitlab_sign/handlers.go | 32 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/cmd/gitlab/project_settings/main.go b/cla-backend-go/cmd/gitlab/project_settings/main.go index 62bfffadd..15f44fd92 100644 --- a/cla-backend-go/cmd/gitlab/project_settings/main.go +++ b/cla-backend-go/cmd/gitlab/project_settings/main.go @@ -6,10 +6,11 @@ package main import ( "context" "flag" + "os" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/xanzy/go-gitlab" - "os" ) var projectID = flag.Int("project", 0, "gitlab project id") diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index 840e4c8e3..e872b0edf 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "net/http" - "net/url" "github.com/communitybridge/easycla/cla-backend-go/config" "github.com/communitybridge/easycla/cla-backend-go/events" @@ -16,11 +15,12 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" - "github.com/go-openapi/strfmt" "github.com/gofrs/uuid" "github.com/savaki/dynastore" "github.com/sirupsen/logrus" "github.com/xanzy/go-gitlab" + "golang.org/x/oauth2" + oauth_gitlab "golang.org/x/oauth2/gitlab" log "github.com/communitybridge/easycla/cla-backend-go/logging" ) @@ -88,21 +88,31 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. http.Redirect(rw, srp.HTTPRequest, *consoleURL, http.StatusSeeOther) } - + log.WithFields(f).Debugf("No existing GitLab Oauth2 Token ") log.WithFields(f).Debug("initiating gitlab sign request ...") state, err := uuid.NewV4() session.Values["gitlab_oauth2_state"] = state session.Save(srp.HTTPRequest, rw) - params := url.Values{} - params.Add("client_id", config.Gitlab.AppClientID) - params.Add("state", state.String()) - params.Add("response_type", "code") - params.Add("redirect_uri", "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback") - params.Add("scope", "profile email") - authURL := strfmt.URI("https://gitlab.com/oauth/authorize?" + params.Encode()) - http.Redirect(rw, srp.HTTPRequest, string(authURL), http.StatusSeeOther) + oauthConfig := &oauth2.Config{ + ClientID: config.Gitlab.AppClientID, + Scopes: []string{ + "api", + "read_user", + "read_api", + "read_repository", + "write_repository", + "email", + }, + Endpoint: oauth_gitlab.Endpoint, + RedirectURL: "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback", + } + + log.WithFields(f).Debug("initiating gitlab sign request ...") + session.Values["gitlab_oauth2_state"] = state.String() + session.Save(srp.HTTPRequest, rw) + http.Redirect(rw, srp.HTTPRequest, oauthConfig.AuthCodeURL(state.String()), http.StatusFound) }) }) From c9556d18a1cf376c139057abe5442eb86fd9b392 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 17 Sep 2021 14:21:13 +0300 Subject: [PATCH 0524/1276] [#3473] Bug/GitLab Sign Token - Resolved issue with fetching gitlab oauth token Signed-off-by: Harold Wanyama --- .../cmd/gitlab/project_settings/main.go | 3 +- cla-backend-go/gitlab_api/auth.go | 45 +++++++------------ cla-backend-go/v2/gitlab-activity/handlers.go | 2 +- cla-backend-go/v2/gitlab_sign/handlers.go | 2 +- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/cla-backend-go/cmd/gitlab/project_settings/main.go b/cla-backend-go/cmd/gitlab/project_settings/main.go index 62bfffadd..15f44fd92 100644 --- a/cla-backend-go/cmd/gitlab/project_settings/main.go +++ b/cla-backend-go/cmd/gitlab/project_settings/main.go @@ -6,10 +6,11 @@ package main import ( "context" "flag" + "os" + gitlab_api "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/xanzy/go-gitlab" - "os" ) var projectID = flag.Int("project", 0, "gitlab project id") diff --git a/cla-backend-go/gitlab_api/auth.go b/cla-backend-go/gitlab_api/auth.go index afa1b93eb..ce2b35772 100644 --- a/cla-backend-go/gitlab_api/auth.go +++ b/cla-backend-go/gitlab_api/auth.go @@ -4,6 +4,7 @@ package gitlab import ( + "context" "errors" "fmt" @@ -11,11 +12,8 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/go-resty/resty/v2" "github.com/sirupsen/logrus" -) - -const ( - // GitLabTokenURL for gitlab oauth flow - GitLabTokenURL = "https://gitlab.com/oauth/token" //nolint + "golang.org/x/oauth2" + "golang.org/x/oauth2/gitlab" ) // FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) @@ -48,18 +46,18 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { "redirect_uri": gitLabConfig.RedirectURI, //"redirect_uri": "http://localhost:8080/v4/gitlab/oauth/callback", } - + url := "https://gitlab.com/oauth/token" resp, err := client.R(). SetQueryParams(params). SetResult(&OauthSuccessResponse{}). - Post(GitLabTokenURL) + Post(url) if err != nil { - log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", GitLabTokenURL) + log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", url) return nil, err } if resp.StatusCode() < 200 || resp.StatusCode() > 299 { - msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", GitLabTokenURL, resp.StatusCode(), string(resp.Body())) + msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", url, resp.StatusCode(), string(resp.Body())) log.WithFields(f).Warn(msg) return nil, errors.New(msg) } @@ -68,7 +66,7 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { } // FetchUserOauthCredentials is responsible for fetching the user credentials from gitlab for alredy started Oauth process (access_token, refresh_token) -func FetchUserOauthCredentials(code string) (*OauthSuccessResponse, error) { +func FetchOauthToken(ctx context.Context, code string) (*oauth2.Token, error) { gitLabConfig := config.GetConfig().Gitlab f := logrus.Fields{ "functionName": "gitlab.auth.FetchUserOauthCredentials", @@ -88,29 +86,18 @@ func FetchUserOauthCredentials(code string) (*OauthSuccessResponse, error) { } // For info on this authorization flow, see: https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow - client := resty.New() - params := map[string]string{ - "client_id": gitLabConfig.AppClientID, - "client_secret": gitLabConfig.AppClientSecret, - "code": code, - "grant_type": "authorization_code", - "redirect_uri": "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback", + oauth2Config := oauth2.Config{ + ClientID: gitLabConfig.AppClientID, + ClientSecret: gitLabConfig.AppClientSecret, + Endpoint: gitlab.Endpoint, } - resp, err := client.R(). - SetQueryParams(params). - SetResult(&OauthSuccessResponse{}). - Post(GitLabTokenURL) + log.WithFields(f).Debugf("Getting token ...") + token, err := oauth2Config.Exchange(ctx, code) if err != nil { - log.WithFields(f).WithError(err).Warnf("problem invoking GitLab auth token exchange to: %s", GitLabTokenURL) + log.WithFields(f).WithError(err).Warn("unable to fetch token object") return nil, err } - if resp.StatusCode() < 200 || resp.StatusCode() > 299 { - msg := fmt.Sprintf("problem invoking GitLab auth token exchange to: %s with status code: %d, response: %s", GitLabTokenURL, resp.StatusCode(), string(resp.Body())) - log.WithFields(f).Warn(msg) - return nil, errors.New(msg) - } - - return resp.Result().(*OauthSuccessResponse), nil + return token, nil } diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index 211d0ac15..cf082497c 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -239,7 +239,7 @@ func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab } log.WithFields(f).Debug("Fetching access token for user...") - token, err := gitlab_api.FetchUserOauthCredentials(guocp.Code) + token, err := gitlab_api.FetchOauthToken(ctx, guocp.Code) if err != nil { msg := fmt.Sprint("unable to fetch access token for user") log.WithFields(f).Warn(msg) diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index 840e4c8e3..3669ae9c2 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -88,7 +88,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. http.Redirect(rw, srp.HTTPRequest, *consoleURL, http.StatusSeeOther) } - + log.WithFields(f).Debugf("No existing GitLab Oauth2 Token ") log.WithFields(f).Debug("initiating gitlab sign request ...") From 06f4def9b9f5adaf41bf2d5df2b6ab7bf5e0563c Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Fri, 17 Sep 2021 14:47:36 +0300 Subject: [PATCH 0525/1276] Bug/ Gitlab Token - Updated description for FetchOauthToken Signed-off-by: Harold Wanyama --- cla-backend-go/gitlab_api/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/gitlab_api/auth.go b/cla-backend-go/gitlab_api/auth.go index ce2b35772..0c35ed529 100644 --- a/cla-backend-go/gitlab_api/auth.go +++ b/cla-backend-go/gitlab_api/auth.go @@ -65,7 +65,7 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { return resp.Result().(*OauthSuccessResponse), nil } -// FetchUserOauthCredentials is responsible for fetching the user credentials from gitlab for alredy started Oauth process (access_token, refresh_token) +// FetchOauthToken is responsible for fetching the user credentials from gitlab for alredy started Oauth process (access_token, refresh_token) func FetchOauthToken(ctx context.Context, code string) (*oauth2.Token, error) { gitLabConfig := config.GetConfig().Gitlab f := logrus.Fields{ From c3220ec07074afae5274c0bfbfd000d8abe95e95 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 17 Sep 2021 08:21:27 -0700 Subject: [PATCH 0526/1276] Resolved Vulnerabilities (#3298) --- cla-backend-go/package.json | 4 +- cla-backend-go/yarn.lock | 43 +++-- cla-backend/package.json | 4 +- cla-backend/yarn.lock | 43 +++-- cla-frontend-contributor-console/package.json | 5 +- cla-frontend-contributor-console/yarn.lock | 156 ++++++------------ cla-frontend-corporate-console/package.json | 5 +- cla-frontend-corporate-console/yarn.lock | 156 ++++++------------ cla-frontend-project-console/package.json | 5 +- cla-frontend-project-console/yarn.lock | 100 ++++------- 10 files changed, 194 insertions(+), 327 deletions(-) diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index a3100320f..441334768 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -27,9 +27,11 @@ "serverless-prune-plugin": "^1.5.1" }, "resolutions": { - "axios": "^0.21.1", + "axios": "^0.21.2", + "glob-parent": "^5.1.2", "ini": "^1.3.7", "normalize-url": "^4.5.1", + "set-value": "^4.0.1", "ws": "^7.4.6", "xmlhttprequest-ssl": "^1.6.2" } diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index db2a2f4e4..20e2dac43 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -1227,12 +1227,12 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.21.1, axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" backo2@1.0.2: version "1.0.2" @@ -2753,10 +2753,10 @@ folder-hash@^3.3.0: graceful-fs "~4.2.0" minimatch "~3.0.4" -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^0.1.3: version "0.1.8" @@ -2978,14 +2978,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^2.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3497,7 +3490,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^2.0.0, is-glob@^2.0.1: +is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= @@ -3588,6 +3581,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-promise@^2.1, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -5565,12 +5563,13 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90" - integrity sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA== +set-value@^3.0.0, set-value@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" + is-primitive "^3.0.1" shallow-clone@^1.0.0: version "1.0.0" diff --git a/cla-backend/package.json b/cla-backend/package.json index 92cb8160a..c4d8e5d64 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -44,10 +44,12 @@ "serverless-wsgi": "^2.0.1" }, "resolutions": { - "axios": "^0.21.1", + "axios": "^0.21.2", + "glob-parent": "^5.1.2", "ini": "^1.3.7", "node-fetch": "^2.6.1", "normalize-url": "^4.5.1", + "set-value": "^4.0.1", "ws": "^7.4.6", "xmlhttprequest-ssl": "^1.6.2" } diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 99a5aec79..70e1a9a38 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -1257,12 +1257,12 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.21.1, axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" backo2@1.0.2: version "1.0.2" @@ -2803,10 +2803,10 @@ folder-hash@^3.3.0: graceful-fs "~4.2.0" minimatch "~3.0.4" -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^0.1.3: version "0.1.8" @@ -3041,14 +3041,7 @@ glob-all@^3.2.1: glob "^7.1.2" yargs "^15.3.1" -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^2.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3575,7 +3568,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^2.0.0, is-glob@^2.0.1: +is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= @@ -3665,6 +3658,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-promise@^2.1, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -5755,12 +5753,13 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90" - integrity sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA== +set-value@^3.0.0, set-value@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" + is-primitive "^3.0.1" sha256-file@1.0.0: version "1.0.0" diff --git a/cla-frontend-contributor-console/package.json b/cla-frontend-contributor-console/package.json index d991e9cae..e1f83fdd7 100644 --- a/cla-frontend-contributor-console/package.json +++ b/cla-frontend-contributor-console/package.json @@ -22,13 +22,16 @@ "serverless-pseudo-parameters": "^2.5.0" }, "resolutions": { - "axios": "^0.21.1", + "axios": "^0.21.2", "bl": "^2.2.1", "braces": "^2.3.1", "glob-parent": "^5.1.2", "ini": "^1.3.7", "netmask": "^2.0.1", "normalize-url": "^4.5.1", + "pac-resolver": "^5.0.0", + "set-value": "^4.0.1", + "tar": "^6.1.9", "trim-newlines": "^3.0.1", "ws": "^7.4.6", "xmlhttprequest-ssl": "^1.6.2" diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index d122325ef..6179b2961 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -991,10 +991,10 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types@0.x.x: - version "0.14.1" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.1.tgz#0b415043770d7a2cbe4b2770271cbd7d2c9f61b9" - integrity sha512-pfSiukbt23P1qMhNnsozLzhMLBs7EEeXqPyvPmnuZM+RMfwfqwDbSVKYflgGuVI7/VehR4oMks0igzdNAg4VeQ== +ast-types@^0.13.2: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== dependencies: tslib "^2.0.1" @@ -1070,12 +1070,12 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2, axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.19.2, axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" backo2@1.0.2: version "1.0.2" @@ -1481,7 +1481,7 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.4: +chownr@^1.0.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -1573,11 +1573,6 @@ clone-response@1.0.2, clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1997,14 +1992,15 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -degenerator@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" - integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= +degenerator@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" + integrity sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ== dependencies: - ast-types "0.x.x" - escodegen "1.x.x" - esprima "3.x.x" + ast-types "^0.13.2" + escodegen "^1.8.1" + esprima "^4.0.0" + vm2 "^3.9.3" delayed-stream@~1.0.0: version "1.0.0" @@ -2288,7 +2284,7 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x: +escodegen@^1.8.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -2308,11 +2304,6 @@ esniff@^1.1.0: d "1" es5-ext "^0.10.12" -esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -2685,10 +2676,10 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^0.1.3: version "0.1.8" @@ -2791,13 +2782,6 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -3563,7 +3547,7 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -3580,6 +3564,11 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-promise@^2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -4260,14 +4249,6 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" @@ -4275,13 +4256,6 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -4306,7 +4280,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -4683,16 +4657,14 @@ pac-proxy-agent@^3.0.1: raw-body "^2.2.0" socks-proxy-agent "^4.0.1" -pac-resolver@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" - integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA== +pac-resolver@^3.0.0, pac-resolver@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" + integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA== dependencies: - co "^4.6.0" - degenerator "^1.0.4" + degenerator "^3.0.1" ip "^1.1.5" - netmask "^1.0.6" - thunkify "^2.1.2" + netmask "^2.0.1" package-json@^6.3.0: version "6.5.0" @@ -5416,7 +5388,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5610,22 +5582,13 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -set-value@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90" - integrity sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA== +set-value@^2.0.0, set-value@^2.0.1, set-value@^3.0.0, set-value@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" + is-primitive "^3.0.1" setprototypeof@1.1.1: version "1.1.1" @@ -5848,7 +5811,7 @@ source-map@^0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -split-string@^3.0.1, split-string@^3.0.2: +split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== @@ -6123,23 +6086,10 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.0.2: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - -tar@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== +tar@^4.0.2, tar@^6.0.5, tar@^6.1.9: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -6177,11 +6127,6 @@ through@^2.3.6, through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunkify@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" - integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= - time-stamp@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" @@ -6514,6 +6459,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vm2@^3.9.3: + version "3.9.3" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" + integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + warning-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" @@ -6672,7 +6622,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/cla-frontend-corporate-console/package.json b/cla-frontend-corporate-console/package.json index 0c4c4f9d5..81dfea51f 100644 --- a/cla-frontend-corporate-console/package.json +++ b/cla-frontend-corporate-console/package.json @@ -22,7 +22,7 @@ "serverless-pseudo-parameters": "^2.5.0" }, "resolutions": { - "axios": "^0.21.1", + "axios": "^0.21.2", "bl": "^1.2.3", "braces": "^2.3.1", "http-proxy": "^1.18.1", @@ -33,6 +33,9 @@ "netmask": "^2.0.1", "minimist": "^1.2.3", "normalize-url": "^4.5.1", + "pac-resolver": "^5.0.0", + "set-value": "^4.0.1", + "tar": "^6.1.9", "ws": "^7.4.6", "xmlhttprequest-ssl": "^1.6.2" } diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 0b2ca65f1..19371dfe4 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -991,10 +991,10 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types@0.x.x: - version "0.14.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" - integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== +ast-types@^0.13.2: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== dependencies: tslib "^2.0.1" @@ -1070,12 +1070,12 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2, axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.19.2, axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" backo2@1.0.2: version "1.0.2" @@ -1468,7 +1468,7 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.4: +chownr@^1.0.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -1560,11 +1560,6 @@ clone-response@1.0.2, clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1984,14 +1979,15 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -degenerator@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" - integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= +degenerator@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" + integrity sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ== dependencies: - ast-types "0.x.x" - escodegen "1.x.x" - esprima "3.x.x" + ast-types "^0.13.2" + escodegen "^1.8.1" + esprima "^4.0.0" + vm2 "^3.9.3" delayed-stream@~1.0.0: version "1.0.0" @@ -2275,7 +2271,7 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x: +escodegen@^1.8.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -2295,11 +2291,6 @@ esniff@^1.1.0: d "1" es5-ext "^0.10.12" -esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -2672,10 +2663,10 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^0.1.3: version "0.1.8" @@ -2778,13 +2769,6 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -3550,7 +3534,7 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -3567,6 +3551,11 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-promise@^2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -4228,14 +4217,6 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" @@ -4243,13 +4224,6 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -4274,7 +4248,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -4646,16 +4620,14 @@ pac-proxy-agent@^3.0.1: raw-body "^2.2.0" socks-proxy-agent "^4.0.1" -pac-resolver@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" - integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA== +pac-resolver@^3.0.0, pac-resolver@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" + integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA== dependencies: - co "^4.6.0" - degenerator "^1.0.4" + degenerator "^3.0.1" ip "^1.1.5" - netmask "^1.0.6" - thunkify "^2.1.2" + netmask "^2.0.1" package-json@^6.3.0: version "6.5.0" @@ -5379,7 +5351,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5573,22 +5545,13 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -set-value@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90" - integrity sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA== +set-value@^2.0.0, set-value@^2.0.1, set-value@^3.0.0, set-value@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" + is-primitive "^3.0.1" setprototypeof@1.1.1: version "1.1.1" @@ -5811,7 +5774,7 @@ source-map@^0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -split-string@^3.0.1, split-string@^3.0.2: +split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== @@ -6086,23 +6049,10 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.0.2: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - -tar@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== +tar@^4.0.2, tar@^6.0.5, tar@^6.1.9: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -6140,11 +6090,6 @@ through@^2.3.6, through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunkify@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" - integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= - time-stamp@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" @@ -6467,6 +6412,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vm2@^3.9.3: + version "3.9.3" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" + integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + warning-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" @@ -6625,7 +6575,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/cla-frontend-project-console/package.json b/cla-frontend-project-console/package.json index 63d5eca0b..b75a9349c 100644 --- a/cla-frontend-project-console/package.json +++ b/cla-frontend-project-console/package.json @@ -22,7 +22,7 @@ "serverless-pseudo-parameters": "^2.5.0" }, "resolutions": { - "axios": "^0.21.1", + "axios": "^0.21.2", "bl": "^1.2.3", "braces": "^2.3.1", "glob-parent": "^5.1.2", @@ -32,6 +32,9 @@ "minimist": "^1.2.3", "netmask": "^2.0.1", "normalize-url": "^4.5.1", + "pac-resolver": "^5.0.0", + "set-value": "^4.0.1", + "tar": "^6.1.9", "ws": "^7.4.6", "xmlhttprequest-ssl": "^1.6.2" } diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index 8514db0a2..ba16f87a1 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -1061,12 +1061,12 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2, axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.19.2, axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" backo2@1.0.2: version "1.0.2" @@ -1481,7 +1481,7 @@ chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3: optionalDependencies: fsevents "~2.1.2" -chownr@^1.0.1, chownr@^1.1.4: +chownr@^1.0.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -2627,10 +2627,10 @@ follow-redirects@^1.0.0: dependencies: debug "^3.2.6" -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^0.1.3: version "0.1.8" @@ -2742,13 +2742,6 @@ fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -3502,7 +3495,7 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -3519,6 +3512,11 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-promise@^2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -4177,14 +4175,6 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" @@ -4192,13 +4182,6 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -4223,7 +4206,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5321,7 +5304,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5519,22 +5502,13 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -set-value@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.1.tgz#52c82af7653ba69eb1db92e81f5cdb32739b9e95" - integrity sha512-w6n3GUPYAWQj4ZyHWzD7K2FnFXHx9OTwJYbWg+6nXjG8sCLfs9DGv+KlqglKIIJx+ks7MlFuwFW2RBPb+8V+xg== +set-value@^2.0.0, set-value@^2.0.1, set-value@^3.0.0, set-value@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" + is-primitive "^3.0.1" setprototypeof@1.1.1: version "1.1.1" @@ -5736,7 +5710,7 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -split-string@^3.0.1, split-string@^3.0.2: +split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== @@ -6011,23 +5985,10 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4, tar@^4.0.2: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - -tar@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== +tar@^4, tar@^4.0.2, tar@^6.0.5, tar@^6.1.9: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -6528,11 +6489,6 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" From 4e6830e7aeeb824f0d77bb888f4ad6956b582bf0 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 20 Sep 2021 16:22:34 +0300 Subject: [PATCH 0527/1276] [#3297] Bug/Oauth2 Redirect (GitLab) - Resolved redirect issue on gitlab ouath2 auth flow Signed-off-by: Harold Wanyama --- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/gitlab_api/auth.go | 40 ------ cla-backend-go/v2/gitlab-activity/handlers.go | 2 +- .../v2/gitlab_organizations/handlers.go | 82 ++++++++++- .../v2/gitlab_organizations/service.go | 129 +++++++++++++++++- cla-backend-go/v2/gitlab_sign/handlers.go | 15 +- 6 files changed, 217 insertions(+), 55 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 40e2317af..44b370871 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -307,7 +307,7 @@ func server(localMode bool) http.Handler { authorizer := auth.NewAuthorizer(authValidator, userRepo) v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo) githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo) - gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo) + gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo, storeRepository, usersService) gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, gitV1Repository, gitV2Repository, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo) gitlabSignService := gitlab_sign.NewService(v2RepositoriesService, gitlabOrganizationRepo, usersService, storeRepository, gitlabApp) v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo, githubOrganizationsService) @@ -349,7 +349,7 @@ func server(localMode bool) http.Handler { v2Metrics.Configure(v2API, v2MetricsService, v1CompanyRepo) github_organizations.Configure(api, githubOrganizationsService, eventsService) v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService) - gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService) + gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService, sessionStore, configFile.CLAContributorv2Base) gitlab_sign.Configure(v2API, gitlabSignService, eventsService, configFile.CLAContributorv2Base, sessionStore) gitlab_activity.Configure(v2API, gitlabActivityService, gitlabOrganizationRepo, eventsService, gitlabApp, gitlabSignService, configFile.CLAContributorv2Base, sessionStore) v1Repositories.Configure(api, v1RepositoriesService, eventsService) diff --git a/cla-backend-go/gitlab_api/auth.go b/cla-backend-go/gitlab_api/auth.go index ce2b35772..41336e61b 100644 --- a/cla-backend-go/gitlab_api/auth.go +++ b/cla-backend-go/gitlab_api/auth.go @@ -4,7 +4,6 @@ package gitlab import ( - "context" "errors" "fmt" @@ -12,8 +11,6 @@ import ( log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/go-resty/resty/v2" "github.com/sirupsen/logrus" - "golang.org/x/oauth2" - "golang.org/x/oauth2/gitlab" ) // FetchOauthCredentials is responsible for fetching the credentials from gitlab for alredy started Oauth process (access_token, refresh_token) @@ -64,40 +61,3 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { return resp.Result().(*OauthSuccessResponse), nil } - -// FetchUserOauthCredentials is responsible for fetching the user credentials from gitlab for alredy started Oauth process (access_token, refresh_token) -func FetchOauthToken(ctx context.Context, code string) (*oauth2.Token, error) { - gitLabConfig := config.GetConfig().Gitlab - f := logrus.Fields{ - "functionName": "gitlab.auth.FetchUserOauthCredentials", - "code": code, - "redirectURI": "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback", - } - - if len(gitLabConfig.AppClientID) > 4 { - f["gitLabClientID"] = fmt.Sprintf("%s...%s", gitLabConfig.AppClientID[0:4], gitLabConfig.AppClientID[len(gitLabConfig.AppClientID)-4:]) - } else { - return nil, errors.New("gitlab application client ID value is not set - value is empty or malformed") - } - if len(gitLabConfig.AppClientSecret) > 4 { - f["gitLabClientSecret"] = fmt.Sprintf("%s...%s", gitLabConfig.AppClientSecret[0:4], gitLabConfig.AppClientSecret[len(gitLabConfig.AppClientSecret)-4:]) - } else { - return nil, errors.New("gitlab application client secret value is not set - value is empty or malformed") - } - - // For info on this authorization flow, see: https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow - oauth2Config := oauth2.Config{ - ClientID: gitLabConfig.AppClientID, - ClientSecret: gitLabConfig.AppClientSecret, - Endpoint: gitlab.Endpoint, - } - - log.WithFields(f).Debugf("Getting token ...") - token, err := oauth2Config.Exchange(ctx, code) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to fetch token object") - return nil, err - } - - return token, nil -} diff --git a/cla-backend-go/v2/gitlab-activity/handlers.go b/cla-backend-go/v2/gitlab-activity/handlers.go index cf082497c..38803310b 100644 --- a/cla-backend-go/v2/gitlab-activity/handlers.go +++ b/cla-backend-go/v2/gitlab-activity/handlers.go @@ -239,7 +239,7 @@ func Configure(api *operations.EasyclaAPI, service Service, gitlabOrgRepo gitlab } log.WithFields(f).Debug("Fetching access token for user...") - token, err := gitlab_api.FetchOauthToken(ctx, guocp.Code) + token, err := gitlab_api.FetchOauthCredentials(guocp.Code) if err != nil { msg := fmt.Sprint("unable to fetch access token for user") log.WithFields(f).Warn(msg) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 84ce7823c..bb2a65ff7 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -32,10 +32,16 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_organizations" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime/middleware" + "github.com/savaki/dynastore" +) + +const ( + // SessionStoreKey for cla-gitlab + SessionStoreKey = "cla-gitlab" ) // Configure setups handlers on api with service -func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventService events.Service) { +func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventService events.Service, sessionStore *dynastore.Store, contributorConsoleV2Base string) { api.GitlabOrganizationsGetProjectGitlabOrganizationsHandler = gitlab_organizations.GetProjectGitlabOrganizationsHandlerFunc( func(params gitlab_organizations.GetProjectGitlabOrganizationsParams, authUser *auth.User) middleware.Responder { @@ -422,6 +428,80 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return NewServerError(reqID, "", errors.New(msg)) } + if codeParts[0] == "user" { + // handle authorization + return middleware.ResponderFunc( + func(rw http.ResponseWriter, p runtime.Producer) { + session, err := sessionStore.Get(params.HTTPRequest, SessionStoreKey) + if err != nil { + log.WithFields(f).WithError(err).Warn("error with session store lookup") + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + state, ok := session.Values["gitlab_oauth2_state"].(string) + if !ok { + log.WithFields(f).Warn("Error getting session state - missing from session object") + http.Error(rw, "no session state", http.StatusInternalServerError) + return + } + + gitlabOriginURL, ok := session.Values["gitlab_origin_url"].(string) + if !ok { + log.WithFields(f).Warn("Error getting gitlab_origin_url - missing from session object") + http.Error(rw, "no return url", http.StatusInternalServerError) + return + } + + repositoryID, ok := session.Values["gitab_repository_id"].(string) + if !ok { + log.WithFields(f).Warn("Error getting gitlab_repository_id - missing from session object") + http.Error(rw, "no return url", http.StatusInternalServerError) + return + } + + mergeRequestID, ok := session.Values["gitlab_merge_request_id"].(string) + if !ok { + log.WithFields(f).Warn("Error getting gitlab_merge_request_id - missing from session object") + http.Error(rw, "no return url", http.StatusInternalServerError) + return + } + + if params.State != state { + msg := fmt.Sprintf("mismatch state, received: %s from callback, but loaded our state as: %s", + params.State, state) + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + log.WithFields(f).Debug("Fetching access token for user...") + token, err := gitlabApi.FetchOauthCredentials(params.Code) + if err != nil { + msg := fmt.Sprint("unable to fetch access token for user") + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + session.Values["gitlab_oauth2_token"] = token.AccessToken + session.Save(params.HTTPRequest, rw) + + // Get client + gitlabClient, err := gitlabApi.NewGitlabOauthClientFromAccessToken(token.AccessToken) + if err != nil { + msg := fmt.Sprintf("unable to create gitlab client from token : %s ", token.AccessToken) + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + + consoleURL, err := service.InitiateSignRequest(ctx, params.HTTPRequest, gitlabClient, repositoryID, mergeRequestID, gitlabOriginURL, contributorConsoleV2Base, eventService) + log.WithFields(f).Debugf("redirecting to :%s ", *consoleURL) + http.Redirect(rw, params.HTTPRequest, *consoleURL, http.StatusSeeOther) + }) + } + gitlabOrganizationID := codeParts[0] stateVar := codeParts[1] diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 7a0e69188..83f428135 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -5,14 +5,19 @@ package gitlab_organizations import ( "context" + "encoding/json" "errors" "fmt" "io" + "net/http" "net/url" "sort" "strconv" "strings" + "time" + + "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/communitybridge/easycla/cla-backend-go/v2/repositories" "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -21,13 +26,16 @@ import ( gitlabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/go-openapi/strfmt" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" projectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/communitybridge/easycla/cla-backend-go/events" v2Models "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/communitybridge/easycla/cla-backend-go/utils" v2ProjectService "github.com/communitybridge/easycla/cla-backend-go/v2/project-service" + "github.com/communitybridge/easycla/cla-backend-go/v2/store" "github.com/sirupsen/logrus" goGitLab "github.com/xanzy/go-gitlab" ) @@ -50,6 +58,7 @@ type ServiceInterface interface { UpdateGitLabOrganization(ctx context.Context, input *common.GitLabAddOrganization) error UpdateGitLabOrganizationAuth(ctx context.Context, gitLabOrganizationID string, oauthResp *gitlabApi.OauthSuccessResponse) error DeleteGitLabOrganizationByFullPath(ctx context.Context, projectSFID string, gitlabOrgFullPath string) error + InitiateSignRequest(ctx context.Context, req *http.Request, gitlabClient *goGitLab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) (*string, error) } // Service data model @@ -58,15 +67,18 @@ type Service struct { v2GitRepoService repositories.ServiceInterface claGroupRepository projects_cla_groups.Repository gitLabApp *gitlabApi.App + storeRepo store.Repository + userService users.Service } // NewService creates a new gitlab organization service -func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceInterface, claGroupRepository projects_cla_groups.Repository) ServiceInterface { +func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceInterface, claGroupRepository projects_cla_groups.Repository, storeRepo store.Repository, userService users.Service) ServiceInterface { return &Service{ repo: repo, v2GitRepoService: v2GitRepoService, claGroupRepository: claGroupRepository, gitLabApp: gitlabApi.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), + userService: userService, } } @@ -643,6 +655,121 @@ func (s *Service) DeleteGitLabOrganizationByFullPath(ctx context.Context, projec return s.repo.DeleteGitLabOrganizationByFullPath(ctx, projectSFID, gitLabOrgFullPath) } +// InitiateSignRequest initiates sign request and returns easy cla redirect url +func (s *Service) InitiateSignRequest(ctx context.Context, req *http.Request, gitlabClient *goGitLab.Client, repositoryID, mergeRequestID, originURL, contributorBaseURL string, eventService events.Service) (*string, error) { + f := logrus.Fields{ + "functionName": "v2.gitlab_organizations.service.InitiateSignRequest", + "repositoryID": repositoryID, + "mergeRequestID": mergeRequestID, + "originURL": originURL, + } + + claUser, err := s.getOrCreateUser(ctx, gitlabClient, eventService) + if err != nil { + msg := fmt.Sprintf("unable to get or create user : %+v ", err) + log.WithFields(f).Warn(msg) + return nil, err + } + + repoIDInt, err := strconv.Atoi(repositoryID) + if err != nil { + msg := fmt.Sprintf("unable to convert GitlabRepoID: %s to int", repositoryID) + log.WithFields(f).Warn(msg) + return nil, err + } + + log.WithFields(f).Debugf("getting gitlab repository for: %d", repoIDInt) + gitlabRepo, err := s.v2GitRepoService.GitLabGetRepositoryByExternalID(ctx, int64(repoIDInt)) + if err != nil { + msg := fmt.Sprintf("unable to find repository by ID: %s , error: %+v ", repositoryID, err) + log.WithFields(f).Warn(msg) + return nil, err + } + + type StoreValue struct { + UserID string `json:"user_id"` + ProjectID string `json:"project_id"` + RepositoryID string `json:"repository_id"` + MergeRequestID string `json:"merge_request_id"` + ReturnURL string `json:"return_url"` + } + + log.WithFields(f).Debugf("setting active signature metadata: claUser: %+v, repository: %+v", claUser, gitlabRepo) + // set active signature metadata to track the user signing process + key := fmt.Sprintf("active_signature:%s", claUser.UserID) + storeValue := StoreValue{ + UserID: claUser.UserID, + ProjectID: gitlabRepo.RepositoryClaGroupID, + RepositoryID: repositoryID, + MergeRequestID: mergeRequestID, + ReturnURL: originURL, + } + json_data, err := json.Marshal(storeValue) + if err != nil { + log.Fatal(err) + } + expire := time.Now().AddDate(0, 0, 1).Unix() + + // jsonVal, _ := json.Marshal(value) + + err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) + if err != nil { + log.WithFields(f).WithError(err).Warn("unable to save signature metadata") + } + + params := "redirect=" + url.QueryEscape(originURL) + consoleURL := fmt.Sprintf("https://%s/#/cla/project/%s/user/%s?%s", contributorBaseURL, gitlabRepo.RepositoryClaGroupID, claUser.UserID, params) + _, err = http.Get(consoleURL) + + if err != nil { + msg := fmt.Sprintf("unable to redirect to : %s , error: %+v ", consoleURL, err) + log.WithFields(f).Warn(msg) + return nil, err + } + + return &consoleURL, nil +} + +func (s *Service) getOrCreateUser(ctx context.Context, gitlabClient *goGitLab.Client, eventsService events.Service) (*models.User, error) { + + f := logrus.Fields{ + "functionName": "v2.gitlab_sign.service.getOrCreateUser", + } + + gitlabUser, _, err := gitlabClient.Users.CurrentUser() + if err != nil { + log.WithFields(f).Debugf("getting gitlab current user for failed : %v ", err) + return nil, err + } + + claUser, err := s.userService.GetUserByGitlabID(gitlabUser.ID) + if err != nil { + log.WithFields(f).Debugf("unable to get CLA user by github ID: %d , error: %+v ", gitlabUser.ID, err) + log.WithFields(f).Infof("creating user record for gitlab user : %+v ", gitlabUser) + user := &models.User{ + GitlabID: fmt.Sprintf("%d", gitlabUser.ID), + GitlabUsername: gitlabUser.Username, + Emails: []string{gitlabUser.Email}, + Username: gitlabUser.Name, + } + claUser, userErr := s.userService.CreateUser(user, nil) + if err != nil { + log.WithFields(f).Debugf("unable to create claUser with details : %+v, error: %+v", user, userErr) + return nil, userErr + } + + // Log the event + eventsService.LogEvent(&events.LogEventArgs{ + EventType: events.UserCreated, + UserID: user.UserID, + UserModel: user, + EventData: &events.UserCreatedEventData{}, + }) + return claUser, nil + } + return claUser, nil +} + func buildInstallationURL(gitlabOrgID string, authStateNonce string) *strfmt.URI { base := "https://gitlab.com/oauth/authorize" c := config.GetConfig() diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index e872b0edf..1c0eb8f58 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -92,27 +92,22 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. log.WithFields(f).Debugf("No existing GitLab Oauth2 Token ") log.WithFields(f).Debug("initiating gitlab sign request ...") - state, err := uuid.NewV4() + stateID, err := uuid.NewV4() + state := fmt.Sprintf("user:%s", stateID.String()) session.Values["gitlab_oauth2_state"] = state session.Save(srp.HTTPRequest, rw) oauthConfig := &oauth2.Config{ ClientID: config.Gitlab.AppClientID, Scopes: []string{ - "api", "read_user", - "read_api", - "read_repository", - "write_repository", "email", }, Endpoint: oauth_gitlab.Endpoint, - RedirectURL: "https://api-gw.dev.platform.linuxfoundation.org/cla-service/v4/gitlab/user/oauth/callback", + RedirectURL: config.Gitlab.RedirectURI, } - - log.WithFields(f).Debug("initiating gitlab sign request ...") - session.Values["gitlab_oauth2_state"] = state.String() + session.Values["gitlab_oauth2_state"] = state session.Save(srp.HTTPRequest, rw) - http.Redirect(rw, srp.HTTPRequest, oauthConfig.AuthCodeURL(state.String()), http.StatusFound) + http.Redirect(rw, srp.HTTPRequest, oauthConfig.AuthCodeURL(state), http.StatusFound) }) }) From 2b69889b1ed7c1de03d1e9f1ded12db6bcef8f17 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 20 Sep 2021 17:03:14 +0300 Subject: [PATCH 0528/1276] [#3297] Lint Issues - Resolved lint issues Signed-off-by: Harold Wanyama --- cla-backend-go/gitlab_api/auth.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cla-backend-go/gitlab_api/auth.go b/cla-backend-go/gitlab_api/auth.go index e46075bc2..41336e61b 100644 --- a/cla-backend-go/gitlab_api/auth.go +++ b/cla-backend-go/gitlab_api/auth.go @@ -61,4 +61,3 @@ func FetchOauthCredentials(code string) (*OauthSuccessResponse, error) { return resp.Result().(*OauthSuccessResponse), nil } - From 5b750b9b79307bb000f0d87754850b3c0909b40b Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 20 Sep 2021 12:48:44 -0700 Subject: [PATCH 0529/1276] [#3297] Bug/Gitlab Session update (#3302) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 1 + cla-backend-go/v2/gitlab_sign/handlers.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index bb2a65ff7..b9c776a86 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -438,6 +438,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic http.Error(rw, err.Error(), http.StatusInternalServerError) return } + log.WithFields(f).Debugf("Loaded session: %+v", session) state, ok := session.Values["gitlab_oauth2_state"].(string) if !ok { diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index 1c0eb8f58..29fd5bc33 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -61,6 +61,15 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. session.Values["gitlab_repository_id"] = srp.GitlabRepositoryID session.Values["gitlab_merge_request_id"] = srp.MergeRequestID + originURL, err := service.GetOriginURL(ctx, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID) + if err != nil { + log.WithFields(f).WithError(err).Warn("error getting origin URL") + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + session.Values["gitlab_origin_url"] = *originURL + gitlabAuthToken, ok := session.Values["gitlab_oauth2_token"].(string) if ok { session.Save(srp.HTTPRequest, rw) @@ -75,8 +84,6 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. log.WithFields(f).Debugf("Initiating Gitlab sign request for : %+v ", srp) - originURL, err := service.GetOriginURL(ctx, srp.OrganizationID, srp.GitlabRepositoryID, srp.MergeRequestID) - consoleURL, err := service.InitiateSignRequest(ctx, srp.HTTPRequest, gitlabClient, srp.GitlabRepositoryID, srp.MergeRequestID, *originURL, contributorConsoleV2Base, eventService) if err != nil { From a7b8518f3de8a55ff68020318ac4ac9aefe4435e Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 20 Sep 2021 14:47:57 -0700 Subject: [PATCH 0530/1276] Bug/GitLab Session auth flow (#3303) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index b9c776a86..109dbc946 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -10,6 +10,7 @@ import ( "net/url" "regexp" "strings" + "strconv" "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -454,14 +455,14 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return } - repositoryID, ok := session.Values["gitab_repository_id"].(string) + repositoryID, ok := session.Values["gitab_repository_id"].(int) if !ok { log.WithFields(f).Warn("Error getting gitlab_repository_id - missing from session object") http.Error(rw, "no return url", http.StatusInternalServerError) return } - mergeRequestID, ok := session.Values["gitlab_merge_request_id"].(string) + mergeRequestID, ok := session.Values["gitlab_merge_request_id"].(int) if !ok { log.WithFields(f).Warn("Error getting gitlab_merge_request_id - missing from session object") http.Error(rw, "no return url", http.StatusInternalServerError) @@ -497,7 +498,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return } - consoleURL, err := service.InitiateSignRequest(ctx, params.HTTPRequest, gitlabClient, repositoryID, mergeRequestID, gitlabOriginURL, contributorConsoleV2Base, eventService) + consoleURL, err := service.InitiateSignRequest(ctx, params.HTTPRequest, gitlabClient, strconv.Itoa(repositoryID), strconv.Itoa(mergeRequestID), gitlabOriginURL, contributorConsoleV2Base, eventService) log.WithFields(f).Debugf("redirecting to :%s ", *consoleURL) http.Redirect(rw, params.HTTPRequest, *consoleURL, http.StatusSeeOther) }) From 45269fa5770932f466ad965c2182fa57555bdd24 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 20 Sep 2021 15:28:48 -0700 Subject: [PATCH 0531/1276] [3304] - Default Template Updates - Address Line 3 (#3305) --- cla-backend-go/template/repository.go | 8 ++++---- cla-backend-go/v2/gitlab_organizations/handlers.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-backend-go/template/repository.go b/cla-backend-go/template/repository.go index 9dc15efed..574563488 100644 --- a/cla-backend-go/template/repository.go +++ b/cla-backend-go/template/repository.go @@ -524,7 +524,7 @@ var templateMap = map[string]models.Template{ ID: "mailing_address3", Name: "Mailing Address", AnchorString: "Mailing Address:", - FieldType: "text_unlocked", + FieldType: "text_optional", IsOptional: true, IsEditable: false, Width: 340, @@ -682,7 +682,7 @@ var templateMap = map[string]models.Template{ ID: "corporation_address3", Name: "Corporation Address3", AnchorString: "Corporation Address:", - FieldType: "text_unlocked", + FieldType: "text_optional", IsOptional: true, IsEditable: true, Width: 350, @@ -849,7 +849,7 @@ var templateMap = map[string]models.Template{ ID: "mailing_address3", Name: "Mailing Address", AnchorString: "Mailing Address:", - FieldType: "text_unlocked", + FieldType: "text_optional", IsOptional: true, IsEditable: false, Width: 340, @@ -1007,7 +1007,7 @@ var templateMap = map[string]models.Template{ ID: "corporation_address3", Name: "Corporation Address3", AnchorString: "Corporation Address:", - FieldType: "text_unlocked", + FieldType: "text_optional", IsOptional: true, IsEditable: true, Width: 350, diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 109dbc946..faa6decb7 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -9,8 +9,8 @@ import ( "net/http" "net/url" "regexp" - "strings" "strconv" + "strings" "github.com/communitybridge/easycla/cla-backend-go/v2/common" From 98bab545e101815cb80ed286e147dd2c86ce4441 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 20 Sep 2021 15:42:10 -0700 Subject: [PATCH 0532/1276] Bug/GitLab Session Repo ID (#3306) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index faa6decb7..dc15ed405 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -455,7 +455,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return } - repositoryID, ok := session.Values["gitab_repository_id"].(int) + repositoryID, ok := session.Values["gitlab_repository_id"].(int) if !ok { log.WithFields(f).Warn("Error getting gitlab_repository_id - missing from session object") http.Error(rw, "no return url", http.StatusInternalServerError) From 90ac155cf632018d153a6ed78eedc1b148901c88 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 21 Sep 2021 13:06:24 +0300 Subject: [PATCH 0533/1276] [#3297] Bug/GitLab Auth Flow - Resolved session map query for GitLab oauth2 callback Signed-off-by: Harold Wanyama --- .../v2/gitlab_organizations/handlers.go | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index dc15ed405..61c7dc330 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -9,7 +9,6 @@ import ( "net/http" "net/url" "regexp" - "strconv" "strings" "github.com/communitybridge/easycla/cla-backend-go/v2/common" @@ -439,33 +438,37 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic http.Error(rw, err.Error(), http.StatusInternalServerError) return } - log.WithFields(f).Debugf("Loaded session: %+v", session) + log.WithFields(f).Debugf("Loaded session: %+v", session.Values) state, ok := session.Values["gitlab_oauth2_state"].(string) if !ok { - log.WithFields(f).Warn("Error getting session state - missing from session object") - http.Error(rw, "no session state", http.StatusInternalServerError) + msg := "Error getting session state - missing from session object" + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) return } gitlabOriginURL, ok := session.Values["gitlab_origin_url"].(string) if !ok { - log.WithFields(f).Warn("Error getting gitlab_origin_url - missing from session object") - http.Error(rw, "no return url", http.StatusInternalServerError) + msg := "Error getting gitlab_origin_url - missing from session object" + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) return } - repositoryID, ok := session.Values["gitlab_repository_id"].(int) + repositoryID, ok := session.Values["gitlab_repository_id"].(string) if !ok { - log.WithFields(f).Warn("Error getting gitlab_repository_id - missing from session object") - http.Error(rw, "no return url", http.StatusInternalServerError) + msg := "Error getting gitlab_repository_id - missing from session object" + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) return } - mergeRequestID, ok := session.Values["gitlab_merge_request_id"].(int) + mergeRequestID, ok := session.Values["gitlab_merge_request_id"].(string) if !ok { - log.WithFields(f).Warn("Error getting gitlab_merge_request_id - missing from session object") - http.Error(rw, "no return url", http.StatusInternalServerError) + msg := "Error getting gitlab_merge_request_id - missing from session object" + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) return } @@ -498,7 +501,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return } - consoleURL, err := service.InitiateSignRequest(ctx, params.HTTPRequest, gitlabClient, strconv.Itoa(repositoryID), strconv.Itoa(mergeRequestID), gitlabOriginURL, contributorConsoleV2Base, eventService) + consoleURL, err := service.InitiateSignRequest(ctx, params.HTTPRequest, gitlabClient, repositoryID, mergeRequestID, gitlabOriginURL, contributorConsoleV2Base, eventService) log.WithFields(f).Debugf("redirecting to :%s ", *consoleURL) http.Redirect(rw, params.HTTPRequest, *consoleURL, http.StatusSeeOther) }) From 5cdfe0593fc467337dddd6f0bb9eede68184384b Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 21 Sep 2021 15:56:56 +0300 Subject: [PATCH 0534/1276] [#3297] Bug/Initiate Sign - Added logging and improved error handling for gitlab sign flow Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab_organizations/service.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 83f428135..a2772b1a9 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -704,17 +704,24 @@ func (s *Service) InitiateSignRequest(ctx context.Context, req *http.Request, gi MergeRequestID: mergeRequestID, ReturnURL: originURL, } + + log.WithFields(f).Debugf("active signature metadata: %+v", storeValue) + json_data, err := json.Marshal(storeValue) if err != nil { - log.Fatal(err) + msg := fmt.Sprintf("unable to marshall storeValue object: %+v", storeValue) + log.WithFields(f).Warn(msg) + return nil, err } expire := time.Now().AddDate(0, 0, 1).Unix() + log.WithFields(f).Debugf("setting expiry for active signature data to : %d", expire) // jsonVal, _ := json.Marshal(value) err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) if err != nil { log.WithFields(f).WithError(err).Warn("unable to save signature metadata") + return nil, err } params := "redirect=" + url.QueryEscape(originURL) From 4fe8a8a985b109d0bd4e465fb37cd0920d651eeb Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 21 Sep 2021 08:38:39 -0700 Subject: [PATCH 0535/1276] Bug/Pointer Derefecence - Sign (#3309) --- cla-backend-go/v2/gitlab_organizations/service.go | 8 +++----- cla-backend-go/v2/gitlab_sign/service.go | 7 ++++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index a2772b1a9..2ba282210 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -716,12 +716,10 @@ func (s *Service) InitiateSignRequest(ctx context.Context, req *http.Request, gi expire := time.Now().AddDate(0, 0, 1).Unix() log.WithFields(f).Debugf("setting expiry for active signature data to : %d", expire) - // jsonVal, _ := json.Marshal(value) - - err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) - if err != nil { + activeSigErr := s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) + if activeSigErr != nil { log.WithFields(f).WithError(err).Warn("unable to save signature metadata") - return nil, err + return nil, activeSigErr } params := "redirect=" + url.QueryEscape(originURL) diff --git a/cla-backend-go/v2/gitlab_sign/service.go b/cla-backend-go/v2/gitlab_sign/service.go index 602c9553e..e7ada8196 100644 --- a/cla-backend-go/v2/gitlab_sign/service.go +++ b/cla-backend-go/v2/gitlab_sign/service.go @@ -151,9 +151,10 @@ func (s service) InitiateSignRequest(ctx context.Context, req *http.Request, git // jsonVal, _ := json.Marshal(value) - err = s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) - if err != nil { - log.WithFields(f).WithError(err).Warn("unable to save signature metadata") + activeSignErr := s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) + if activeSignErr != nil { + log.WithFields(f).WithError(activeSignErr).Warn("unable to save signature metadata") + return nil, activeSignErr } params := "redirect=" + url.QueryEscape(originURL) From c72c9ccd81934e3b90a4388c7ca7602176090e94 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 21 Sep 2021 10:38:19 -0700 Subject: [PATCH 0536/1276] Axios Lib Update (#3310) --- cla-backend-go/package.json | 3 +- cla-backend-go/yarn.lock | 1009 +--------------- cla-backend/package.json | 3 +- cla-backend/serverless.yml | 1 - cla-backend/yarn.lock | 1018 +---------------- cla-frontend-contributor-console/package.json | 2 +- cla-frontend-corporate-console/package.json | 2 +- cla-frontend-project-console/package.json | 2 +- cla-landing-page/package.json | 3 + cla-landing-page/yarn.lock | 18 +- package.json | 2 +- yarn.lock | 18 +- 12 files changed, 60 insertions(+), 2021 deletions(-) diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index 441334768..fd5690af3 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -22,12 +22,11 @@ "serverless": "^2.57.0", "serverless-finch": "^2.6.0", "serverless-layers": "^2.5.1", - "serverless-offline": "^8.0.0", "serverless-plugin-tracing": "^2.0.0", "serverless-prune-plugin": "^1.5.1" }, "resolutions": { - "axios": "^0.21.2", + "axios": "^0.21.4", "glob-parent": "^5.1.2", "ini": "^1.3.7", "normalize-url": "^4.5.1", diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index 20e2dac43..a04058007 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -27,266 +27,6 @@ wraptile "^2.0.0" zames "^2.0.0" -"@hapi/accept@^5.0.1": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" - integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/ammo@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-5.0.1.tgz#9d34560f5c214eda563d838c01297387efaab490" - integrity sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/b64@5.x.x": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" - integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/boom@9.x.x", "@hapi/boom@^9.1.0", "@hapi/boom@^9.1.2": - version "9.1.4" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" - integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/bounce@2.x.x", "@hapi/bounce@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-2.0.0.tgz#e6ef56991c366b1e2738b2cd83b01354d938cf3d" - integrity sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/bourne@2.x.x": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" - integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== - -"@hapi/call@^8.0.0": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@hapi/call/-/call-8.0.1.tgz#9e64cd8ba6128eb5be6e432caaa572b1ed8cd7c0" - integrity sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/catbox-memory@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz#cb63fca0ded01d445a2573b38eb2688df67f70ac" - integrity sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/catbox@^11.1.1": - version "11.1.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-11.1.1.tgz#d277e2d5023fd69cddb33d05b224ea03065fec0c" - integrity sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/podium" "4.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/content@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-5.0.2.tgz#ae57954761de570392763e64cdd75f074176a804" - integrity sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw== - dependencies: - "@hapi/boom" "9.x.x" - -"@hapi/cryptiles@5.x.x": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" - integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== - dependencies: - "@hapi/boom" "9.x.x" - -"@hapi/file@2.x.x": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/file/-/file-2.0.0.tgz#2ecda37d1ae9d3078a67c13b7da86e8c3237dfb9" - integrity sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ== - -"@hapi/h2o2@^9.0.2": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.1.0.tgz#b223f4978b6f2b0d7d9db10a84a567606c4c3551" - integrity sha512-B7E58bMhxmpiDI22clxTexoAaVShNBk1Ez6S8SQjQZu5FxxD6Tqa44sXeZQBtWrdJF7ZRbsY60/C8AHLRxagNA== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - "@hapi/wreck" "17.x.x" - -"@hapi/hapi@^20.1.3": - version "20.1.5" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.1.5.tgz#235dbc6bcc72960724696028c5145c0ecfe6962d" - integrity sha512-BhJ5XFR9uWPUBj/z5pPqXSk8OnvQQU/EbQjwpmjZy0ymNEiq7kIhXkAmzXcntbBHta9o7zpW8XMeXnfV4wudXw== - dependencies: - "@hapi/accept" "^5.0.1" - "@hapi/ammo" "^5.0.1" - "@hapi/boom" "^9.1.0" - "@hapi/bounce" "^2.0.0" - "@hapi/call" "^8.0.0" - "@hapi/catbox" "^11.1.1" - "@hapi/catbox-memory" "^5.0.0" - "@hapi/heavy" "^7.0.1" - "@hapi/hoek" "^9.0.4" - "@hapi/mimos" "^6.0.0" - "@hapi/podium" "^4.1.1" - "@hapi/shot" "^5.0.5" - "@hapi/somever" "^3.0.0" - "@hapi/statehood" "^7.0.3" - "@hapi/subtext" "^7.0.3" - "@hapi/teamwork" "^5.1.0" - "@hapi/topo" "^5.0.0" - "@hapi/validate" "^1.1.1" - -"@hapi/heavy@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-7.0.1.tgz#73315ae33b6e7682a0906b7a11e8ca70e3045874" - integrity sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" - integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== - -"@hapi/iron@6.x.x": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" - integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== - dependencies: - "@hapi/b64" "5.x.x" - "@hapi/boom" "9.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/cryptiles" "5.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/mimos@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-6.0.0.tgz#daa523d9c07222c7e8860cb7c9c5501fd6506484" - integrity sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg== - dependencies: - "@hapi/hoek" "9.x.x" - mime-db "1.x.x" - -"@hapi/nigel@4.x.x": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-4.0.2.tgz#8f84ef4bca4fb03b2376463578f253b0b8e863c4" - integrity sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw== - dependencies: - "@hapi/hoek" "^9.0.4" - "@hapi/vise" "^4.0.0" - -"@hapi/pez@^5.0.1": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-5.0.3.tgz#b75446e6fef8cbb16816573ab7da1b0522e7a2a1" - integrity sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA== - dependencies: - "@hapi/b64" "5.x.x" - "@hapi/boom" "9.x.x" - "@hapi/content" "^5.0.2" - "@hapi/hoek" "9.x.x" - "@hapi/nigel" "4.x.x" - -"@hapi/podium@4.x.x", "@hapi/podium@^4.1.1": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" - integrity sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g== - dependencies: - "@hapi/hoek" "9.x.x" - "@hapi/teamwork" "5.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/shot@^5.0.5": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-5.0.5.tgz#a25c23d18973bec93c7969c51bf9579632a5bebd" - integrity sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A== - dependencies: - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/somever@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-3.0.1.tgz#9961cd5bdbeb5bb1edc0b2acdd0bb424066aadcc" - integrity sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w== - dependencies: - "@hapi/bounce" "2.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/statehood@^7.0.3": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-7.0.3.tgz#655166f3768344ed3c3b50375a303cdeca8040d9" - integrity sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bounce" "2.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/cryptiles" "5.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/iron" "6.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/subtext@^7.0.3": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-7.0.3.tgz#f7440fc7c966858e1f39681e99eb6171c71e7abd" - integrity sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/content" "^5.0.2" - "@hapi/file" "2.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/pez" "^5.0.1" - "@hapi/wreck" "17.x.x" - -"@hapi/teamwork@5.x.x", "@hapi/teamwork@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124" - integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" - integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - -"@hapi/vise@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-4.0.0.tgz#c6a94fe121b94a53bf99e7489f7fcc74c104db02" - integrity sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/wreck@17.x.x": - version "17.1.0" - resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" - integrity sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/hoek" "9.x.x" - "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -713,11 +453,6 @@ dependencies: "@types/node" "*" -"@types/retry@^0.12.0": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" - integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== - "@types/tough-cookie@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" @@ -1202,7 +937,7 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sdk@^2.834.0, aws-sdk@^2.979.0: +aws-sdk@^2.979.0: version "2.980.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== @@ -1227,7 +962,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.21.1, axios@^0.21.2: +axios@^0.21.1, axios@^0.21.4: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -1316,7 +1051,7 @@ bluebird@^3.4.1, bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -boxen@^5.0.0, boxen@^5.0.1: +boxen@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== @@ -1363,11 +1098,6 @@ buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3, buffer-crc32@~0. resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -1448,14 +1178,6 @@ cachedir@^2.3.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - camelcase@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" @@ -1571,11 +1293,6 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - ci-info@^3.1.1, ci-info@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -1829,18 +1546,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -1927,14 +1632,6 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" -cron-parser@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" - integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== - dependencies: - is-nan "^1.3.0" - moment-timezone "^0.5.31" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -1946,25 +1643,6 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -cuid@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0" - integrity sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg== - currify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/currify/-/currify-3.0.0.tgz#ec5b18fe65c2b3b08daba7f2a75a01063b2c89c2" @@ -2011,13 +1689,6 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^3.1.1: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -2118,13 +1789,6 @@ deferred@^0.7.11: next-tick "^1.0.0" timers-ext "^0.1.7" -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -2188,13 +1852,6 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - dot-qs@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dot-qs/-/dot-qs-0.2.0.tgz#d36517fe24b7cda61fce7a5026a0024afaf5a439" @@ -2246,13 +1903,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -2315,38 +1965,6 @@ error-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= -es-abstract@^1.18.0-next.2: - version "1.18.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" - integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.2" - internal-slot "^1.0.3" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.3" - is-string "^1.0.6" - object-inspect "^1.11.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.47, es5-ext@^0.10.49, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" @@ -2407,11 +2025,6 @@ es6-weak-map@^2.0.2, es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2443,31 +2056,11 @@ event-emitter@^0.3.5, event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - exit-on-epipe@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" @@ -2521,7 +2114,7 @@ extend-shallow@^2.0.0, extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2925,15 +2518,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" @@ -2961,7 +2545,7 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -3009,13 +2593,6 @@ glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" - integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== - dependencies: - ini "2.0.0" - global-modules@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" @@ -3110,11 +2687,6 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -3144,28 +2716,11 @@ has-glob@^0.1.1: dependencies: is-glob "^2.0.1" -has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -3210,11 +2765,6 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - iconv-lite@^0.4.24, iconv-lite@~0.4.11: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3242,11 +2792,6 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3275,7 +2820,7 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@2.0.0, ini@^1.3.4, ini@^1.3.7, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.7, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== @@ -3334,15 +2879,6 @@ install@^0.13.0: resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776" integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA== -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - is-absolute@^0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" @@ -3370,13 +2906,6 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3384,31 +2913,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-buffer@^1.1.0, is-buffer@^1.1.3, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -3423,13 +2932,6 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -3504,44 +3006,11 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-nan@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" - integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - -is-number-object@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" - integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== - dependencies: - has-tostringtag "^1.0.0" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3559,16 +3028,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -3591,14 +3050,6 @@ is-promise@^2.1, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-relative@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" @@ -3611,25 +3062,6 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3669,11 +3101,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - is@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" @@ -3721,21 +3148,11 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -java-invoke-local@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/java-invoke-local/-/java-invoke-local-0.0.6.tgz#0e04b20b5e306a1e8384846a9ac286790ee6d868" - integrity sha512-gZmQKe1QrfkkMjCn8Qv9cpyJFyogTYqkP5WCobX5RNaHsJzIV/6NvAnlnouOcwKr29QrxLGDGcqYuJ+ae98s1A== - jmespath@0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= -js-string-escape@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" - integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= - js-yaml@^3.13.1, js-yaml@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" @@ -3824,32 +3241,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonpath-plus@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz#2fc4b2e461950626c98525425a3a3518b85af6c3" - integrity sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ== - -jsonschema@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" - integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== - -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3860,7 +3251,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.5.0, jszip@^3.7.1: +jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -3870,23 +3261,6 @@ jszip@^3.5.0, jszip@^3.7.1: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" @@ -3961,13 +3335,6 @@ kuler@1.0.x: dependencies: colornames "^1.1.1" -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -4009,41 +3376,11 @@ lodash.flatten@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -4098,11 +3435,6 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" -long-timeout@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" - integrity sha1-lyHXiLR+C8taJMLivuGg2lXatRQ= - long@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/long/-/long-1.1.2.tgz#eaef5951ca7551d96926b82da242db9d6b28fb53" @@ -4137,11 +3469,6 @@ lru-queue@0.1, lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" -luxon@^1.25.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" - integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== - make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -4149,20 +3476,13 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-dir@^3.0.0, make-dir@^3.1.0: +make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -4190,14 +3510,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^6.0.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/mem/-/mem-6.1.1.tgz#ea110c2ebc079eca3022e6b08c85a795e77f6318" - integrity sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^3.0.0" - memoizee@^0.4.14: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" @@ -4226,11 +3538,6 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -4262,7 +3569,7 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-db@1.49.0, mime-db@1.x.x: +mime-db@1.49.0: version "1.49.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== @@ -4301,11 +3608,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" - integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== - mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -4351,7 +3653,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -4363,18 +3665,6 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment-timezone@^0.5.31: - version "0.5.33" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" - integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0": - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4480,15 +3770,6 @@ node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-schedule@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.3.tgz#f8e01c5fb9597f09ecf9c4c25d6938e5e7a06f48" - integrity sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw== - dependencies: - cron-parser "^2.18.0" - long-timeout "0.1.1" - sorted-array-functions "^1.3.0" - node.extend@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" @@ -4512,13 +3793,6 @@ normalize-url@^4.1.0, normalize-url@^4.5.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -4558,16 +3832,6 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-inspect@^1.11.0, object-inspect@^1.9.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" - integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -4575,26 +3839,6 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.fromentries@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" - integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4614,7 +3858,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0, onetime@^5.1.2: +onetime@^5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -4662,11 +3906,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-event@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" @@ -4679,31 +3918,7 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-memoize@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/p-memoize/-/p-memoize-4.0.1.tgz#6f4231857fec10de2504611fe820c808fa8c5f8b" - integrity sha512-km0sP12uE0dOZ5qP+s7kGVf07QngxyG0gS8sYFvFWhqlgzOsSy+m71aUejf/0akxj5W7gE//2G74qTv6b4iMog== - dependencies: - mem "^6.0.1" - mimic-fn "^3.0.0" - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-retry@^4.3.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" - integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== - dependencies: - "@types/retry" "^0.12.0" - retry "^0.13.1" - -p-timeout@^3.1.0, p-timeout@^3.2.0: +p-timeout@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== @@ -4750,11 +3965,6 @@ path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-loader@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/path-loader/-/path-loader-1.0.10.tgz#dd3d1bd54cb6f2e6423af2ad334a41cc0bce4cf6" @@ -4825,27 +4035,11 @@ pipe-io@^3.0.0: resolved "https://registry.yarnpkg.com/pipe-io/-/pipe-io-3.0.12.tgz#90ff84888876a1feccbf9f753eacf22b260b2884" integrity sha512-reR49NtpkVgedzCQ9DPV727VAZKw8Ax3N/3iQwD1vHxTmswsuhurFh0Z5woVNM1OhHDigKzDN7u4kNipAA9yyA== -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - pointer-symbol@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pointer-symbol/-/pointer-symbol-1.0.0.tgz#60f9110204ea7a929b62644a21315543cbb3d447" integrity sha1-YPkRAgTqepKbYmRKITFVQ8uz1Ec= -portfinder@^1.0.28: - version "1.0.28" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" - integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.5" - prebuild-install@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" @@ -5038,13 +4232,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" @@ -5303,11 +4490,6 @@ retry@^0.10.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -5374,24 +4556,12 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +semver@^5.4.1, semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -5438,41 +4608,6 @@ serverless-layers@^2.5.1: semver "^7.3.2" slugify "^1.4.0" -serverless-offline@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/serverless-offline/-/serverless-offline-8.0.0.tgz#66ffb69d8f631b096957b9cc657cc5c66600f1b8" - integrity sha512-ov5PbO8gbT+KtHK0rA6NiYnNdzeVaCt8FLn9sJGN2nPvuyTbemJ5i0eOeOOX8Wn9LsHIIyYuxGa+tQM353M8wg== - dependencies: - "@hapi/boom" "^9.1.2" - "@hapi/h2o2" "^9.0.2" - "@hapi/hapi" "^20.1.3" - aws-sdk "^2.834.0" - boxen "^5.0.0" - chalk "^4.1.0" - cuid "^2.1.8" - execa "^5.0.0" - extend "^3.0.2" - fs-extra "^9.1.0" - java-invoke-local "0.0.6" - js-string-escape "^1.0.1" - jsonpath-plus "^5.0.2" - jsonschema "^1.4.0" - jsonwebtoken "^8.5.1" - jszip "^3.5.0" - luxon "^1.25.0" - node-fetch "^2.6.1" - node-schedule "^1.3.3" - object.fromentries "^2.0.3" - p-memoize "^4.0.1" - p-queue "^6.6.2" - p-retry "^4.3.0" - please-upgrade-node "^3.2.0" - portfinder "^1.0.28" - semver "^7.3.4" - update-notifier "^5.0.1" - velocityjs "^2.0.3" - ws "^7.4.2" - serverless-plugin-tracing@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/serverless-plugin-tracing/-/serverless-plugin-tracing-2.0.0.tgz#df6b8b3166ac9bb70a37c7fc875014b2369158f6" @@ -5594,23 +4729,11 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - shortid@^2.2.14: version "2.2.15" resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122" @@ -5618,15 +4741,6 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -5721,11 +4835,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sorted-array-functions@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" - integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== - split2@^3.1.1: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -5827,22 +4936,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5910,11 +5003,6 @@ strip-dirs@^2.0.0: dependencies: is-natural-number "^4.0.1" -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -6243,16 +5331,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - which-boxed-primitive "^1.0.2" - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6266,13 +5344,6 @@ unc-path-regex@^0.1.0: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -6298,26 +5369,6 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-notifier@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - uri-js@^4.2.2: version "4.4.0" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" @@ -6367,11 +5418,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -velocityjs@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/velocityjs/-/velocityjs-2.0.3.tgz#cc772f687061997127b7d8a827dbef3af8a0bbe6" - integrity sha512-sUkygY7HwvbKZvS3naiI7t2o4RTqui6efSwTXLb03igdvPKm3SwCpnqA2kU4/jLD2f0eHB9xPoiza9XAkpuU+g== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -6395,17 +5441,6 @@ warning-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" integrity sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE= -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" @@ -6418,13 +5453,6 @@ which@^1.2.12, which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -6498,7 +5526,7 @@ write-file-atomic@^2.4.3: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: +write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== @@ -6508,16 +5536,11 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.3.1, ws@^7.4.2, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: +ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" diff --git a/cla-backend/package.json b/cla-backend/package.json index c4d8e5d64..850defbe3 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -37,14 +37,13 @@ "serverless-domain-manager": "^5.1.0", "serverless-finch": "^2.6.0", "serverless-layers": "^2.5.1", - "serverless-offline": "^8.0.0", "serverless-plugin-tracing": "^2.0.0", "serverless-prune-plugin": "^1.5.1", "serverless-python-requirements": "^5.1.1", "serverless-wsgi": "^2.0.1" }, "resolutions": { - "axios": "^0.21.2", + "axios": "^0.21.4", "glob-parent": "^5.1.2", "ini": "^1.3.7", "node-fetch": "^2.6.1", diff --git a/cla-backend/serverless.yml b/cla-backend/serverless.yml index c2164613b..3c6afa147 100644 --- a/cla-backend/serverless.yml +++ b/cla-backend/serverless.yml @@ -386,7 +386,6 @@ plugins: # To avoid a Code Storage Limit after tons of deploys and revisions - we can prune old versions # This plugin allows us to remove/prune the old versions either manually or automatically - serverless-prune-plugin - - serverless-offline - serverless-domain-manager functions: diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 70e1a9a38..89335c172 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -27,266 +27,6 @@ wraptile "^2.0.0" zames "^2.0.0" -"@hapi/accept@^5.0.1": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" - integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/ammo@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-5.0.1.tgz#9d34560f5c214eda563d838c01297387efaab490" - integrity sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/b64@5.x.x": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" - integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/boom@9.x.x", "@hapi/boom@^9.1.0", "@hapi/boom@^9.1.2": - version "9.1.4" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" - integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/bounce@2.x.x", "@hapi/bounce@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-2.0.0.tgz#e6ef56991c366b1e2738b2cd83b01354d938cf3d" - integrity sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/bourne@2.x.x": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" - integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== - -"@hapi/call@^8.0.0": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@hapi/call/-/call-8.0.1.tgz#9e64cd8ba6128eb5be6e432caaa572b1ed8cd7c0" - integrity sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/catbox-memory@^5.0.0": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz#cb63fca0ded01d445a2573b38eb2688df67f70ac" - integrity sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/catbox@^11.1.1": - version "11.1.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-11.1.1.tgz#d277e2d5023fd69cddb33d05b224ea03065fec0c" - integrity sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/podium" "4.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/content@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-5.0.2.tgz#ae57954761de570392763e64cdd75f074176a804" - integrity sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw== - dependencies: - "@hapi/boom" "9.x.x" - -"@hapi/cryptiles@5.x.x": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" - integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== - dependencies: - "@hapi/boom" "9.x.x" - -"@hapi/file@2.x.x": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/file/-/file-2.0.0.tgz#2ecda37d1ae9d3078a67c13b7da86e8c3237dfb9" - integrity sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ== - -"@hapi/h2o2@^9.0.2": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.1.0.tgz#b223f4978b6f2b0d7d9db10a84a567606c4c3551" - integrity sha512-B7E58bMhxmpiDI22clxTexoAaVShNBk1Ez6S8SQjQZu5FxxD6Tqa44sXeZQBtWrdJF7ZRbsY60/C8AHLRxagNA== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - "@hapi/wreck" "17.x.x" - -"@hapi/hapi@^20.1.3": - version "20.1.5" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.1.5.tgz#235dbc6bcc72960724696028c5145c0ecfe6962d" - integrity sha512-BhJ5XFR9uWPUBj/z5pPqXSk8OnvQQU/EbQjwpmjZy0ymNEiq7kIhXkAmzXcntbBHta9o7zpW8XMeXnfV4wudXw== - dependencies: - "@hapi/accept" "^5.0.1" - "@hapi/ammo" "^5.0.1" - "@hapi/boom" "^9.1.0" - "@hapi/bounce" "^2.0.0" - "@hapi/call" "^8.0.0" - "@hapi/catbox" "^11.1.1" - "@hapi/catbox-memory" "^5.0.0" - "@hapi/heavy" "^7.0.1" - "@hapi/hoek" "^9.0.4" - "@hapi/mimos" "^6.0.0" - "@hapi/podium" "^4.1.1" - "@hapi/shot" "^5.0.5" - "@hapi/somever" "^3.0.0" - "@hapi/statehood" "^7.0.3" - "@hapi/subtext" "^7.0.3" - "@hapi/teamwork" "^5.1.0" - "@hapi/topo" "^5.0.0" - "@hapi/validate" "^1.1.1" - -"@hapi/heavy@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-7.0.1.tgz#73315ae33b6e7682a0906b7a11e8ca70e3045874" - integrity sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" - integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== - -"@hapi/iron@6.x.x": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" - integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== - dependencies: - "@hapi/b64" "5.x.x" - "@hapi/boom" "9.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/cryptiles" "5.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/mimos@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-6.0.0.tgz#daa523d9c07222c7e8860cb7c9c5501fd6506484" - integrity sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg== - dependencies: - "@hapi/hoek" "9.x.x" - mime-db "1.x.x" - -"@hapi/nigel@4.x.x": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-4.0.2.tgz#8f84ef4bca4fb03b2376463578f253b0b8e863c4" - integrity sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw== - dependencies: - "@hapi/hoek" "^9.0.4" - "@hapi/vise" "^4.0.0" - -"@hapi/pez@^5.0.1": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-5.0.3.tgz#b75446e6fef8cbb16816573ab7da1b0522e7a2a1" - integrity sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA== - dependencies: - "@hapi/b64" "5.x.x" - "@hapi/boom" "9.x.x" - "@hapi/content" "^5.0.2" - "@hapi/hoek" "9.x.x" - "@hapi/nigel" "4.x.x" - -"@hapi/podium@4.x.x", "@hapi/podium@^4.1.1": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" - integrity sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g== - dependencies: - "@hapi/hoek" "9.x.x" - "@hapi/teamwork" "5.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/shot@^5.0.5": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-5.0.5.tgz#a25c23d18973bec93c7969c51bf9579632a5bebd" - integrity sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A== - dependencies: - "@hapi/hoek" "9.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/somever@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-3.0.1.tgz#9961cd5bdbeb5bb1edc0b2acdd0bb424066aadcc" - integrity sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w== - dependencies: - "@hapi/bounce" "2.x.x" - "@hapi/hoek" "9.x.x" - -"@hapi/statehood@^7.0.3": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-7.0.3.tgz#655166f3768344ed3c3b50375a303cdeca8040d9" - integrity sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bounce" "2.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/cryptiles" "5.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/iron" "6.x.x" - "@hapi/validate" "1.x.x" - -"@hapi/subtext@^7.0.3": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-7.0.3.tgz#f7440fc7c966858e1f39681e99eb6171c71e7abd" - integrity sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/content" "^5.0.2" - "@hapi/file" "2.x.x" - "@hapi/hoek" "9.x.x" - "@hapi/pez" "^5.0.1" - "@hapi/wreck" "17.x.x" - -"@hapi/teamwork@5.x.x", "@hapi/teamwork@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124" - integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" - integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - -"@hapi/vise@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-4.0.0.tgz#c6a94fe121b94a53bf99e7489f7fcc74c104db02" - integrity sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg== - dependencies: - "@hapi/hoek" "9.x.x" - -"@hapi/wreck@17.x.x": - version "17.1.0" - resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" - integrity sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw== - dependencies: - "@hapi/boom" "9.x.x" - "@hapi/bourne" "2.x.x" - "@hapi/hoek" "9.x.x" - "@iarna/toml@^2.2.5": version "2.2.5" resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" @@ -718,11 +458,6 @@ dependencies: "@types/node" "*" -"@types/retry@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - "@types/tough-cookie@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" @@ -1232,7 +967,7 @@ aws-sdk@^2.756.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.834.0, aws-sdk@^2.979.0: +aws-sdk@^2.979.0: version "2.980.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== @@ -1257,7 +992,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.21.1, axios@^0.21.2: +axios@^0.21.1, axios@^0.21.4: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -1346,7 +1081,7 @@ bluebird@^3.4.1, bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -boxen@^5.0.0, boxen@^5.0.1: +boxen@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== @@ -1393,11 +1128,6 @@ buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3, buffer-crc32@~0. resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -1478,14 +1208,6 @@ cachedir@^2.3.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -1606,11 +1328,6 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - ci-info@^3.1.1, ci-info@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -1873,18 +1590,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -1971,14 +1676,6 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" -cron-parser@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" - integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== - dependencies: - is-nan "^1.3.0" - moment-timezone "^0.5.31" - cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -1990,25 +1687,6 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -cuid@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0" - integrity sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg== - currify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/currify/-/currify-3.0.0.tgz#ec5b18fe65c2b3b08daba7f2a75a01063b2c89c2" @@ -2048,7 +1726,7 @@ debug@^2.1.3, debug@^2.6.6, debug@^2.6.8: dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0, debug@^3.1.1: +debug@^3.0.1, debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2160,13 +1838,6 @@ deferred@^0.7.11: next-tick "^1.0.0" timers-ext "^0.1.7" -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -2230,13 +1901,6 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - dot-qs@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dot-qs/-/dot-qs-0.2.0.tgz#d36517fe24b7cda61fce7a5026a0024afaf5a439" @@ -2288,13 +1952,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -2357,38 +2014,6 @@ error-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= -es-abstract@^1.18.0-next.2: - version "1.18.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" - integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.2" - internal-slot "^1.0.3" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.3" - is-string "^1.0.6" - object-inspect "^1.11.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.47, es5-ext@^0.10.49, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" @@ -2449,11 +2074,6 @@ es6-weak-map@^2.0.2, es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2485,31 +2105,11 @@ event-emitter@^0.3.5, event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - exit-on-epipe@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" @@ -2563,7 +2163,7 @@ extend-shallow@^2.0.0, extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2980,15 +2580,6 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" @@ -3016,7 +2607,7 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -3072,13 +2663,6 @@ glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" - integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== - dependencies: - ini "2.0.0" - global-modules@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" @@ -3173,11 +2757,6 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -3207,33 +2786,11 @@ has-glob@^0.1.1: dependencies: is-glob "^2.0.1" -has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -3285,11 +2842,6 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - iconv-lite@^0.4.24, iconv-lite@~0.4.11: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3317,11 +2869,6 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3350,7 +2897,7 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@2.0.0, ini@^1.3.4, ini@^1.3.7, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.7, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== @@ -3409,15 +2956,6 @@ install@^0.13.0: resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776" integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA== -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - is-absolute@^0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" @@ -3445,13 +2983,6 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -3459,36 +2990,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-buffer@^1.1.0, is-buffer@^1.1.3, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== - -is-callable@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -3503,11 +3009,6 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -3582,43 +3083,11 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-nan@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03" - integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ== - dependencies: - define-properties "^1.1.3" - is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - -is-number-object@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" - integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== - dependencies: - has-tostringtag "^1.0.0" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3636,16 +3105,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -3668,14 +3127,6 @@ is-promise@^2.1, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-relative@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" @@ -3688,32 +3139,6 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-string@^1.0.5, is-string@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3753,11 +3178,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - is@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" @@ -3805,21 +3225,11 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -java-invoke-local@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/java-invoke-local/-/java-invoke-local-0.0.6.tgz#0e04b20b5e306a1e8384846a9ac286790ee6d868" - integrity sha512-gZmQKe1QrfkkMjCn8Qv9cpyJFyogTYqkP5WCobX5RNaHsJzIV/6NvAnlnouOcwKr29QrxLGDGcqYuJ+ae98s1A== - jmespath@0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= -js-string-escape@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" - integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= - js-yaml@^3.13.1, js-yaml@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" @@ -3908,32 +3318,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonpath-plus@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-5.1.0.tgz#2fc4b2e461950626c98525425a3a3518b85af6c3" - integrity sha512-890w2Pjtj0iswAxalRlt2kHthi6HKrXEfZcn+ZNZptv7F3rUGIeDuZo+C+h4vXBHLEsVjJrHeCm35nYeZLzSBQ== - -jsonschema@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" - integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== - -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3944,7 +3328,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.5.0, jszip@^3.6.0, jszip@^3.7.1: +jszip@^3.6.0, jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -3954,23 +3338,6 @@ jszip@^3.5.0, jszip@^3.6.0, jszip@^3.7.1: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" @@ -4045,13 +3412,6 @@ kuler@1.0.x: dependencies: colornames "^1.1.1" -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -4105,41 +3465,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -4209,11 +3539,6 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" -long-timeout@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" - integrity sha1-lyHXiLR+C8taJMLivuGg2lXatRQ= - long@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/long/-/long-1.1.2.tgz#eaef5951ca7551d96926b82da242db9d6b28fb53" @@ -4248,11 +3573,6 @@ lru-queue@0.1, lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" -luxon@^1.25.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" - integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== - make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -4260,20 +3580,13 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-dir@^3.0.0, make-dir@^3.1.0: +make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -4301,14 +3614,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^6.0.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/mem/-/mem-6.1.1.tgz#ea110c2ebc079eca3022e6b08c85a795e77f6318" - integrity sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^3.0.0" - memoizee@^0.4.14: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" @@ -4337,11 +3642,6 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -4378,7 +3678,7 @@ mime-db@1.49.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== -mime-db@1.x.x, mime-db@^1.28.0: +mime-db@^1.28.0: version "1.45.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== @@ -4412,11 +3712,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" - integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== - mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -4462,7 +3757,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1, mkdirp@^0.5.5: +mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -4474,18 +3769,6 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment-timezone@^0.5.31: - version "0.5.31" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05" - integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0": - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4591,15 +3874,6 @@ node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-schedule@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.3.tgz#f8e01c5fb9597f09ecf9c4c25d6938e5e7a06f48" - integrity sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw== - dependencies: - cron-parser "^2.18.0" - long-timeout "0.1.1" - sorted-array-functions "^1.3.0" - node.extend@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" @@ -4623,13 +3897,6 @@ normalize-url@^4.1.0, normalize-url@^4.5.1: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -4669,16 +3936,6 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-inspect@^1.11.0, object-inspect@^1.9.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" - integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -4686,26 +3943,6 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.fromentries@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" - integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4725,7 +3962,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0, onetime@^5.1.2: +onetime@^5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -4773,11 +4010,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-event@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" @@ -4804,31 +4036,7 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-memoize@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/p-memoize/-/p-memoize-4.0.1.tgz#6f4231857fec10de2504611fe820c808fa8c5f8b" - integrity sha512-km0sP12uE0dOZ5qP+s7kGVf07QngxyG0gS8sYFvFWhqlgzOsSy+m71aUejf/0akxj5W7gE//2G74qTv6b4iMog== - dependencies: - mem "^6.0.1" - mimic-fn "^3.0.0" - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-retry@^4.3.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" - integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== - dependencies: - "@types/retry" "^0.12.0" - retry "^0.13.1" - -p-timeout@^3.1.0, p-timeout@^3.2.0: +p-timeout@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== @@ -4885,11 +4093,6 @@ path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-loader@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/path-loader/-/path-loader-1.0.10.tgz#dd3d1bd54cb6f2e6423af2ad334a41cc0bce4cf6" @@ -4960,27 +4163,11 @@ pipe-io@^3.0.0: resolved "https://registry.yarnpkg.com/pipe-io/-/pipe-io-3.0.12.tgz#90ff84888876a1feccbf9f753eacf22b260b2884" integrity sha512-reR49NtpkVgedzCQ9DPV727VAZKw8Ax3N/3iQwD1vHxTmswsuhurFh0Z5woVNM1OhHDigKzDN7u4kNipAA9yyA== -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - pointer-symbol@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pointer-symbol/-/pointer-symbol-1.0.0.tgz#60f9110204ea7a929b62644a21315543cbb3d447" integrity sha1-YPkRAgTqepKbYmRKITFVQ8uz1Ec= -portfinder@^1.0.28: - version "1.0.28" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" - integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.5" - prebuild-install@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" @@ -5173,13 +4360,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - qrcode-terminal@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" @@ -5448,11 +4628,6 @@ retry@^0.10.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -5526,24 +4701,12 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +semver@^5.4.1, semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -5598,41 +4761,6 @@ serverless-layers@^2.5.1: semver "^7.3.2" slugify "^1.4.0" -serverless-offline@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/serverless-offline/-/serverless-offline-8.0.0.tgz#66ffb69d8f631b096957b9cc657cc5c66600f1b8" - integrity sha512-ov5PbO8gbT+KtHK0rA6NiYnNdzeVaCt8FLn9sJGN2nPvuyTbemJ5i0eOeOOX8Wn9LsHIIyYuxGa+tQM353M8wg== - dependencies: - "@hapi/boom" "^9.1.2" - "@hapi/h2o2" "^9.0.2" - "@hapi/hapi" "^20.1.3" - aws-sdk "^2.834.0" - boxen "^5.0.0" - chalk "^4.1.0" - cuid "^2.1.8" - execa "^5.0.0" - extend "^3.0.2" - fs-extra "^9.1.0" - java-invoke-local "0.0.6" - js-string-escape "^1.0.1" - jsonpath-plus "^5.0.2" - jsonschema "^1.4.0" - jsonwebtoken "^8.5.1" - jszip "^3.5.0" - luxon "^1.25.0" - node-fetch "^2.6.1" - node-schedule "^1.3.3" - object.fromentries "^2.0.3" - p-memoize "^4.0.1" - p-queue "^6.6.2" - p-retry "^4.3.0" - please-upgrade-node "^3.2.0" - portfinder "^1.0.28" - semver "^7.3.4" - update-notifier "^5.0.1" - velocityjs "^2.0.3" - ws "^7.4.2" - serverless-plugin-tracing@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/serverless-plugin-tracing/-/serverless-plugin-tracing-2.0.0.tgz#df6b8b3166ac9bb70a37c7fc875014b2369158f6" @@ -5789,23 +4917,11 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - shell-quote@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" @@ -5818,15 +4934,6 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -5921,11 +5028,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sorted-array-functions@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz#8605695563294dffb2c9796d602bd8459f7a0dd5" - integrity sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA== - split2@^3.1.1: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -6027,22 +5129,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -6110,11 +5196,6 @@ strip-dirs@^2.0.0: dependencies: is-natural-number "^4.0.1" -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -6443,16 +5524,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - which-boxed-primitive "^1.0.2" - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6466,13 +5537,6 @@ unc-path-regex@^0.1.0: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -6498,26 +5562,6 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-notifier@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - uri-js@^4.2.2: version "4.4.0" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" @@ -6567,11 +5611,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -velocityjs@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/velocityjs/-/velocityjs-2.0.3.tgz#cc772f687061997127b7d8a827dbef3af8a0bbe6" - integrity sha512-sUkygY7HwvbKZvS3naiI7t2o4RTqui6efSwTXLb03igdvPKm3SwCpnqA2kU4/jLD2f0eHB9xPoiza9XAkpuU+g== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -6595,17 +5634,6 @@ warning-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" integrity sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE= -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -6623,13 +5651,6 @@ which@^1.2.12, which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -6712,7 +5733,7 @@ write-file-atomic@^2.4.3: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: +write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== @@ -6722,16 +5743,11 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.3.1, ws@^7.4.2, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: +ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" diff --git a/cla-frontend-contributor-console/package.json b/cla-frontend-contributor-console/package.json index e1f83fdd7..b8f0ac3f8 100644 --- a/cla-frontend-contributor-console/package.json +++ b/cla-frontend-contributor-console/package.json @@ -22,7 +22,7 @@ "serverless-pseudo-parameters": "^2.5.0" }, "resolutions": { - "axios": "^0.21.2", + "axios": "^0.21.4", "bl": "^2.2.1", "braces": "^2.3.1", "glob-parent": "^5.1.2", diff --git a/cla-frontend-corporate-console/package.json b/cla-frontend-corporate-console/package.json index 81dfea51f..8975f6848 100644 --- a/cla-frontend-corporate-console/package.json +++ b/cla-frontend-corporate-console/package.json @@ -22,7 +22,7 @@ "serverless-pseudo-parameters": "^2.5.0" }, "resolutions": { - "axios": "^0.21.2", + "axios": "^0.21.4", "bl": "^1.2.3", "braces": "^2.3.1", "http-proxy": "^1.18.1", diff --git a/cla-frontend-project-console/package.json b/cla-frontend-project-console/package.json index b75a9349c..44ad817f4 100644 --- a/cla-frontend-project-console/package.json +++ b/cla-frontend-project-console/package.json @@ -22,7 +22,7 @@ "serverless-pseudo-parameters": "^2.5.0" }, "resolutions": { - "axios": "^0.21.2", + "axios": "^0.21.4", "bl": "^1.2.3", "braces": "^2.3.1", "glob-parent": "^5.1.2", diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 803b3459f..c12e4d1b9 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -87,5 +87,8 @@ "ts-node": "~8.3.0", "tslint": "~6.1.0", "typescript": "~3.8.3" + }, + "resolutions": { + "axios": "^0.21.4" } } diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 0f5e1d3eb..a7795164a 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -2502,12 +2502,12 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.21.1, axios@^0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" axobject-query@2.0.2: version "2.0.2" @@ -5437,10 +5437,10 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -follow-redirects@^1.10.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b" - integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== for-in@^0.1.3: version "0.1.8" diff --git a/package.json b/package.json index fda74960c..3c6a55214 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "pretty-quick": "^2.0.1" }, "resolutions": { - "axios": "^0.21.1", + "axios": "^0.21.4", "ini": "^1.3.7", "netmask": "^2.0.1", "normalize-url": "^4.5.1", diff --git a/yarn.lock b/yarn.lock index fd1445b25..0f54c6ff2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -832,12 +832,12 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.19.2, axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== +axios@^0.19.2, axios@^0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" backo2@1.0.2: version "1.0.2" @@ -2067,10 +2067,10 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -follow-redirects@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.14.0: + version "1.14.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" + integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== forever-agent@~0.6.1: version "0.6.1" From 80a142b49bf8d9cbbed9a35733cb0fd074071ec3 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 21 Sep 2021 15:13:19 -0700 Subject: [PATCH 0537/1276] Bug/Derefence (#3312) --- cla-backend-go/v2/gitlab_organizations/service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index 2ba282210..bb08465ca 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -79,6 +79,7 @@ func NewService(repo RepositoryInterface, v2GitRepoService repositories.ServiceI claGroupRepository: claGroupRepository, gitLabApp: gitlabApi.Init(config.GetConfig().Gitlab.AppClientID, config.GetConfig().Gitlab.AppClientSecret, config.GetConfig().Gitlab.AppPrivateKey), userService: userService, + storeRepo: storeRepo, } } @@ -715,6 +716,7 @@ func (s *Service) InitiateSignRequest(ctx context.Context, req *http.Request, gi } expire := time.Now().AddDate(0, 0, 1).Unix() log.WithFields(f).Debugf("setting expiry for active signature data to : %d", expire) + log.WithFields(f).Debugf("json data: %s", string(json_data)) activeSigErr := s.storeRepo.SetActiveSignatureMetaData(ctx, key, expire, string(json_data)) if activeSigErr != nil { From aa4565a5680b68ed195f518f7c8eba8bd22f49d1 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Wed, 22 Sep 2021 10:43:17 +0300 Subject: [PATCH 0538/1276] [#3297] Bug/GitLab Client Sign - Resolved sign flow for existing user session Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab_sign/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_sign/handlers.go b/cla-backend-go/v2/gitlab_sign/handlers.go index 29fd5bc33..591dfa2c7 100644 --- a/cla-backend-go/v2/gitlab_sign/handlers.go +++ b/cla-backend-go/v2/gitlab_sign/handlers.go @@ -12,13 +12,13 @@ import ( "github.com/communitybridge/easycla/cla-backend-go/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/restapi/operations/gitlab_sign" + gitlabApi "github.com/communitybridge/easycla/cla-backend-go/gitlab_api" "github.com/communitybridge/easycla/cla-backend-go/utils" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" "github.com/savaki/dynastore" "github.com/sirupsen/logrus" - "github.com/xanzy/go-gitlab" "golang.org/x/oauth2" oauth_gitlab "golang.org/x/oauth2/gitlab" @@ -74,7 +74,7 @@ func Configure(api *operations.EasyclaAPI, service Service, eventService events. if ok { session.Save(srp.HTTPRequest, rw) log.WithFields(f).Debugf("using existing Gitlab Ouath2 Token: %s ", gitlabAuthToken) - gitlabClient, err := gitlab.NewClient(gitlabAuthToken) + gitlabClient, err := gitlabApi.NewGitlabOauthClientFromAccessToken(gitlabAuthToken) if err != nil { msg := fmt.Sprintf("problem creating gitlab client with token : %s ", gitlabAuthToken) From b18e4dbd8b42dc8827f20e209345aec7e1e2aa17 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 22 Sep 2021 15:02:51 -0700 Subject: [PATCH 0539/1276] Bug/GitLab Config Auto Enable (#3314) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 61c7dc330..bfb2ea0dc 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -279,8 +279,8 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic // Load the project parent parentProjectModel, err := psc.GetParentProjectModel(params.ProjectSFID) if err != nil || parentProjectModel == nil { - return gitlab_organizations.NewAddProjectGitlabOrganizationForbidden().WithPayload( - utils.ErrorResponseNotFound(reqID, fmt.Sprintf("unable to locate parent project from project with ID: %s", params.ProjectSFID))) + msg := fmt.Sprintf("unable to locate parent project from project with ID: %s", params.ProjectSFID) + log.WithFields(f).Warn(msg) } if !utils.IsUserAuthorizedForProjectTree(ctx, authUser, params.ProjectSFID, utils.ALLOW_ADMIN_SCOPE) { @@ -298,7 +298,6 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic inputModel := &common.GitLabAddOrganization{ ProjectSFID: params.ProjectSFID, - ParentProjectSFID: parentProjectModel.ID, AutoEnabled: params.Body.AutoEnabled, AutoEnabledClaGroupID: params.Body.AutoEnabledClaGroupID, BranchProtectionEnabled: params.Body.BranchProtectionEnabled, @@ -306,6 +305,10 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic Enabled: true, } + if parentProjectModel != nil { + inputModel.ParentProjectSFID = parentProjectModel.ID + } + updateErr := service.UpdateGitLabOrganization(ctx, inputModel) if updateErr != nil { if errors.Is(updateErr, projects_cla_groups.ErrCLAGroupDoesNotExist) { From 1578dee2d837107f5622eeb3e2beb120a2646aee Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 24 Sep 2021 10:54:26 -0700 Subject: [PATCH 0540/1276] Resolved Create GitLab User Issue (#3322) --- cla-backend-go/v2/gitlab_organizations/service.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/service.go b/cla-backend-go/v2/gitlab_organizations/service.go index bb08465ca..d21571f6d 100644 --- a/cla-backend-go/v2/gitlab_organizations/service.go +++ b/cla-backend-go/v2/gitlab_organizations/service.go @@ -759,9 +759,11 @@ func (s *Service) getOrCreateUser(ctx context.Context, gitlabClient *goGitLab.Cl Emails: []string{gitlabUser.Email}, Username: gitlabUser.Name, } - claUser, userErr := s.userService.CreateUser(user, nil) - if err != nil { - log.WithFields(f).Debugf("unable to create claUser with details : %+v, error: %+v", user, userErr) + + var userErr error + claUser, userErr = s.userService.CreateUser(user, nil) + if userErr != nil { + log.WithFields(f).WithError(userErr).Warnf("unable to create user with details : %+v", user) return nil, userErr } From d6f43a3dc9a51f46dff4ca771f4645625ead424e Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 29 Sep 2021 09:14:49 -0700 Subject: [PATCH 0541/1276] Feature/GitLab Badge (#3323) --- cla-backend-go/v2/gitlab-activity/service.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 4b0dcc4e8..9c897b15b 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -236,8 +236,8 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git CLA Signed
    `, badgeHyperlink) failedBadge := fmt.Sprintf(` CLA Not Signed
    `, badgeHyperlink) - missingUserIDBadge := fmt.Sprintf(` -CLA Missing ID
    `, badgeHyperlink) + // missingUserIDBadge := fmt.Sprintf(` + // CLA Missing ID
    `, badgeHyperlink) confirmationNeededBadge := fmt.Sprintf(` CLA Confirmation Needed
    `, badgeHyperlink) @@ -257,21 +257,13 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git body = coveredBadge } - gitlabSupportURL := "https://about.gitlab.com/support" + // gitlabSupportURL := "https://about.gitlab.com/support" easyCLASupportURL := "https://jira.linuxfoundation.org/servicedesk/customer/portal/4" if len(missingUsers) > 0 { result += "
      " for _, missingUser := range missingUsers { authorInfo := getAuthorInfo(missingUser.User) - if errors.Is(missingUser.err, missingID) { - msg := fmt.Sprintf(`
    • %s The commit associated with %s is missing the User's ID, preventing the EasyCLA check. - Consult GitLab Help to resolve. - For further assistance with EasyCLA, - please submit a support request ticket. -
    • `, failed, authorInfo, gitlabSupportURL, easyCLASupportURL) - result += msg - body = missingUserIDBadge - } else if errors.Is(missingUser.err, missingCompanyAffiliation) { + if errors.Is(missingUser.err, missingCompanyAffiliation) { msg := fmt.Sprintf(`
    • %s is authorized, but they must confirm their affiliation with their company. Start the authorization process by clicking here, click "Corporate", From 557b46285cb6fdd3e326e6edb32c938d65914307 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 29 Sep 2021 12:37:57 -0700 Subject: [PATCH 0542/1276] [#3321] Added GitLab Text to Landing Page (#3324) --- cla-landing-page/package.json | 25 +- .../src/app/components/home/home.component.ts | 4 +- cla-landing-page/yarn.lock | 596 ++++++------------ 3 files changed, 229 insertions(+), 396 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index c12e4d1b9..b16794604 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -89,6 +89,29 @@ "typescript": "~3.8.3" }, "resolutions": { - "axios": "^0.21.4" + "axios": "^0.21.4", + "browserslist": "^4.16.5", + "dns-packet": "^5.2.2", + "elliptic": "^6.5.4", + "glob-parent": "^5.1.2", + "hosted-git-info": "^3.0.8", + "ini": "^1.3.6", + "jszip": "^3.7.0", + "lodash": "^4.17.21", + "normalize-url": "^6.0.1", + "pac-resolver": "^5.0.0", + "path-parse": "^1.0.7", + "postcss": "^8.2.10", + "set-getter": "^0.1.1", + "socket.io": "^2.4.0", + "socket.io-parser": "^3.4.1", + "ssri": "^8.0.1", + "tar": "^6.1.9", + "ua-parser-js": "^0.7.24", + "url-parse": "^1.5.2", + "ws": "^7.4.6", + "xmlhttprequest-ssl": "^1.6.1", + "y18n": "^5.0.5", + "yargs-parser": "^18.1.2" } } diff --git a/cla-landing-page/src/app/components/home/home.component.ts b/cla-landing-page/src/app/components/home/home.component.ts index 51727f976..5ac40f000 100644 --- a/cla-landing-page/src/app/components/home/home.component.ts +++ b/cla-landing-page/src/app/components/home/home.component.ts @@ -37,7 +37,7 @@ export class HomeComponent implements OnInit { title: 'Organizations', subtitle: 'Enable all your developers to contribute code easily and quickly while remaining compliant with contribution policies.', highlights: [ - 'Approved developers based on email, domain, GitHub handle, or GitHub organization', + 'Approved developers based on email, domain, GitHub handle, GitHub organization, GitLab handle, or GitLab Group', 'Enable your signatories and contributors to sign CLAs using DocuSign® electronic signatures', 'Enforce signing of the Corporate CLA by your developers without slowing them down with manual bureaucracy' ], @@ -49,7 +49,7 @@ export class HomeComponent implements OnInit { title: 'Developers', subtitle: 'Get started contributing code faster and with less friction.', highlights: [ - 'Receive an automatic notification in GitHub or Gerrit if you need to be authorized', + 'Receive an automatic notification in GitHub, GitLab or Gerrit if you need to be authorized', 'Sign your Individual CLA with an e-signature', 'Start contributing faster with a streamlined authorization workflow for Corporate CLAs' ], diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index a7795164a..cd927bec2 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1041,6 +1041,11 @@ resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0" + integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg== + "@ng-bootstrap/ng-bootstrap@^6.1.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-6.2.0.tgz#0506d612ca6002bd8fa398d006fa2641013e11d4" @@ -2377,10 +2382,10 @@ ast-types-flow@0.0.7: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= -ast-types@0.x.x: - version "0.14.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" - integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== +ast-types@^0.13.2: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== dependencies: tslib "^2.0.1" @@ -2389,11 +2394,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async@^2.5.0, async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -2548,11 +2548,6 @@ base64-arraybuffer@0.1.4: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= - base64-js@^1.0.2, base64-js@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -2588,13 +2583,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -2667,11 +2655,16 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.0, bluebird@^3. resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: +bn.js@^4.0.0, bn.js@^4.1.0: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + bn.js@^5.1.1: version "5.1.3" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" @@ -2760,7 +2753,7 @@ braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -2833,15 +2826,16 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.3, browserslist@^4.8.5, browserslist@^4.9.1: - version "4.14.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" - integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.5, browserslist@^4.8.3, browserslist@^4.8.5, browserslist@^4.9.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9" + integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ== dependencies: - caniuse-lite "^1.0.30001135" - electron-to-chromium "^1.3.571" - escalade "^3.1.0" - node-releases "^1.1.61" + caniuse-lite "^1.0.30001259" + electron-to-chromium "^1.3.846" + escalade "^3.1.1" + nanocolors "^0.1.5" + node-releases "^1.1.76" browserstack@^1.5.1: version "1.6.0" @@ -3084,11 +3078,6 @@ caller-path@^2.0.0: dependencies: caller-callsite "^2.0.0" -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" @@ -3114,11 +3103,16 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001135: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001032: version "1.0.30001148" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz#dc97c7ed918ab33bf8706ddd5e387287e015d637" integrity sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw== +caniuse-lite@^1.0.30001259: + version "1.0.30001261" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01" + integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA== + canonical-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d" @@ -3436,11 +3430,6 @@ clone@^2.1.1, clone@^2.1.2: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -3715,16 +3704,16 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - cookie@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookiejar@^2.1.0, cookiejar@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" @@ -4340,14 +4329,15 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -degenerator@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" - integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= +degenerator@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" + integrity sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ== dependencies: - ast-types "0.x.x" - escodegen "1.x.x" - esprima "3.x.x" + ast-types "^0.13.2" + escodegen "^1.8.1" + esprima "^4.0.0" + vm2 "^3.9.3" del@^2.2.0: version "2.2.2" @@ -4476,13 +4466,12 @@ dns-equal@^1.0.0: resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== +dns-packet@^1.3.1, dns-packet@^5.2.2: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.3.0.tgz#9a0f66118d3be176b828b911a842b0b1a4bdfd4f" + integrity sha512-Nce7YLu6YCgWRvOmDBsJMo9M5/jV3lEZ5vUWnWXYmwURvPylHvq7nkDWhNmk1ZQoZZOP7oQh/S0lSxbisKOfHg== dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" + "@leichtgewicht/ip-codec" "^2.0.1" dns-txt@^2.0.2: version "2.0.2" @@ -4605,23 +4594,23 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.571: - version "1.3.582" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.582.tgz#1adfac5affce84d85b3d7b3dfbc4ade293a6ffc4" - integrity sha512-0nCJ7cSqnkMC+kUuPs0YgklFHraWGl/xHqtZWWtOeVtyi+YqkoAOMGuZQad43DscXCQI/yizcTa3u6B5r+BLww== +electron-to-chromium@^1.3.846: + version "1.3.853" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82" + integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ== -elliptic@^6.5.3: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== +elliptic@^6.5.3, elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emoji-regex@^7.0.1: version "7.0.3" @@ -4681,6 +4670,23 @@ engine.io-client@~3.4.0: xmlhttprequest-ssl "~1.5.4" yeast "0.1.2" +engine.io-client@~3.5.0: + version "3.5.2" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.2.tgz#0ef473621294004e9ceebe73cef0af9e36f2f5fa" + integrity sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA== + dependencies: + component-emitter "~1.3.0" + component-inherit "0.0.3" + debug "~3.1.0" + engine.io-parser "~2.2.0" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.6" + parseuri "0.0.6" + ws "~7.4.2" + xmlhttprequest-ssl "~1.6.2" + yeast "0.1.2" + engine.io-parser@~2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7" @@ -4692,17 +4698,17 @@ engine.io-parser@~2.2.0: blob "0.0.5" has-binary2 "~1.0.2" -engine.io@~3.4.0: - version "3.4.2" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.4.2.tgz#8fc84ee00388e3e228645e0a7d3dfaeed5bd122c" - integrity sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg== +engine.io@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b" + integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA== dependencies: accepts "~1.3.4" base64id "2.0.0" - cookie "0.3.1" + cookie "~0.4.1" debug "~4.1.0" engine.io-parser "~2.2.0" - ws "^7.1.2" + ws "~7.4.2" enhanced-resolve@4.1.1: version "4.1.1" @@ -4882,7 +4888,7 @@ es6-weak-map@^2.0.2, es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escalade@^3.1.0: +escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== @@ -4897,7 +4903,7 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x: +escodegen@^1.8.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -4925,11 +4931,6 @@ esniff@^1.1.0: d "1" es5-ext "^0.10.12" -esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -5573,13 +5574,6 @@ fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -5754,22 +5748,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^3.1.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -6008,7 +5987,7 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -6017,15 +5996,10 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== - -hosted-git-info@^3.0.2, hosted-git-info@^3.0.6: - version "3.0.7" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.7.tgz#a30727385ea85acfcee94e0aad9e368c792e036c" - integrity sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ== +hosted-git-info@^2.1.4, hosted-git-info@^2.7.1, hosted-git-info@^3.0.2, hosted-git-info@^3.0.6, hosted-git-info@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== dependencies: lru-cache "^6.0.0" @@ -6349,10 +6323,10 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@1.3.5, ini@^1.3.4, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +ini@1.3.5, ini@^1.3.4, ini@^1.3.6, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== inquirer-autocomplete-prompt@^1.3.0: version "1.4.0" @@ -6447,7 +6421,7 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip@1.1.5, ip@^1.1.0, ip@^1.1.5: +ip@1.1.5, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= @@ -6603,7 +6577,7 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= @@ -6625,13 +6599,6 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -7137,17 +7104,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.1.3: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - set-immediate-shim "~1.0.1" - -jszip@^3.7.1: +jszip@^3.1.3, jszip@^3.7.0, jszip@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== @@ -7467,12 +7424,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.17.21: +lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7876,7 +7828,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= @@ -7914,7 +7866,7 @@ minipass-pipeline@^1.2.2: dependencies: minipass "^3.0.0" -minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: +minipass@^2.3.5: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -7929,13 +7881,6 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -7976,7 +7921,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1, mkdirp@~0.5.x: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1, mkdirp@~0.5.x: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -8048,11 +7993,26 @@ nan@^2.12.1, nan@^2.14.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nanocolors@^0.1.5: + version "0.1.12" + resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6" + integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ== + +nanocolors@^0.2.2: + version "0.2.12" + resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.2.12.tgz#4d05932e70116078673ea4cc6699a1c56cc77777" + integrity sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug== + nanoid@^2.1.0: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== +nanoid@^3.1.25: + version "3.1.28" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.28.tgz#3c01bac14cb6c5680569014cc65a2f26424c6bd4" + integrity sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -8121,10 +8081,10 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== -netmask@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= +netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== next-tick@1, next-tick@^1.0.0, next-tick@^1.1.0: version "1.1.0" @@ -8203,10 +8163,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-releases@^1.1.61: - version "1.1.64" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.64.tgz#71b4ae988e9b1dd7c1ffce58dd9e561752dfebc5" - integrity sha512-Iec8O9166/x2HRMJyLLLWkd0sFFLrFNy+Xf+JQfSQsdBJzPcHpNl3JQ9gD4j+aJxmCa25jNsIbM4bmACtSbkSg== +node-releases@^1.1.76: + version "1.1.76" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e" + integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA== noop-logger@^0.1.1: version "0.1.1" @@ -8240,25 +8200,10 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@1.9.1, normalize-url@^3.0.0, normalize-url@^4.1.0, normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== npm-bundled@^1.0.1: version "1.1.1" @@ -8391,11 +8336,6 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -8723,16 +8663,14 @@ pac-proxy-agent@^3.0.1: raw-body "^2.2.0" socks-proxy-agent "^4.0.1" -pac-resolver@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" - integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA== +pac-resolver@^3.0.0, pac-resolver@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" + integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA== dependencies: - co "^4.6.0" - degenerator "^1.0.4" + degenerator "^3.0.1" ip "^1.1.5" - netmask "^1.0.6" - thunkify "^2.1.2" + netmask "^2.0.1" package-json@^6.3.0: version "6.5.0" @@ -8818,25 +8756,11 @@ parse5@4.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" - parseqs@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - parseuri@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" @@ -8857,11 +8781,6 @@ path-browserify@0.0.1: resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -8895,10 +8814,10 @@ path-loader@^1.0.10: native-promise-only "^0.8.1" superagent "^3.8.3" -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" @@ -9348,23 +9267,14 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@7.0.27: - version "7.0.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== +postcss@7.0.27, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6, postcss@^8.2.10: + version "8.3.8" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.8.tgz#9ebe2a127396b4b4570ae9f7770e7fb83db2bac1" + integrity sha512-GT5bTjjZnwDifajzczOC+r3FI3Cu+PgPvrsjhQdRqa2kTJ4968/X9CUce9xttIB0xOs5c6xf0TCWZo/y9lF6bA== dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.35" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" - integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" + nanocolors "^0.2.2" + nanoid "^3.1.25" + source-map-js "^0.6.2" prebuild-install@5.3.0: version "5.3.0" @@ -9393,11 +9303,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -9719,14 +9624,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - query-string@^6.13.6: version "6.13.6" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.6.tgz#e5ac7c74f2a5da43fbca0b883b4f0bafba439966" @@ -10647,10 +10544,10 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= +set-getter@^0.1.0, set-getter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.1.tgz#a3110e1b461d31a9cfc8c5c9ee2e9737ad447102" + integrity sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw== dependencies: to-object-path "^0.3.0" @@ -10823,23 +10720,20 @@ socket.io-adapter@~1.1.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9" integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== -socket.io-client@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" - integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== +socket.io-client@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35" + integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ== dependencies: backo2 "1.0.2" - base64-arraybuffer "0.1.5" component-bind "1.0.0" - component-emitter "1.2.1" - debug "~4.1.0" - engine.io-client "~3.4.0" + component-emitter "~1.3.0" + debug "~3.1.0" + engine.io-client "~3.5.0" has-binary2 "~1.0.2" - has-cors "1.1.0" indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" + parseqs "0.0.6" + parseuri "0.0.6" socket.io-parser "~3.3.0" to-array "0.1.4" @@ -10860,16 +10754,7 @@ socket.io-client@^2.3.0: socket.io-parser "~3.3.0" to-array "0.1.4" -socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== - dependencies: - component-emitter "~1.3.0" - debug "~3.1.0" - isarray "2.0.1" - -socket.io-parser@~3.4.0: +socket.io-parser@^3.4.1, socket.io-parser@~3.3.0, socket.io-parser@~3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.1.tgz#b06af838302975837eab2dc980037da24054d64a" integrity sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A== @@ -10878,16 +10763,16 @@ socket.io-parser@~3.4.0: debug "~4.1.0" isarray "2.0.1" -socket.io@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb" - integrity sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg== +socket.io@^2.3.0, socket.io@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2" + integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w== dependencies: debug "~4.1.0" - engine.io "~3.4.0" + engine.io "~3.5.0" has-binary2 "~1.0.2" socket.io-adapter "~1.1.0" - socket.io-client "2.3.0" + socket.io-client "2.4.0" socket.io-parser "~3.4.0" sockjs-client@1.4.0: @@ -10946,6 +10831,11 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +source-map-js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" + integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== + source-map-loader@0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" @@ -11112,17 +11002,10 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -ssri@^6.0.0, ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== +ssri@^6.0.0, ssri@^6.0.1, ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" @@ -11204,11 +11087,6 @@ streamroller@^2.2.4: debug "^4.1.1" fs-extra "^8.1.0" -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -11544,32 +11422,7 @@ tar-stream@^2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.10: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - -tar@^6.0.1, tar@^6.0.2: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.1.11: +tar@^4.4.10, tar@^6.0.1, tar@^6.0.2, tar@^6.1.11, tar@^6.1.9: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -11668,11 +11521,6 @@ through2@^2.0.0: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunkify@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" - integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= - thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -11967,10 +11815,10 @@ typescript@~3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@0.7.21: - version "0.7.21" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== +ua-parser-js@0.7.21, ua-parser-js@^0.7.24: + version "0.7.28" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" + integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== unbzip2-stream@^1.0.9: version "1.4.3" @@ -12123,15 +11971,7 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -url-parse@^1.5.2: +url-parse@^1.4.3, url-parse@^1.5.2: version "1.5.3" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== @@ -12267,6 +12107,11 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vm2@^3.9.3: + version "3.9.3" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" + integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" @@ -12614,29 +12459,10 @@ write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== - dependencies: - async-limiter "~1.0.0" - -ws@^7.1.2, ws@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== - -ws@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.4.tgz#56bfa20b167427e138a7795de68d134fe92e21f9" - integrity sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@^6.2.1, ws@^7.3.1, ws@^7.4.6, ws@^7.5.3, ws@~6.1.0, ws@~7.4.2: + version "7.5.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== xml2js@0.4.19: version "0.4.19" @@ -12664,10 +12490,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@^1.6.1, xmlhttprequest-ssl@~1.5.4, xmlhttprequest-ssl@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xregexp@2.0.0: version "2.0.0" @@ -12679,12 +12505,12 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0, y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== @@ -12707,23 +12533,7 @@ yamljs@^0.3.0: argparse "^1.0.7" glob "^7.0.5" -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^18.1.0, yargs-parser@^18.1.2: +yargs-parser@^11.1.1, yargs-parser@^13.1.2, yargs-parser@^18.1.0, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== From cb607e0ab4455d785c296030ef33992dcb7fa2ab Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 29 Sep 2021 13:10:38 -0700 Subject: [PATCH 0543/1276] Resolved Landing Page Build Issue (#3325) --- .circleci/config.yml | 46 +++++++++++++++++++++++++++++++++++ cla-landing-page/package.json | 2 +- cla-landing-page/yarn.lock | 21 ++++------------ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 273991bca..a076e9574 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -570,6 +570,35 @@ jobs: yarn build popd + buildLandingPage: &buildLandingPageAnchor + docker: + - image: circleci/node:12-browsers + steps: + - checkout + - *setup_aws + - run: echo 'export NVM_DIR=${HOME}/.nvm' >> $BASH_ENV + - *install-node-12 + - run: + name: Install Top Level Dependencies + command: | + echo "Node version is: $(node --version)" + echo "Running top level install..." + yarn install + - run: + name: Install UI Dependencies + command: | + pushd $PROJECT_DIR + echo "Running yarn install in folder: `pwd`." + yarn install + popd + - run: + name: Build Source + command: | + echo "Building source..." + pushd $PROJECT_DIR + yarn build:${STAGE} + popd + # Build Project Management Console buildProjectConsoleDev: <<: *buildFrontendAnchor @@ -609,6 +638,19 @@ jobs: ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org PRODUCT_DOMAIN: dev.lfcla.com + # Build Landing Page Console + buildLandingPageDev: + <<: *buildLandingPageAnchor + environment: + AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV + AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV + AWS_PROFILE: lf-cla + AWS_REGION: us-east-1 + STAGE: dev + PROJECT_DIR: cla-landing-page + ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org + PRODUCT_DOMAIN: dev.lfcla.com + deployFrontend: &deployFrontendAnchor docker: - image: circleci/node:8-browsers @@ -1017,6 +1059,10 @@ workflows: filters: tags: only: /.*/ + - buildLandingPageDev: + filters: + tags: + only: /.*/ # Deploy v1 Dev - deployV1BackendDev: diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index b16794604..ebe96ab9f 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -101,7 +101,7 @@ "normalize-url": "^6.0.1", "pac-resolver": "^5.0.0", "path-parse": "^1.0.7", - "postcss": "^8.2.10", + "postcss": "^7.0.36", "set-getter": "^0.1.1", "socket.io": "^2.4.0", "socket.io-parser": "^3.4.1", diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index cd927bec2..b4d408085 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -8008,11 +8008,6 @@ nanoid@^2.1.0: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== -nanoid@^3.1.25: - version "3.1.28" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.28.tgz#3c01bac14cb6c5680569014cc65a2f26424c6bd4" - integrity sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw== - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -9267,14 +9262,13 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@7.0.27, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6, postcss@^8.2.10: - version "8.3.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.8.tgz#9ebe2a127396b4b4570ae9f7770e7fb83db2bac1" - integrity sha512-GT5bTjjZnwDifajzczOC+r3FI3Cu+PgPvrsjhQdRqa2kTJ4968/X9CUce9xttIB0xOs5c6xf0TCWZo/y9lF6bA== +postcss@7.0.27, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.38.tgz#5365a9c5126643d977046ad239f60eadda2491d6" + integrity sha512-wNrSHWjHDQJR/IZL5IKGxRtFgrYNaAA/UrkW2WqbtZO6uxSLMxMN+s2iqUMwnAWm3fMROlDYZB41dr0Mt7vBwQ== dependencies: nanocolors "^0.2.2" - nanoid "^3.1.25" - source-map-js "^0.6.2" + source-map "^0.6.1" prebuild-install@5.3.0: version "5.3.0" @@ -10831,11 +10825,6 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== - source-map-loader@0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" From 3ac9de18ff0707d70d92273bf74485ad478f72bd Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 30 Sep 2021 11:38:05 +0300 Subject: [PATCH 0544/1276] Feature/GitLab Sign URL - Updated sign url link on gitlab using same tab Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab-activity/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 9c897b15b..43f483901 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -266,7 +266,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git if errors.Is(missingUser.err, missingCompanyAffiliation) { msg := fmt.Sprintf(`
    • %s is authorized, but they must confirm their affiliation with their company. Start the authorization process - by clicking here, click "Corporate", + by clicking here, click "Corporate", select the appropriate company from the list, then confirm your affiliation on the page that appears. For further assistance with EasyCLA, @@ -275,7 +275,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git result += msg body = confirmationNeededBadge } else { - msg := fmt.Sprintf(`
    • %s - + msg := fmt.Sprintf(`
    • %s - %s's commit is not authorized under a signed CLA. Please click here to be authorized. For further assistance with EasyCLA, From c61909b43962b2a8693d279c40cfbda45d021553 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 30 Sep 2021 12:21:57 +0300 Subject: [PATCH 0545/1276] [#3318,#3235] Bug/GitLab Oauth - Handle nil code/state for gitlab oauth callback Signed-off-by: Harold Wanyama --- cla-backend-go/swagger/cla.v2.yaml | 2 - cla-backend-go/v2/gitlab-activity/service.go | 2 +- .../v2/gitlab_organizations/handlers.go | 41 ++++++++++++------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index d3973557b..a79de6e32 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3943,12 +3943,10 @@ paths: description: oauth code used to fetch the access token in: query type: string - required: true - name: state description: state is used to find the gitlab organization in: query type: string - required: true responses: '200': description: 'Success' diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index 43f483901..ba3855f5e 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -277,7 +277,7 @@ func PrepareMrCommentContent(missingUsers []*gatedGitlabUser, signedUsers []*git } else { msg := fmt.Sprintf(`
    • %s - %s's commit is not authorized under a signed CLA. - Please click here to be authorized. + Please click here to be authorized. For further assistance with EasyCLA, please submit a support request ticket.
    • `, signURL, failed, authorInfo, signURL, easyCLASupportURL) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index bfb2ea0dc..ea3abadc0 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -412,21 +412,32 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic requestID, _ := uuid.NewV4() reqID := requestID.String() - if params.Code == "" { - msg := "missing code parameter" - log.WithFields(f).Warn(msg) - return NewServerError(reqID, "", errors.New(msg)) - } - if params.State == "" { - msg := "missing state parameter" + if params.Code == nil || params.State == nil { + msg := "missing code or state parameter" log.WithFields(f).Warn(msg) - return NewServerError(reqID, "", errors.New(msg)) + return middleware.ResponderFunc( + func(rw http.ResponseWriter, p runtime.Producer) { + session, err := sessionStore.Get(params.HTTPRequest, SessionStoreKey) + if err != nil { + log.WithFields(f).WithError(err).Warn("error with session store lookup") + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + gitlabOriginURL, ok := session.Values["gitlab_origin_url"].(string) + if !ok { + msg := "Error getting gitlab_origin_url - missing from session object" + log.WithFields(f).Warn(msg) + http.Error(rw, msg, http.StatusInternalServerError) + return + } + http.Redirect(rw, params.HTTPRequest, gitlabOriginURL, http.StatusSeeOther) + }) } - codeParts := strings.Split(params.State, ":") + codeParts := strings.Split(*params.State, ":") if len(codeParts) != 2 { - msg := fmt.Sprintf("invalid state variable passed : %s", params.State) + msg := fmt.Sprintf("invalid state variable passed : %s", *params.State) log.WithFields(f).Warn(msg) return NewServerError(reqID, "", errors.New(msg)) } @@ -441,8 +452,8 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic http.Error(rw, err.Error(), http.StatusInternalServerError) return } - log.WithFields(f).Debugf("Loaded session: %+v", session.Values) + log.WithFields(f).Debugf("Loaded session: %+v", session.Values) state, ok := session.Values["gitlab_oauth2_state"].(string) if !ok { msg := "Error getting session state - missing from session object" @@ -475,16 +486,16 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic return } - if params.State != state { + if *params.State != state { msg := fmt.Sprintf("mismatch state, received: %s from callback, but loaded our state as: %s", - params.State, state) + *params.State, state) log.WithFields(f).Warn(msg) http.Error(rw, msg, http.StatusInternalServerError) return } log.WithFields(f).Debug("Fetching access token for user...") - token, err := gitlabApi.FetchOauthCredentials(params.Code) + token, err := gitlabApi.FetchOauthCredentials(*params.Code) if err != nil { msg := fmt.Sprint("unable to fetch access token for user") log.WithFields(f).Warn(msg) @@ -521,7 +532,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic } // now fetch the oauth credentials and store to db - oauthResp, err := gitlabApi.FetchOauthCredentials(params.Code) + oauthResp, err := gitlabApi.FetchOauthCredentials(*params.Code) if err != nil { msg := fmt.Sprintf("fetching gitlab credentials failed : %s : %v", gitlabOrganizationID, err) log.WithFields(f).WithError(err).Warn(msg) From f6d053c88ccfbce3bc5a52cac0834268246f39c5 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 30 Sep 2021 15:11:23 +0300 Subject: [PATCH 0546/1276] Bug/GitLab Org Post - Trim trailing "/" for gitlab org fullpath url Signed-off-by: Harold Wanyama --- cla-backend-go/v2/gitlab_organizations/handlers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index ea3abadc0..644238e05 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -164,6 +164,11 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic if strings.HasPrefix(params.Body.OrganizationFullPath, "/") { params.Body.OrganizationFullPath = params.Body.OrganizationFullPath[1:] } + + if strings.HasSuffix(params.Body.OrganizationFullPath, "/") { + log.WithFields(f).Debugf("Trimming suffix for : %s", params.Body.OrganizationFullPath) + params.Body.OrganizationFullPath = strings.TrimSuffix(params.Body.OrganizationFullPath, "/") + } } if params.Body.AutoEnabled == nil { From 480bbe39eece38d8c0e3037018ac469e9560fceb Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 4 Oct 2021 10:30:28 -0700 Subject: [PATCH 0547/1276] Bug/Child Project Repos (#3335) --- cla-backend/cla/models/dynamo_models.py | 2 +- cla-backend/cla/routes.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 4ff11c0eb..416be252b 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -2240,7 +2240,7 @@ def get_repository_by_project_sfid(self, project_sfid) -> List[dict]: for repository_model in repository_generator: repository = Repository() repository.model = repository_model - repositories.append(repository.to_dict()) + repositories.append(repository) return repositories def get_repository_models_by_repository_sfdc_id(self, project_sfid) -> List[Repository]: diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 4ab7db905..63fa66a0c 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -817,7 +817,9 @@ def get_project(project_id: hug.types.uuid): cla.log.debug(f'{fn} - querying github repositories for cla group: {project.get("project_name", None)} ' f'with id: {project_id}...') - mapping_record['github_repos'] = Repository().get_repository_by_project_sfid(project_sfid) + repositories = Repository().get_repository_by_project_sfid(project_sfid) + mapping_record['github_repos'] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "github"] + mapping_record["gitlab_repos"] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "gitlab"] mapping_record['gerrit_repos'] = [] try: cla.log.debug(f'{fn} - querying gerrit repositories for cla group: {project.get("project_name", None)} ' From ec5dda7aeccaf9896a2883631b904c4152e3ee4a Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Mon, 4 Oct 2021 13:42:43 -0700 Subject: [PATCH 0548/1276] [#3328]Feature/GitLab Error Handling (#3336) --- cla-backend-go/v2/gitlab_organizations/handlers.go | 2 +- cla-backend-go/v2/repositories/repository.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 644238e05..4596ac34c 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -549,7 +549,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic if updateErr != nil { msg := fmt.Sprintf("installation of GitLab Group and Repositories, error: %v", updateErr) log.WithFields(f).WithError(updateErr).Warn(msg) - return NewServerError(reqID, "", errors.New(msg)) + return NewServerError(reqID, "", errors.New(fmt.Sprintf("%v", updateErr))) } // Reload the GitLab organization - will have additional details now... diff --git a/cla-backend-go/v2/repositories/repository.go b/cla-backend-go/v2/repositories/repository.go index 27fa02048..14e3d00b6 100644 --- a/cla-backend-go/v2/repositories/repository.go +++ b/cla-backend-go/v2/repositories/repository.go @@ -325,7 +325,7 @@ func (r *Repository) GitLabAddRepository(ctx context.Context, projectSFID string } } else { return nil, &utils.GitLabRepositoryExists{ - Message: fmt.Sprintf("GitLab repository with name: %s has alerady been registered", input.RepositoryName), + Message: fmt.Sprintf("GitLab repository with name: %s has already been registered", input.RepositoryName), RepositoryName: "", Err: nil, } From 587759e790f6e4ed7de2e82de744fd0470d60f69 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 5 Oct 2021 14:01:49 +0300 Subject: [PATCH 0549/1276] Bug/Project Repos - resolved fetching repos by cla-group-id reference Signed-off-by: Harold Wanyama --- cla-backend/cla/routes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 63fa66a0c..9d4126736 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -817,9 +817,12 @@ def get_project(project_id: hug.types.uuid): cla.log.debug(f'{fn} - querying github repositories for cla group: {project.get("project_name", None)} ' f'with id: {project_id}...') - repositories = Repository().get_repository_by_project_sfid(project_sfid) - mapping_record['github_repos'] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "github"] - mapping_record["gitlab_repos"] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "gitlab"] + mapping_record['github_repos'] = [] + mapping_record["gitlab_repos"] = [] + repositories = Repository().get_repositories_by_cla_group_id(project_id) + if repositories: + mapping_record['github_repos'] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "github"] + mapping_record["gitlab_repos"] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "gitlab"] mapping_record['gerrit_repos'] = [] try: cla.log.debug(f'{fn} - querying gerrit repositories for cla group: {project.get("project_name", None)} ' From cc0fa121179d236117d3cfade88e0053afd704c8 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 5 Oct 2021 14:32:37 +0300 Subject: [PATCH 0550/1276] Bug/Gl-repo fetch - Resolved gitlab fetch by project sfid index query Signed-off-by: Harold Wanyama --- cla-backend/cla/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index 9d4126736..c4e051621 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -817,9 +817,9 @@ def get_project(project_id: hug.types.uuid): cla.log.debug(f'{fn} - querying github repositories for cla group: {project.get("project_name", None)} ' f'with id: {project_id}...') + repositories = Repository().get_repository_by_project_sfid(project_sfid) mapping_record['github_repos'] = [] mapping_record["gitlab_repos"] = [] - repositories = Repository().get_repositories_by_cla_group_id(project_id) if repositories: mapping_record['github_repos'] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "github"] mapping_record["gitlab_repos"] = [repo.to_dict() for repo in repositories if repo.get_repository_type() == "gitlab"] From b38174a62d55c793952c6f676d5169513d0260ad Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 7 Oct 2021 15:55:44 +0300 Subject: [PATCH 0551/1276] [#3334] Bug/GitLab Group Add - Resolved group path with not expected path name Signed-off-by: Harold Wanyama --- cla-backend-go/utils/regex.go | 15 ++++++++++++ .../v2/gitlab_organizations/handlers.go | 23 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/cla-backend-go/utils/regex.go b/cla-backend-go/utils/regex.go index e83d9053f..c00e725e8 100644 --- a/cla-backend-go/utils/regex.go +++ b/cla-backend-go/utils/regex.go @@ -28,3 +28,18 @@ func ValidWebsite(website string) bool { r := regexp.MustCompile(`^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$`) return r.MatchString(website) } + +// ParseString parses a string and returns group values defined in the regex +func ParseString(regEx, val string) (paramsMap map[string]string) { + + var compRegEx = regexp.MustCompile(regEx) + match := compRegEx.FindStringSubmatch(val) + + paramsMap = make(map[string]string) + for i, name := range compRegEx.SubexpNames() { + if i > 0 && i <= len(match) { + paramsMap[name] = match[i] + } + } + return paramsMap +} diff --git a/cla-backend-go/v2/gitlab_organizations/handlers.go b/cla-backend-go/v2/gitlab_organizations/handlers.go index 4596ac34c..66e049145 100644 --- a/cla-backend-go/v2/gitlab_organizations/handlers.go +++ b/cla-backend-go/v2/gitlab_organizations/handlers.go @@ -143,7 +143,7 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic if params.Body.OrganizationFullPath != "" { r, regexErr := regexp.Compile(`^http(s)?://`) if regexErr != nil { - msg := fmt.Sprintf("invalid regex for group/organization full path, error: %+v", regexErr) + msg := fmt.Sprintf("invalid for group/organization full path, error: %+v", regexErr) log.WithFields(f).WithError(regexErr).Warn(msg) return gitlab_organizations.NewAddProjectGitlabOrganizationInternalServerError().WithPayload( utils.ErrorResponseInternalServerErrorWithError(reqID, msg, regexErr)) @@ -157,7 +157,8 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic utils.ErrorResponseBadRequestWithError(reqID, msg, urlParseErr)) } // Update the group full path value - just include the path and not the https://... part - params.Body.OrganizationFullPath = groupWithUrl.Path + params.Body.OrganizationFullPath = cleanPath(groupWithUrl.Path) + log.WithFields(f).Debug(fmt.Sprintf("updated full path: %s ", params.Body.OrganizationFullPath)) } // Remove leading slash @@ -658,3 +659,21 @@ func (o *ServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Pro panic(err) } } + +//cleanPath helper function that strips the groups/ prefix in full path +func cleanPath(fullPath string) string { + f := logrus.Fields{ + "functionName": "gitlab_organizations.handlers.cleanPath", + "orgPathName": fullPath, + } + var result string + result = fullPath + reg := `(?P\bgroups\/\b)?(?P\w+)` + paramsMap := utils.ParseString(reg, fullPath) + if paramsMap["groups"] != "" { + log.WithFields(f).Debugf("stripping %s from %s", paramsMap["groups"], fullPath) + result = strings.ReplaceAll(fullPath, paramsMap["groups"], "") + } + log.WithFields(f).Debugf("clean path is %s ", result) + return result +} From 55bbeeaf59b24f82107e811c48a78b2bf0d5ecb1 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 11 Oct 2021 01:33:08 +0300 Subject: [PATCH 0552/1276] [#3391] GitLab Repo Add/Delete - Added event upon enrolling/unenrolling gitlab repo Signed-off-by: Harold Wanyama --- cla-backend-go/events/event_data.go | 26 ++++++++++++--- cla-backend-go/utils/constants.go | 6 ++++ cla-backend-go/v2/repositories/handlers.go | 37 ++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/cla-backend-go/events/event_data.go b/cla-backend-go/events/event_data.go index 70dd7d429..c973794b7 100644 --- a/cla-backend-go/events/event_data.go +++ b/cla-backend-go/events/event_data.go @@ -40,12 +40,14 @@ type ProjectServiceCLADisabledData struct { // RepositoryAddedEventData event data model type RepositoryAddedEventData struct { RepositoryName string + RepositoryType string } // RepositoryDisabledEventData event data model type RepositoryDisabledEventData struct { RepositoryName string RepositoryExternalID int64 + RepositoryType string } // RepositoryDeletedEventData event data model @@ -480,7 +482,11 @@ func (ed *ProjectServiceCLADisabledData) GetEventDetailsString(args *LogEventArg // GetEventDetailsString returns the details string for this event func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository: %s was added for the project %s", ed.RepositoryName, args.ProjectName) + repositoryType := utils.GitHubRepositoryType + if ed.RepositoryType != "" { + repositoryType = ed.RepositoryType + } + data := fmt.Sprintf("The %s repository: %s was added for the project %s", repositoryType, ed.RepositoryName, args.ProjectName) if args.UserName != "" { data = data + fmt.Sprintf(" by the user %s", args.UserName) } @@ -490,7 +496,11 @@ func (ed *RepositoryAddedEventData) GetEventDetailsString(args *LogEventArgs) (s // GetEventDetailsString returns the details string for this event func (ed *RepositoryDisabledEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) { - data := "The GitHub repository " // nolint + repositoryType := utils.GitHubRepositoryType + if repositoryType != "" { + repositoryType = ed.RepositoryType + } + data := fmt.Sprintf("The %s repository", repositoryType) // nolint if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1489,7 +1499,11 @@ func (ed *ProjectServiceCLADisabledData) GetEventSummaryString(args *LogEventArg // GetEventSummaryString returns the summary string for this event func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := fmt.Sprintf("The GitHub repository %s was added", ed.RepositoryName) + repositoryType := utils.GitHubRepositoryType + if ed.RepositoryType != "" { + repositoryType = ed.RepositoryType + } + data := fmt.Sprintf("The %s repository %s was added", repositoryType, ed.RepositoryName) if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } @@ -1508,7 +1522,11 @@ func (ed *RepositoryAddedEventData) GetEventSummaryString(args *LogEventArgs) (s // GetEventSummaryString returns the summary string for this event func (ed *RepositoryDisabledEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) { - data := "The GitHub repository " // nolint + repositoryType := utils.GitHubRepositoryType + if ed.RepositoryType != "" { + repositoryType = ed.RepositoryType + } + data := fmt.Sprintf("The %s repository", repositoryType) // nolint if args.CLAGroupName != "" { data = data + fmt.Sprintf(" for the CLA Group %s", args.CLAGroupName) } diff --git a/cla-backend-go/utils/constants.go b/cla-backend-go/utils/constants.go index e3be79cca..155ce9106 100644 --- a/cla-backend-go/utils/constants.go +++ b/cla-backend-go/utils/constants.go @@ -207,3 +207,9 @@ const SignatureQueryDefaultAll = "all" // query should return data - show only 'active' signatures or 'all' signatures when no other query signed/approved // params are provided const SignatureQueryDefaultActive = "active" + +//GitLabRepositoryType representing the GitLab repository type +const GitLabRepositoryType = "GitLab" + +//GitHubRepositoryType representing the GitLab repository type +const GitHubRepositoryType = "GitHub" diff --git a/cla-backend-go/v2/repositories/handlers.go b/cla-backend-go/v2/repositories/handlers.go index c547cab27..f7d97fe36 100644 --- a/cla-backend-go/v2/repositories/handlers.go +++ b/cla-backend-go/v2/repositories/handlers.go @@ -503,6 +503,25 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic log.WithFields(f).WithError(enableErr).Warn(msg) return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, enableErr)) } + + // Log unenroll gitlab project event + for _, externalID := range params.GitlabRepositoriesEnroll.Enroll { + gitlabRepo, err := service.GitLabGetRepositoryByExternalID(ctx, externalID) + if err != nil { + log.WithFields(f).Errorf("unable to fetch repository by externalID: %d: error: %+v", externalID, err) + continue + } + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryAdded, + ProjectSFID: params.ProjectSFID, + CLAGroupID: gitlabRepo.RepositoryClaGroupID, + LfUsername: authUser.UserName, + EventData: &events.RepositoryAddedEventData{ + RepositoryName: gitlabRepo.RepositoryName, + RepositoryType: utils.GitLabRepositoryType, + }, + }) + } } if len(params.GitlabRepositoriesEnroll.Unenroll) > 0 { @@ -513,6 +532,24 @@ func Configure(api *operations.EasyclaAPI, service ServiceInterface, eventServic log.WithFields(f).WithError(enableErr).Warn(msg) return gitlab_repositories.NewEnrollGitLabRepositoryBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, enableErr)) } + // Log unenroll gitlab project event + for _, externalID := range params.GitlabRepositoriesEnroll.Unenroll { + gitlabRepo, err := service.GitLabGetRepositoryByExternalID(ctx, externalID) + if err != nil { + log.WithFields(f).Errorf("unable to fetch repository by externalID: %d: error: %+v", externalID, err) + continue + } + eventService.LogEventWithContext(ctx, &events.LogEventArgs{ + EventType: events.RepositoryDisabled, + ProjectSFID: params.ProjectSFID, + CLAGroupID: gitlabRepo.RepositoryClaGroupID, + LfUsername: authUser.UserName, + EventData: &events.RepositoryDisabledEventData{ + RepositoryName: gitlabRepo.RepositoryName, + RepositoryType: utils.GitLabRepositoryType, + }, + }) + } } repoList, getErr := service.GitLabGetRepositoriesByProjectSFID(ctx, params.ProjectSFID) From e196eacc21317dc79d0dd634bbd382cdb086d91b Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 12 Oct 2021 13:10:07 -0700 Subject: [PATCH 0553/1276] Resolved Typo in Log File (#3344) --- cla-backend-go/v2/signatures/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend-go/v2/signatures/handlers.go b/cla-backend-go/v2/signatures/handlers.go index 7ae5c2788..a7e760bd5 100644 --- a/cla-backend-go/v2/signatures/handlers.go +++ b/cla-backend-go/v2/signatures/handlers.go @@ -118,7 +118,7 @@ func Configure(api *operations.EasyclaAPI, claGroupService project.Service, proj // Must be in the Project|Organization Scope to see this - signature ACL is double-checked in the service level when the signature is loaded if !utils.IsUserAuthorizedForProjectOrganizationTree(ctx, authUser, params.ProjectSFID, companyModel.CompanyExternalID, utils.DISALLOW_ADMIN_SCOPE) { msg := fmt.Sprintf("user '%s' does not have access to update Project Company Approval List with Project|Organization scope of %s | %s", - authUser.UserName, params.ProjectSFID, params.CompanyID) + authUser.UserName, params.ProjectSFID, companyModel.CompanyExternalID) log.WithFields(f).Warn(msg) return signatures.NewUpdateApprovalListForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } From cac571db402c72d3d6bb1a99408f65471b981cff Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Tue, 9 Nov 2021 15:19:18 +0300 Subject: [PATCH 0554/1276] Bug/GH Committers Fail - Resolved case for the pygithub query for accessing pr authors - Updated pyGithub to 1.55 - Updated PyJWT to 2.3.0 Signed-off-by: Harold Wanyama --- .../v2/organization-service/client.go | 2 +- cla-backend/cla/models/github_models.py | 72 ++++++++++--------- cla-backend/requirements.txt | 4 +- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/cla-backend-go/v2/organization-service/client.go b/cla-backend-go/v2/organization-service/client.go index 8fde244e5..7f0c64aeb 100644 --- a/cla-backend-go/v2/organization-service/client.go +++ b/cla-backend-go/v2/organization-service/client.go @@ -29,7 +29,7 @@ import ( // Client is client for organization_service type Client struct { - cl *client.OrganziationService + cl *client.OrganizationService } const ( diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 28047b12f..93ee784ed 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -891,39 +891,45 @@ def get_pull_request_commit_authors(pull_request): for commit in pull_request.get_commits(): cla.log.debug('Processing commit while looking for authors, commit: {}'.format(commit.sha)) # Note: we can get the author info in two different ways: - if commit.author is not None: - # commit.author is a github.NamedUser.NamedUser type object - # https://pygithub.readthedocs.io/en/latest/github_objects/NamedUser.html - if commit.author.name is not None: - cla.log.debug('PR: {}, GitHub commit.author.name author found for commit SHA {}, ' - 'author id: {}, name: {}, email: {}'. - format(pull_request.number, commit.sha, commit.author.id, - commit.author.name, commit.author.email)) - commit_authors.append((commit.sha, (commit.author.id, commit.author.name, commit.author.email))) - elif commit.author.login is not None: - cla.log.debug('PR: {}, GitHub commit.author.login author found for commit SHA {}, ' - 'author id: {}, login: {}, email: {}'. - format(pull_request.number, commit.sha, commit.author.id, - commit.author.login, commit.author.email)) - commit_authors.append((commit.sha, (commit.author.id, commit.author.login, commit.author.email))) - else: - cla.log.debug(f'PR: {pull_request.number}, GitHub commit.author.name and commit.author.login ' - f'author information NOT found for commit SHA {commit.sha}, ' - f'author id: {commit.author.id}, ' - f'name: {commit.author.name}, ' - f'login: {commit.author.login}, ' - f'email: {commit.author.email}') - commit_authors.append((commit.sha, None)) - elif commit.commit.author is not None: - cla.log.debug('github.GitAuthor.GitAuthor object: {}'.format(commit.commit.author)) - # commit.commit.author is a github.GitAuthor.GitAuthor object type - object - # only has date, name and email attributes - no ID attribute/value - # https://pygithub.readthedocs.io/en/latest/github_objects/GitAuthor.html - cla.log.debug('PR: {}, GitHub NamedUser author NOT found for commit SHA {}, ' - 'however, found GitAuthor author id: None, name: {}, email: {}'. - format(pull_request.number, commit.sha, - commit.commit.author.name, commit.commit.author.email)) - commit_authors.append((commit.sha, (None, commit.commit.author.name, commit.commit.author.email))) + if commit.author: + try: + # commit.author is a github.NamedUser.NamedUser type object + # https://pygithub.readthedocs.io/en/latest/github_objects/NamedUser.html + if commit.author.name is not None: + cla.log.debug('PR: {}, GitHub commit.author.name author found for commit SHA {}, ' + 'author id: {}, name: {}, email: {}'. + format(pull_request.number, commit.sha, commit.author.id, + commit.author.name, commit.author.email)) + commit_authors.append((commit.sha, (commit.author.id, commit.author.name, commit.author.email))) + elif commit.author.login is not None: + cla.log.debug('PR: {}, GitHub commit.author.login author found for commit SHA {}, ' + 'author id: {}, login: {}, email: {}'. + format(pull_request.number, commit.sha, commit.author.id, + commit.author.login, commit.author.email)) + commit_authors.append((commit.sha, (commit.author.id, commit.author.login, commit.author.email))) + else: + cla.log.debug(f'PR: {pull_request.number}, GitHub commit.author.name and commit.author.login ' + f'author information NOT found for commit SHA {commit.sha}, ' + f'author id: {commit.author.id}, ' + f'name: {commit.author.name}, ' + f'login: {commit.author.login}, ' + f'email: {commit.author.email}') + commit_authors.append((commit.sha, None)) + except GithubException.IncompletableObject as ex: + cla.log.debug(f'Commit sha: {commit.sha} exception: {ex}') + try: + cla.log.debug('github.GitAuthor.GitAuthor object: {}'.format(commit.commit.author)) + # commit.commit.author is a github.GitAuthor.GitAuthor object type - object + # only has date, name and email attributes - no ID attribute/value + # https://pygithub.readthedocs.io/en/latest/github_objects/GitAuthor.html + cla.log.debug('PR: {}, GitHub NamedUser author NOT found for commit SHA {}, ' + 'however, found GitAuthor author id: None, name: {}, email: {}'. + format(pull_request.number, commit.sha, + commit.commit.author.name, commit.commit.author.email)) + commit_authors.append((commit.sha, (None, commit.commit.author.name, commit.commit.author.email))) + except GithubException.IncompletableObject: + cla.log.warning('PR: {}, could not find any commit author for SHA {}'.format(pull_request.number, commit.sha)) + commit_authors.append((commit.sha, None)) else: cla.log.warning('PR: {}, could not find any commit author for SHA {}'. format(pull_request.number, commit.sha)) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index c0f65349f..0a0545ab9 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -34,8 +34,8 @@ pluggy==0.13.1 py==1.10.0 pyasn1==0.4.8 pydocusign==2.2 -PyGithub==1.43.8 -PyJWT==1.7.1 +PyGithub==1.55 +PyJWT==2.3.0 pylint==1.5.2 pynamodb==3.4.1 pyparsing==2.4.5 From ccac20c32e2682094de3a6e33fc04d265a1afe36 Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Tue, 9 Nov 2021 11:57:24 -0800 Subject: [PATCH 0555/1276] Bug/Get Github authors (#3357) --- cla-backend/cla/models/github_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 93ee784ed..434233835 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -12,7 +12,7 @@ import falcon import github from github import PullRequest -from github.GithubException import UnknownObjectException, BadCredentialsException, GithubException +from github.GithubException import UnknownObjectException, BadCredentialsException, GithubException, IncompletableObject from requests_oauthlib import OAuth2Session import cla @@ -915,7 +915,7 @@ def get_pull_request_commit_authors(pull_request): f'login: {commit.author.login}, ' f'email: {commit.author.email}') commit_authors.append((commit.sha, None)) - except GithubException.IncompletableObject as ex: + except (GithubException, IncompletableObject) as ex: cla.log.debug(f'Commit sha: {commit.sha} exception: {ex}') try: cla.log.debug('github.GitAuthor.GitAuthor object: {}'.format(commit.commit.author)) @@ -927,7 +927,7 @@ def get_pull_request_commit_authors(pull_request): format(pull_request.number, commit.sha, commit.commit.author.name, commit.commit.author.email)) commit_authors.append((commit.sha, (None, commit.commit.author.name, commit.commit.author.email))) - except GithubException.IncompletableObject: + except (GithubException, IncompletableObject): cla.log.warning('PR: {}, could not find any commit author for SHA {}'.format(pull_request.number, commit.sha)) commit_authors.append((commit.sha, None)) else: From c435c6bf58bdeed153572e260426859b7e05d019 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 10 Nov 2021 11:06:16 -0800 Subject: [PATCH 0556/1276] Updated Sign Handler Permission Logging (#3358) --- cla-backend-go/events/service.go | 2 +- .../utils/utils_user_auth_standalone.go | 1 + cla-backend-go/v2/cla_manager/handlers.go | 22 ++++++++-------- cla-backend-go/v2/cla_manager/service.go | 2 ++ cla-backend-go/v2/sign/handlers.go | 25 +++++++++++++------ 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/cla-backend-go/events/service.go b/cla-backend-go/events/service.go index 7274fe8e0..deda1c295 100644 --- a/cla-backend-go/events/service.go +++ b/cla-backend-go/events/service.go @@ -427,7 +427,7 @@ func (s *service) LogEventWithContext(ctx context.Context, args *LogEventArgs) { }() if args == nil || args.EventType == "" || args.EventData == nil || (args.UserID == "" && args.LfUsername == "") { - log.WithFields(f).Warnf("invalid arguments to LogEvent, missing one or more required values. args %#v", args) + log.WithFields(f).Warnf("invalid arguments to LogEvent, missing one or more required values. Need EventType, EventData or one of UserID or LfUsername. args %#v", args) return } diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index ab3696b40..3cf9aebd3 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -274,5 +274,6 @@ func IsUserAuthorizedForProjectOrganizationTree(ctx context.Context, user *auth. } else { log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) } + return val } diff --git a/cla-backend-go/v2/cla_manager/handlers.go b/cla-backend-go/v2/cla_manager/handlers.go index 8cfd93bca..20f169043 100644 --- a/cla-backend-go/v2/cla_manager/handlers.go +++ b/cla-backend-go/v2/cla_manager/handlers.go @@ -40,11 +40,11 @@ const ( func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1Company.IService, LfxPortalURL, CorporateConsoleV2URL string, projectClaGroupRepo projects_cla_groups.Repository, easyCLAUserRepo v1User.RepositoryService) { // nolint api.ClaManagerCreateCLAManagerHandler = cla_manager.CreateCLAManagerHandlerFunc(func(params cla_manager.CreateCLAManagerParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerHandler", + "functionName": "v2.cla_manager.handlers.ClaManagerCreateCLAManagerHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "CompanyID": params.CompanyID, "ProjectSFID": params.ProjectSFID, @@ -92,10 +92,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C api.ClaManagerDeleteCLAManagerHandler = cla_manager.DeleteCLAManagerHandlerFunc(func(params cla_manager.DeleteCLAManagerParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_manager.handlers.ClaManagerDeleteCLAManagerHandler", + "functionName": "v2.cla_manager.handlers.ClaManagerDeleteCLAManagerHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "CompanyID": params.CompanyID, "ProjectSFID": params.ProjectSFID, @@ -136,10 +136,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C api.ClaManagerCreateCLAManagerDesigneeHandler = cla_manager.CreateCLAManagerDesigneeHandlerFunc(func(params cla_manager.CreateCLAManagerDesigneeParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerDesigneeHandler", + "functionName": "v2.cla_manager.handlers.ClaManagerCreateCLAManagerDesigneeHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "CompanyID": params.CompanyID, "ProjectSFID": params.ProjectSFID, @@ -175,10 +175,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C api.ClaManagerCreateCLAManagerDesigneeByGroupHandler = cla_manager.CreateCLAManagerDesigneeByGroupHandlerFunc( func(params cla_manager.CreateCLAManagerDesigneeByGroupParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerDesigneeByGroupHandler", + "functionName": "v2.cla_manager.handlers.ClaManagerCreateCLAManagerDesigneeByGroupHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "CompanySFID": params.CompanyID, "ClaGroupID": params.ClaGroupID, @@ -296,10 +296,10 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C api.ClaManagerCreateCLAManagerRequestHandler = cla_manager.CreateCLAManagerRequestHandlerFunc(func(params cla_manager.CreateCLAManagerRequestParams, authUser *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, authUser) // nolint utils.SetAuthUserProperties(authUser, params.XUSERNAME, params.XEMAIL) f := logrus.Fields{ - "functionName": "cla_manager.handlers.ClaManagerCreateCLAManagerRequestHandler", + "functionName": "v2.cla_manager.handlers.ClaManagerCreateCLAManagerRequestHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "CompanyID": params.CompanyID, "ProjectSFID": params.ProjectSFID, @@ -369,7 +369,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C reqID := utils.GetRequestID(params.XREQUESTID) ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint f := logrus.Fields{ - "functionName": "cla_manager.handlers.ClaManagerNotifyCLAManagersHandler", + "functionName": "v2.cla_manager.handlers.ClaManagerNotifyCLAManagersHandler", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), "companyName": params.Body.CompanyName, "signingEntityName": params.Body.SigningEntityName, diff --git a/cla-backend-go/v2/cla_manager/service.go b/cla-backend-go/v2/cla_manager/service.go index 505056e73..d4c230ff8 100644 --- a/cla-backend-go/v2/cla_manager/service.go +++ b/cla-backend-go/v2/cla_manager/service.go @@ -405,6 +405,8 @@ func (s *service) CreateCLAManagerDesignee(ctx context.Context, companyID string ProjectSFID: projectSFID, CompanyModel: v1CompanyModel, CompanyID: v1CompanyModel.CompanyID, + UserName: lfxUser.Username, + LfUsername: lfxUser.Username, EventData: &events.AssignRoleScopeData{ Role: "cla-manager-designee", Scope: fmt.Sprintf("%s|%s", projectSFID, v1CompanyModel.CompanyExternalID), diff --git a/cla-backend-go/v2/sign/handlers.go b/cla-backend-go/v2/sign/handlers.go index 822a549d4..4d578f994 100644 --- a/cla-backend-go/v2/sign/handlers.go +++ b/cla-backend-go/v2/sign/handlers.go @@ -4,11 +4,13 @@ package sign import ( - "context" "errors" "fmt" "strings" + log "github.com/communitybridge/easycla/cla-backend-go/logging" + "github.com/sirupsen/logrus" + "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" "github.com/LF-Engineering/lfx-kit/auth" @@ -26,15 +28,22 @@ func Configure(api *operations.EasyclaAPI, service Service) { api.SignRequestCorporateSignatureHandler = sign.RequestCorporateSignatureHandlerFunc( func(params sign.RequestCorporateSignatureParams, user *auth.User) middleware.Responder { reqID := utils.GetRequestID(params.XREQUESTID) - ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint + ctx := utils.ContextWithRequestAndUser(params.HTTPRequest.Context(), reqID, user) // nolint utils.SetAuthUserProperties(user, params.XUSERNAME, params.XEMAIL) + f := logrus.Fields{ + "functionName": "v2.sign.handlers.SignRequestCorporateSignatureHandler", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "CompanyID": params.Input.CompanySfid, + "ProjectSFID": params.Input.ProjectSfid, + "authUserName": utils.StringValue(params.XUSERNAME), + "authUserEmail": utils.StringValue(params.XEMAIL), + } + if !utils.IsUserAuthorizedForProjectOrganizationTree(ctx, user, utils.StringValue(params.Input.ProjectSfid), utils.StringValue(params.Input.CompanySfid), utils.DISALLOW_ADMIN_SCOPE) { - return sign.NewRequestCorporateSignatureForbidden().WithPayload(&models.ErrorResponse{ - Code: "403", - Message: fmt.Sprintf("EasyCLA - 403 Forbidden - user %s does not have access to Request Corporate Signature with Project|Organization scope of %s | %s", - user.UserName, utils.StringValue(params.Input.ProjectSfid), utils.StringValue(params.Input.CompanySfid)), - XRequestID: reqID, - }) + msg := fmt.Sprintf("user %s does not have access to Request Corporate Signature with Project|Organization scope tree of %s | %s - allow admin scope: false", + user.UserName, utils.StringValue(params.Input.ProjectSfid), utils.StringValue(params.Input.CompanySfid)) + log.WithFields(f).Warn(msg) + return sign.NewRequestCorporateSignatureForbidden().WithPayload(utils.ErrorResponseForbidden(reqID, msg)) } resp, err := service.RequestCorporateSignature(ctx, utils.StringValue(params.XUSERNAME), params.Authorization, params.Input) From e1170ce1220692bd5c93bef4c9db5a46fdca5e32 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Nov 2021 11:41:26 +0300 Subject: [PATCH 0557/1276] Bug/PR Status Update - Handled invalid author details for PR comment update Signed-off-by: Harold Wanyama --- cla-backend/cla/models/github_models.py | 23 ++++++++++++------- .../cla/tests/unit/test_github_models.py | 12 ++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 434233835..7cc420d98 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -733,6 +733,10 @@ def handle_commit_from_user(project, commit_sha, author_info, signed, missing): :type missing: list of strings """ + # handle edge case of non existant users + if author_info is None: + missing.append((commit_sha, [])) + return # Extract the author_info tuple details author_id = author_info[0] author_username = author_info[1] @@ -994,7 +998,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep text = "" for authors in missing: # Check for valid github id - if authors[1][0] is None: + if authors[1] is None or authors[1][0] is None: help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" else: help_url = cla.utils.get_full_sign_url('github', str(installation_id), github_repository_id, @@ -1004,15 +1008,18 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep commit_sha = authors[0] if commit_sha != last_commit.sha: continue - author_email = authors[1][2] - author_id = authors[1][0] - if author_id: - if len(authors[1]) == 4: - text += f'{author_email} must confirm corporate affiliation.\n' + if authors[1]: + author_email = authors[1][2] + author_id = authors[1][0] + if author_id: + if len(authors[1]) == 4: + text += f'{author_email} must confirm corporate affiliation.\n' + else: + text += f'{author_email} is not authorized under a signed CLA.\n' else: - text += f'{author_email} is not authorized under a signed CLA.\n' + text += f'{author_email} is not linked to this commit. \n' else: - text += f'{author_email} is not linked to this commit. \n' + text += 'Invalid author details. \n' payload = { "name": "CLA check", diff --git a/cla-backend/cla/tests/unit/test_github_models.py b/cla-backend/cla/tests/unit/test_github_models.py index ca387c031..f7d02d17d 100644 --- a/cla-backend/cla/tests/unit/test_github_models.py +++ b/cla-backend/cla/tests/unit/test_github_models.py @@ -97,6 +97,18 @@ def test_handle_commit_author_whitelisted(self) -> None: # We commented out this functionality for now - re-enable if we add it back # self.assertListEqual(missing, [('fake_sha', [123, 'foo', 'foo@gmail.com', True])]) self.assertEqual(signed, []) + + def test_handle_invalid_author(self) -> None: + """ + Test case handling non existant author tagged to a given commit + """ + project = Project() + author_info = None + signed = [] + missing = [] + handle_commit_from_user(project, 'fake_sha', author_info, signed, missing) + self.assertEqual(signed, []) + self.assertEqual(missing, [('fake_sha', [])]) class TestGithubModelsPrComment(unittest.TestCase): From fc7c978648ec13cd1ed39eaa220969417cdc3ca9 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Nov 2021 13:12:54 +0300 Subject: [PATCH 0558/1276] Bug/Invalid GH author - Handling invalid author logging bug Signed-off-by: Harold Wanyama --- cla-backend/cla/models/github_models.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 7cc420d98..fbadb19bb 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -432,12 +432,15 @@ def update_change_request(self, installation_id, github_repository_id, change_re 'determining who has signed a CLA an who has not.') for commit_sha, author_info in commit_authors: # Extract the author info tuple details - author_id = author_info[0] - author_username = author_info[1] - author_email = author_info[2] - cla.log.debug(f'{fn} - PR: {pull_request.number}, ' - f'processing sha: {commit_sha} ' - f'from author id: {author_id}, username: {author_username}, email: {author_email}') + if author_info: + author_id = author_info[0] + author_username = author_info[1] + author_email = author_info[2] + cla.log.debug(f'{fn} - PR: {pull_request.number}, ' + f'processing sha: {commit_sha} ' + f'from author id: {author_id}, username: {author_username}, email: {author_email}') + else: + cla.log.debug(f'{fn} - PR: {pull_request.number}, processing sha: {commit_sha} with invalid author details') handle_commit_from_user(project, commit_sha, author_info, signed, missing) cla.log.debug(f'{fn} - PR: {pull_request.number}, ' From 8848fd39f462d408d5ca863d1860013095e91312 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Nov 2021 15:55:32 +0300 Subject: [PATCH 0559/1276] Bug/Invalid Author PR Update - Resolved invalid author for creating PR status Signed-off-by: Harold Wanyama --- cla-backend/cla/models/github_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index fbadb19bb..59a1bc6e2 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -992,7 +992,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep """ notification = cla.conf['GITHUB_PR_NOTIFICATION'] both = notification == 'status+comment' or notification == 'comment+status' - last_commit = pull_request.get_commits().reversed[0] + last_commit = pull_request.get_commits()[0] # Here we update the PR status by adding/updating the PR body - this is the way the EasyCLA app # knows if it is pass/fail. @@ -1001,7 +1001,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep text = "" for authors in missing: # Check for valid github id - if authors[1] is None or authors[1][0] is None: + if authors[1] is None or (authors[1] and authors[1][0] is None): help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" else: help_url = cla.utils.get_full_sign_url('github', str(installation_id), github_repository_id, From e9e238c41ea74d1e23e744ae628f770cf8255022 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Nov 2021 16:24:55 +0300 Subject: [PATCH 0560/1276] Bug/Assemble CLA comment - Resolved no author for assembling cla comment Signed-off-by: Harold Wanyama --- cla-backend/cla/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index dd2251e4f..08ef1ea73 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -915,7 +915,7 @@ def assemble_cla_comment(repository_type, installation_id, github_repository_id, :type project_version: string """ num_missing = len(missing) - missing_ids = list(filter(lambda x: x[1][0] is None, missing)) + missing_ids = list(filter(lambda x: (x[1] is None or (x[1] and x[1][0] is None)), missing)) no_user_id = len(missing_ids) > 0 # check if an unsigned committer has been approved by a CLA Manager, but not associated with a company # Logic not supported as we removed the DB query in the caller From 262a1d144dfd46bdc24668d64f00d0e9cf786053 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Nov 2021 17:12:17 +0300 Subject: [PATCH 0561/1276] Bug/Get comment body - Resolved invalid author edge case for getting comment body Signed-off-by: Harold Wanyama --- cla-backend/cla/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 08ef1ea73..80cf9cd80 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -977,6 +977,8 @@ def get_comment_body(repository_type, sign_url, signed, missing): committers = {} # Consider the case where github Id does not exist for commit, author in missing: + if author is None: + author = (None,None,None) if author[0] is None: author[1] = "Unknown" if author[1] not in committers: From 3124afd09fe504840a62bcb46ca0e6988d703eb8 Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Thu, 11 Nov 2021 17:49:21 +0300 Subject: [PATCH 0562/1276] Bug/Get Author details - Resolved creating gh comment for invalid author details Signed-off-by: Harold Wanyama --- cla-backend/cla/utils.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 80cf9cd80..30ba64544 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -977,16 +977,15 @@ def get_comment_body(repository_type, sign_url, signed, missing): committers = {} # Consider the case where github Id does not exist for commit, author in missing: - if author is None: - author = (None,None,None) - if author[0] is None: - author[1] = "Unknown" - if author[1] not in committers: - committers[author[1]] = [] - committers[author[1]].append(commit) + name = "Unknown" + if author and author[0]: + name = author[1] + if name not in committers: + committers[name] = [] + committers[name].append(commit) # Check case for whitelisted unsigned user if len(author) == 4: - committers[author[1]].append(True) + committers[name].append(True) # Print author commit information. committers_comment += "
        " From 159ddf6f3d309e9198b935f6f59242166fefa959 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Nov 2021 07:52:50 -0800 Subject: [PATCH 0563/1276] Bump color-string from 1.5.4 to 1.6.0 in /cla-landing-page (#3355) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-landing-page/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index b4d408085..88a57b37d 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -3492,9 +3492,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2, color-string@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.6.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" From 89d3a9b62d0ae1b164c399256249abb3f4e70e6b Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 11 Nov 2021 12:54:58 -0800 Subject: [PATCH 0564/1276] Added permissions check debug (#3365) --- .../utils/utils_user_auth_lambda.go | 77 +++++++++++++++---- .../utils/utils_user_auth_standalone.go | 72 ++++++++++++++--- 2 files changed, 124 insertions(+), 25 deletions(-) diff --git a/cla-backend-go/utils/utils_user_auth_lambda.go b/cla-backend-go/utils/utils_user_auth_lambda.go index 39c0a3a66..d5dfdf82a 100644 --- a/cla-backend-go/utils/utils_user_auth_lambda.go +++ b/cla-backend-go/utils/utils_user_auth_lambda.go @@ -8,11 +8,13 @@ package utils import ( "context" + "fmt" "strings" + "github.com/sirupsen/logrus" + "github.com/LF-Engineering/lfx-kit/auth" log "github.com/communitybridge/easycla/cla-backend-go/logging" - "github.com/sirupsen/logrus" ) const ( @@ -45,9 +47,16 @@ func IsUserAuthorizedForOrganization(ctx context.Context, user *auth.User, compa val := user.IsUserAuthorizedForOrganizationScope(companySFID) if val { - log.WithFields(f).Debugf("user is authorized for companySFID: %s", companySFID) + log.WithFields(f).Debugf("user '%s' is authorized for companySFID: %s admin flag=%t", + user.UserName, companySFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for companySFID: %s", companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for companySFID: %s, admin flag=%t, scopeInfo: %s", + user.UserName, companySFID, user.Admin, scopeInfo) } return val } @@ -71,9 +80,16 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorized(auth.Project, projectSFID, true) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s tree, admin flag=%t", + user.UserName, projectSFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s tree, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, user.Admin, scopeInfo) } return val } @@ -97,9 +113,16 @@ func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFI log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorizedForProjectScope(projectSFID) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s, admin flag=%t", + user.UserName, projectSFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, user.Admin, scopeInfo) } return val } @@ -128,7 +151,13 @@ func IsUserAuthorizedForAnyProjects(ctx context.Context, user *auth.User, projec } } - log.WithFields(f).Debugf("project scope checks failed for: %s...", strings.Join(projectSFIDs, ",")) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for project scope checks for any projects: %s, admin flag=%t, scopeInfo: %s", + user.UserName, strings.Join(projectSFIDs, ","), user.Admin, scopeInfo) return false } @@ -151,10 +180,18 @@ func IsUserAuthorizedForProjectOrganization(ctx context.Context, user *auth.User val := user.IsUserAuthorizedByProject(projectSFID, companySFID) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t", + user.UserName, projectSFID, companySFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, companySFID, user.Admin, scopeInfo) } + return val } @@ -186,7 +223,13 @@ func IsUserAuthorizedForAnyProjectOrganization(ctx context.Context, user *auth.U } } - log.WithFields(f).Debugf("user is not authorized for any projectSFID: %s + companySFID: %s", strings.Join(projectSFIDs, ","), companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for any projectSFID: %s + companySFID: %s, admin flag=%t, scopeInfo: %s", + user.UserName, strings.Join(projectSFIDs, ","), companySFID, user.Admin, scopeInfo) return false } @@ -209,9 +252,17 @@ func IsUserAuthorizedForProjectOrganizationTree(ctx context.Context, user *auth. val := user.IsUserAuthorized(auth.ProjectOrganization, projectSFID+"|"+companySFID, true) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t", + user.UserName, projectSFID, companySFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, companySFID, user.Admin, scopeInfo) } + return val } diff --git a/cla-backend-go/utils/utils_user_auth_standalone.go b/cla-backend-go/utils/utils_user_auth_standalone.go index 3cf9aebd3..aa4e1dd43 100644 --- a/cla-backend-go/utils/utils_user_auth_standalone.go +++ b/cla-backend-go/utils/utils_user_auth_standalone.go @@ -8,6 +8,7 @@ package utils import ( "context" + "fmt" "os" "strconv" "strings" @@ -70,9 +71,16 @@ func IsUserAuthorizedForOrganization(ctx context.Context, user *auth.User, compa val := user.IsUserAuthorizedForOrganizationScope(companySFID) if val { - log.WithFields(f).Debugf("user is authorized for companySFID: %s", companySFID) + log.WithFields(f).Debugf("user '%s' is authorized for companySFID: %s admin flag=%t", + user.UserName, companySFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for companySFID: %s", companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for companySFID: %s, admin flag=%t, scopeInfo: %s", + user.UserName, companySFID, user.Admin, scopeInfo) } return val } @@ -104,9 +112,16 @@ func IsUserAuthorizedForProjectTree(ctx context.Context, user *auth.User, projec log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorized(auth.Project, projectSFID, true) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s tree, admin flag=%t", + user.UserName, projectSFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s tree, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, user.Admin, scopeInfo) } return val } @@ -136,9 +151,16 @@ func IsUserAuthorizedForProject(ctx context.Context, user *auth.User, projectSFI log.WithFields(f).Debugf("checking project scope for projectSFID: %s...", projectSFID) val := user.IsUserAuthorizedForProjectScope(projectSFID) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s", projectSFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s, admin flag=%t", + user.UserName, projectSFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s", projectSFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, user.Admin, scopeInfo) } return val } @@ -172,7 +194,13 @@ func IsUserAuthorizedForAnyProjects(ctx context.Context, user *auth.User, projec } } - log.WithFields(f).Debugf("project scope checks failed for: %s...", strings.Join(projectSFIDs, ",")) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for project scope checks for any projects: %s, admin flag=%t, scopeInfo: %s", + user.UserName, strings.Join(projectSFIDs, ","), user.Admin, scopeInfo) return false } @@ -200,9 +228,16 @@ func IsUserAuthorizedForProjectOrganization(ctx context.Context, user *auth.User val := user.IsUserAuthorizedByProject(projectSFID, companySFID) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t", + user.UserName, projectSFID, companySFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s", projectSFID, companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, companySFID, user.Admin, scopeInfo) } return val } @@ -241,7 +276,13 @@ func IsUserAuthorizedForAnyProjectOrganization(ctx context.Context, user *auth.U } } - log.WithFields(f).Debugf("user is not authorized for any projectSFID: %s + companySFID: %s", strings.Join(projectSFIDs, ","), companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for any projectSFID: %s + companySFID: %s, admin flag=%t, scopeInfo: %s", + user.UserName, strings.Join(projectSFIDs, ","), companySFID, user.Admin, scopeInfo) return false } @@ -270,9 +311,16 @@ func IsUserAuthorizedForProjectOrganizationTree(ctx context.Context, user *auth. val := user.IsUserAuthorized(auth.ProjectOrganization, projectSFID+"|"+companySFID, true) if val { - log.WithFields(f).Debugf("user is authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + log.WithFields(f).Debugf("user '%s' is authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t", + user.UserName, projectSFID, companySFID, user.Admin) } else { - log.WithFields(f).Debugf("user is not authorized for projectSFID: %s + companySFID: %s tree", projectSFID, companySFID) + var scopeInfo string + for i, scope := range user.Scopes { + scopeInfo = fmt.Sprintf("%sscope[%d] = {type=%s, id=%s, level=%s, role=%s, related=[%s]} ", + scopeInfo, i, scope.Type, scope.ID, scope.Level, scope.Role, strings.Join(scope.Related, ",")) + } + log.WithFields(f).Debugf("user '%s' is not authorized for projectSFID: %s + companySFID: %s tree, admin flag=%t, scopeInfo: %s", + user.UserName, projectSFID, companySFID, user.Admin, scopeInfo) } return val From 4b9c68b071d4038b81b9de58e2bc80853ae37812 Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 12 Nov 2021 10:50:42 -0800 Subject: [PATCH 0565/1276] Resolved Update Status Issue (#3368) --- cla-backend/cla/models/github_models.py | 61 ++++++++++++++----------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 59a1bc6e2..f7b01d39e 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -437,10 +437,11 @@ def update_change_request(self, installation_id, github_repository_id, change_re author_username = author_info[1] author_email = author_info[2] cla.log.debug(f'{fn} - PR: {pull_request.number}, ' - f'processing sha: {commit_sha} ' - f'from author id: {author_id}, username: {author_username}, email: {author_email}') + f'processing sha: {commit_sha} ' + f'from author id: {author_id}, username: {author_username}, email: {author_email}') else: - cla.log.debug(f'{fn} - PR: {pull_request.number}, processing sha: {commit_sha} with invalid author details') + cla.log.debug( + f'{fn} - PR: {pull_request.number}, processing sha: {commit_sha} with invalid author details') handle_commit_from_user(project, commit_sha, author_info, signed, missing) cla.log.debug(f'{fn} - PR: {pull_request.number}, ' @@ -904,23 +905,23 @@ def get_pull_request_commit_authors(pull_request): # https://pygithub.readthedocs.io/en/latest/github_objects/NamedUser.html if commit.author.name is not None: cla.log.debug('PR: {}, GitHub commit.author.name author found for commit SHA {}, ' - 'author id: {}, name: {}, email: {}'. - format(pull_request.number, commit.sha, commit.author.id, - commit.author.name, commit.author.email)) + 'author id: {}, name: {}, email: {}'. + format(pull_request.number, commit.sha, commit.author.id, + commit.author.name, commit.author.email)) commit_authors.append((commit.sha, (commit.author.id, commit.author.name, commit.author.email))) elif commit.author.login is not None: cla.log.debug('PR: {}, GitHub commit.author.login author found for commit SHA {}, ' - 'author id: {}, login: {}, email: {}'. - format(pull_request.number, commit.sha, commit.author.id, - commit.author.login, commit.author.email)) + 'author id: {}, login: {}, email: {}'. + format(pull_request.number, commit.sha, commit.author.id, + commit.author.login, commit.author.email)) commit_authors.append((commit.sha, (commit.author.id, commit.author.login, commit.author.email))) else: cla.log.debug(f'PR: {pull_request.number}, GitHub commit.author.name and commit.author.login ' - f'author information NOT found for commit SHA {commit.sha}, ' - f'author id: {commit.author.id}, ' - f'name: {commit.author.name}, ' - f'login: {commit.author.login}, ' - f'email: {commit.author.email}') + f'author information NOT found for commit SHA {commit.sha}, ' + f'author id: {commit.author.id}, ' + f'name: {commit.author.name}, ' + f'login: {commit.author.login}, ' + f'email: {commit.author.email}') commit_authors.append((commit.sha, None)) except (GithubException, IncompletableObject) as ex: cla.log.debug(f'Commit sha: {commit.sha} exception: {ex}') @@ -930,12 +931,13 @@ def get_pull_request_commit_authors(pull_request): # only has date, name and email attributes - no ID attribute/value # https://pygithub.readthedocs.io/en/latest/github_objects/GitAuthor.html cla.log.debug('PR: {}, GitHub NamedUser author NOT found for commit SHA {}, ' - 'however, found GitAuthor author id: None, name: {}, email: {}'. - format(pull_request.number, commit.sha, - commit.commit.author.name, commit.commit.author.email)) + 'however, found GitAuthor author id: None, name: {}, email: {}'. + format(pull_request.number, commit.sha, + commit.commit.author.name, commit.commit.author.email)) commit_authors.append((commit.sha, (None, commit.commit.author.name, commit.commit.author.email))) except (GithubException, IncompletableObject): - cla.log.warning('PR: {}, could not find any commit author for SHA {}'.format(pull_request.number, commit.sha)) + cla.log.warning( + 'PR: {}, could not find any commit author for SHA {}'.format(pull_request.number, commit.sha)) commit_authors.append((commit.sha, None)) else: cla.log.warning('PR: {}, could not find any commit author for SHA {}'. @@ -992,7 +994,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep """ notification = cla.conf['GITHUB_PR_NOTIFICATION'] both = notification == 'status+comment' or notification == 'comment+status' - last_commit = pull_request.get_commits()[0] + last_commit = pull_request.get_commits().reversed[0] # Here we update the PR status by adding/updating the PR body - this is the way the EasyCLA app # knows if it is pass/fail. @@ -1001,7 +1003,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep text = "" for authors in missing: # Check for valid github id - if authors[1] is None or (authors[1] and authors[1][0] is None): + if authors[1] is None or (authors[1] and authors[1][0] is None): help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" else: help_url = cla.utils.get_full_sign_url('github', str(installation_id), github_repository_id, @@ -1075,7 +1077,8 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep context, body = cla.utils.assemble_cla_status(context_name, signed=False) sign_url = cla.utils.get_full_sign_url( 'github', str(installation_id), github_repository_id, pull_request.number, project_version) - cla.log.debug(f'Creating new CLA {state} status - {len(signed)} passed, {missing}, signing url: {sign_url}') + cla.log.debug(f'Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + f'signing url: {sign_url}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) elif signed is not None and len(signed) > 0: state = 'success' @@ -1085,7 +1088,8 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep sign_url = cla.conf["CLA_LANDING_PAGE"] # Remove this once signature detail page ready. sign_url = os.path.join(sign_url, "#/") sign_url = append_project_version_to_url(address=sign_url, project_version=project_version) - cla.log.debug(f'Creating new CLA {state} status - {len(signed)} passed, {missing}, signing url: {sign_url}') + cla.log.debug(f'Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + f'signing url: {sign_url}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) else: # error condition - should have a least one committer and they would be in one of the above @@ -1096,7 +1100,8 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep context, body = cla.utils.assemble_cla_status(context_name, signed=False) sign_url = cla.utils.get_full_sign_url( 'github', str(installation_id), github_repository_id, pull_request.number, project_version) - cla.log.debug(f'Creating new CLA {state} status - {len(signed)} passed, {missing}, signing url: {sign_url}') + cla.log.debug(f'Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + f'signing url: {sign_url}') cla.log.warning('This is an error condition - should have at least one committer in one of these lists: ' f'{len(signed)} passed, {missing}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) @@ -1130,11 +1135,13 @@ def create_commit_status(pull_request, commit_hash, state, sign_url, body, conte return # context is a string label to differentiate one signer status from another signer status. # committer name is used as context label - commit_obj.create_status(state, sign_url, body, context) - cla.log.info(f'Successfully posted status {state} on PR {pull_request.number}: Commit {commit_hash}' - f'with SignUrl : {sign_url}') + cla.log.info(f'Updating status with state \'{state}\' on PR {pull_request.number} for commit {commit_hash}...') + # returns github.CommitStatus.CommitStatus + resp = commit_obj.create_status(state, sign_url, body, context) + cla.log.info(f'Successfully posted status \'{state}\' on PR {pull_request.number}: Commit {commit_hash} ' + f'with SignUrl : {sign_url} with response: {resp}') except GithubException as exc: - cla.log.error(f'Could not post status {state} on PR: {pull_request.number}, ' + cla.log.error(f'Could not post status \'{state}\' on PR: {pull_request.number}, ' f'Commit: {commit_hash}, ' f'Response Code: {exc.status}, ' f'Message: {exc.data}') From 25590f71f16a2396aa2842e5ed97a56c6c141244 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:22:58 -0800 Subject: [PATCH 0566/1276] Bump aws-sdk from 2.798.0 to 2.814.0 in /infra (#3375) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- infra/package.json | 2 +- infra/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infra/package.json b/infra/package.json index fa18f216f..2ca15c636 100644 --- a/infra/package.json +++ b/infra/package.json @@ -7,7 +7,7 @@ "@types/node": "^8.0.0" }, "dependencies": { - "aws-sdk": "^2.623.0", + "aws-sdk": "^2.814.0", "bluebird": "^3.7.2", "commander": "^4.1.1" } diff --git a/infra/yarn.lock b/infra/yarn.lock index e52276c48..9a396b0ce 100644 --- a/infra/yarn.lock +++ b/infra/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== -aws-sdk@^2.623.0: - version "2.798.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.798.0.tgz#fd108e0569d7210816003fc8cc0c995b39854ad6" - integrity sha512-7BMCH90yFpMmCF5uxZiiKMMzIAFibZz8b6CLGw/92FgMd86ZedpNqDyaikcGv1yOVtOlNCCnXN6OglC8/vwm4Q== +aws-sdk@^2.814.0: + version "2.814.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.814.0.tgz#7a1c36006e0b5826f14bd2511b1d229ef6814bb0" + integrity sha512-empd1m/J/MAkL6d9OeRpmg9thobULu0wk4v8W3JToaxGi2TD7PIdvE6yliZKyOVAdJINhBWEBhxR4OUIHhcGbQ== dependencies: buffer "4.9.2" events "1.1.1" From 3b1aa81f435d6b356a295469517f20c972f352aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:51:09 -0800 Subject: [PATCH 0567/1276] Bump aws-sdk from 2.488.0 to 2.814.0 in /cla-frontend-project-console/src (#3372) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/src/package.json | 2 +- cla-frontend-project-console/src/yarn.lock | 24 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cla-frontend-project-console/src/package.json b/cla-frontend-project-console/src/package.json index 5e690d08e..10f96381a 100644 --- a/cla-frontend-project-console/src/package.json +++ b/cla-frontend-project-console/src/package.json @@ -39,7 +39,7 @@ "@types/lodash": "4.14.121", "@types/node": "^8.0.17", "auth0-js": "^9.13.2", - "aws-sdk": "^2.304.0", + "aws-sdk": "^2.814.0", "chart.js": "^2.9.4", "google-libphonenumber": "^2.0.18", "graceful-fs": "^4.2.2", diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index 191d14eee..e452d1fc7 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -424,13 +424,14 @@ autoprefixer@^7.2.6: postcss "^6.0.17" postcss-value-parser "^3.2.3" -aws-sdk@^2.304.0: - version "2.488.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.488.0.tgz#1664e14743d93793d2ffc4b2e70b7ce19c008d84" +aws-sdk@^2.814.0: + version "2.814.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.814.0.tgz#7a1c36006e0b5826f14bd2511b1d229ef6814bb0" + integrity sha512-empd1m/J/MAkL6d9OeRpmg9thobULu0wk4v8W3JToaxGi2TD7PIdvE6yliZKyOVAdJINhBWEBhxR4OUIHhcGbQ== dependencies: - buffer "4.9.1" + buffer "4.9.2" events "1.1.1" - ieee754 "1.1.8" + ieee754 "1.1.13" jmespath "0.15.0" querystring "0.2.0" sax "1.2.1" @@ -639,9 +640,10 @@ buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" -buffer@4.9.1, buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" +buffer@4.9.2, buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1902,11 +1904,7 @@ idtoken-verifier@^2.0.2: unfetch "^4.1.0" url-join "^4.0.1" -ieee754@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - -ieee754@^1.1.4: +ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" From 35040ae76cbe9d0d814423ebbc524068adc2c09e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:51:42 -0800 Subject: [PATCH 0568/1276] Bump aws-sdk from 2.488.0 to 2.814.0 in /cla-frontend-contributor-console/src (#3370) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/package.json | 2 +- .../src/yarn.lock | 24 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cla-frontend-contributor-console/src/package.json b/cla-frontend-contributor-console/src/package.json index db7f27eab..e17c2b465 100644 --- a/cla-frontend-contributor-console/src/package.json +++ b/cla-frontend-contributor-console/src/package.json @@ -40,7 +40,7 @@ "@types/lodash": "4.14.121", "@types/node": "^8.0.17", "auth0-js": "^9.13.2", - "aws-sdk": "^2.304.0", + "aws-sdk": "^2.814.0", "chart.js": "^2.9.4", "google-libphonenumber": "^2.0.18", "graceful-fs": "^4.2.2", diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index f93af2470..7359874d0 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -406,13 +406,14 @@ autoprefixer@^7.2.6: postcss "^6.0.17" postcss-value-parser "^3.2.3" -aws-sdk@^2.304.0: - version "2.488.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.488.0.tgz#1664e14743d93793d2ffc4b2e70b7ce19c008d84" +aws-sdk@^2.814.0: + version "2.814.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.814.0.tgz#7a1c36006e0b5826f14bd2511b1d229ef6814bb0" + integrity sha512-empd1m/J/MAkL6d9OeRpmg9thobULu0wk4v8W3JToaxGi2TD7PIdvE6yliZKyOVAdJINhBWEBhxR4OUIHhcGbQ== dependencies: - buffer "4.9.1" + buffer "4.9.2" events "1.1.1" - ieee754 "1.1.8" + ieee754 "1.1.13" jmespath "0.15.0" querystring "0.2.0" sax "1.2.1" @@ -613,9 +614,10 @@ buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" -buffer@4.9.1, buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" +buffer@4.9.2, buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1849,11 +1851,7 @@ idtoken-verifier@^2.0.2: unfetch "^4.1.0" url-join "^4.0.1" -ieee754@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - -ieee754@^1.1.4: +ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" From bf2b09f21aaebbbc82d3b6c35135352475787fda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:52:09 -0800 Subject: [PATCH 0569/1276] Bump aws-sdk from 2.803.0 to 2.1042.0 in /cla-frontend-corporate-console (#3384) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 25 +++++------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index 19371dfe4..aad9f80bf 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -1030,25 +1030,10 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -aws-sdk@^2.224.1: - version "2.803.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.803.0.tgz#054b850ec8ceb3b6ccb18d2a4cc6139ef3fbb7ef" - integrity sha512-rcjwbslKEtoOcxeskwaF6lVUgOxnEqVIeufzO+W8gnAzEaUamunqCAFHsqCFldF5efkHISCDbTKLhMHcEZ6yyg== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.803.0: - version "2.806.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.806.0.tgz#5bd3ac07425ee34f5dc9dc40a38d78c49066a88f" - integrity sha512-kCrGfZyzZiS56qblXEzznkTi64ZbzhQGlbyEjDI9cIUjX4dA9IyqvNWUdJvUQoZmiEnObbuXMVrv7blJzT8uhQ== +aws-sdk@^2.224.1, aws-sdk@^2.803.0: + version "2.1042.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1042.0.tgz#c3385bf6cbb8f97c2cde427c0ab3d9720fa4b82a" + integrity sha512-JWjs6+Zhuo990WYH1iQR1njGOvoCFzaf2azX/zh3JdL7QNwzdqczoODMj0wb22831/7EoPDGaXHqp7aQwDsxwA== dependencies: buffer "4.9.2" events "1.1.1" @@ -1070,7 +1055,7 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2, axios@^0.21.2: +axios@^0.19.2, axios@^0.21.4: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== From 9f67ed0ebbf26ec246a9a508dfc51c6753ba39e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:52:42 -0800 Subject: [PATCH 0570/1276] Bump aws-sdk from 2.787.0 to 2.1042.0 (#3383) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0f54c6ff2..7f7022f07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -808,9 +808,9 @@ at-least-node@^1.0.0: integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== aws-sdk@^2.786.0: - version "2.787.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.787.0.tgz#4d8966d11c7dbe770de26632e552c97b2d91e340" - integrity sha512-3WlUdWqUB8Vhdvj/7TENr/7SEmQzxmnHxOJ8l2WjZbcMRSuI0/9Ym4p1TC3hf21VDVDhkdGlw60QqpZQ1qb+Mg== + version "2.1042.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1042.0.tgz#c3385bf6cbb8f97c2cde427c0ab3d9720fa4b82a" + integrity sha512-JWjs6+Zhuo990WYH1iQR1njGOvoCFzaf2azX/zh3JdL7QNwzdqczoODMj0wb22831/7EoPDGaXHqp7aQwDsxwA== dependencies: buffer "4.9.2" events "1.1.1" @@ -855,9 +855,9 @@ base64-arraybuffer@0.1.4: integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bcrypt-pbkdf@^1.0.0: version "1.0.2" @@ -2505,11 +2505,16 @@ iconv-lite@^0.4.24, iconv-lite@~0.4.11: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@1.1.13, ieee754@^1.1.4: +ieee754@1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" From 3413e1bc7c9e3c48951f4ced6bc5b9abccfb80ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:53:12 -0800 Subject: [PATCH 0571/1276] Bump aws-sdk from 2.489.0 to 2.1042.0 in /cla-frontend-project-console (#3381) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 178 +++++++++++++++++-------- 1 file changed, 123 insertions(+), 55 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index ba16f87a1..83d4e005f 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -982,6 +982,13 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types@^0.13.2: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== + dependencies: + tslib "^2.0.1" + async-each@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -1021,25 +1028,10 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -aws-sdk@^2.224.1: - version "2.489.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.489.0.tgz#5ca376546dd3e912febf3114d2bcfa4d82c5584a" - integrity sha512-P85rLUWpO2DAG/ZQoOscNiwTKY4LgWNCHarw7RACGt0vOGiMkuKL0JN+FvurcmDEBj0xRUXsKDMfwWHDFhA2Jw== - dependencies: - buffer "4.9.1" - events "1.1.1" - ieee754 "1.1.8" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.803.0: - version "2.806.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.806.0.tgz#5bd3ac07425ee34f5dc9dc40a38d78c49066a88f" - integrity sha512-kCrGfZyzZiS56qblXEzznkTi64ZbzhQGlbyEjDI9cIUjX4dA9IyqvNWUdJvUQoZmiEnObbuXMVrv7blJzT8uhQ== +aws-sdk@^2.224.1, aws-sdk@^2.803.0: + version "2.1042.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1042.0.tgz#c3385bf6cbb8f97c2cde427c0ab3d9720fa4b82a" + integrity sha512-JWjs6+Zhuo990WYH1iQR1njGOvoCFzaf2azX/zh3JdL7QNwzdqczoODMj0wb22831/7EoPDGaXHqp7aQwDsxwA== dependencies: buffer "4.9.2" events "1.1.1" @@ -1061,7 +1053,7 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2, axios@^0.21.2: +axios@^0.19.2, axios@^0.21.4: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -1083,12 +1075,7 @@ base64-arraybuffer@0.1.4: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== - -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1253,15 +1240,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer@4.9.1: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - buffer@4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -1271,15 +1249,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" - integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffer@^5.2.1: +buffer@^5.1.0, buffer@^5.2.1: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -1939,6 +1909,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -1982,6 +1957,16 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +degenerator@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" + integrity sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ== + dependencies: + ast-types "^0.13.2" + escodegen "^1.8.1" + esprima "^4.0.0" + vm2 "^3.9.3" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2260,6 +2245,18 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escodegen@^1.8.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + esniff@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/esniff/-/esniff-1.1.0.tgz#c66849229f91464dede2e0d40201ed6abf65f2ac" @@ -2268,7 +2265,7 @@ esniff@^1.1.0: d "1" es5-ext "^0.10.12" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -2278,6 +2275,16 @@ essentials@^1.1.1: resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.1.1.tgz#03befbfbee7078301741279b38a806b6ca624821" integrity sha512-SmaxoAdVu86XkZQM/u6TYSu96ZlFGwhvSk1l9zAkznFuQkMb9mRDS2iq/XWDow7R8OwBwdYH8nLyDKznMD+GWw== +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -2480,6 +2487,11 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + fast-safe-stringify@^2.0.4: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" @@ -3152,17 +3164,12 @@ iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4, ic dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@1.1.13, ieee754@^1.1.4: +ieee754@1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ieee754@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - integrity sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q= - -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3311,6 +3318,11 @@ ionic@^3.20.0: semver "^5.3.0" tslib "^1.8.0" +ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + ipaddr.js@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" @@ -3794,6 +3806,14 @@ leek@0.0.24: lodash.assign "^3.2.0" rsvp "^3.0.21" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -4539,6 +4559,18 @@ optional@^0.1.3: resolved "https://registry.yarnpkg.com/optional/-/optional-0.1.4.tgz#cdb1a9bedc737d2025f690ceeb50e049444fd5b3" integrity sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw== +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -4611,6 +4643,15 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +pac-resolver@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" + integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA== + dependencies: + degenerator "^3.0.1" + ip "^1.1.5" + netmask "^2.0.1" + package-json@^6.3.0: version "6.5.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" @@ -4758,6 +4799,11 @@ prebuild-install@5.3.0: tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -5705,7 +5751,7 @@ source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0: +source-map@^0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -6155,6 +6201,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6167,6 +6218,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -6312,12 +6370,12 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@3.3.2, uuid@^3.0.1: +uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -6341,6 +6399,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vm2@^3.9.3: + version "3.9.5" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.5.tgz#5288044860b4bbace443101fcd3bddb2a0aa2496" + integrity sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng== + warning-symbol@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" @@ -6424,6 +6487,11 @@ winston@3.2.1: triple-beam "^1.3.0" winston-transport "^4.3.0" +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" From 4ec66960b4b29a840c4898f2e3785c3b57529459 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Dec 2021 16:12:47 -0800 Subject: [PATCH 0572/1276] Bump aws-sdk from 2.745.0 to 2.1042.0 in /cla-frontend-contributor-console (#3382) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 46 +++++----------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 6179b2961..b7a008873 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -1030,25 +1030,10 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -aws-sdk@^2.224.1: - version "2.745.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.745.0.tgz#543ae8487176da9a2d3b295aee51b0c5df42e8cd" - integrity sha512-YTmDvxb0foJC/iZOSsn+MdO4g9nPnlyRdhIpFqvPUkrFzWn5rP4mPtDJan2Eu0j4R02pdwfDhmvr82ckH2w7OQ== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.803.0: - version "2.806.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.806.0.tgz#5bd3ac07425ee34f5dc9dc40a38d78c49066a88f" - integrity sha512-kCrGfZyzZiS56qblXEzznkTi64ZbzhQGlbyEjDI9cIUjX4dA9IyqvNWUdJvUQoZmiEnObbuXMVrv7blJzT8uhQ== +aws-sdk@^2.224.1, aws-sdk@^2.803.0: + version "2.1042.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1042.0.tgz#c3385bf6cbb8f97c2cde427c0ab3d9720fa4b82a" + integrity sha512-JWjs6+Zhuo990WYH1iQR1njGOvoCFzaf2azX/zh3JdL7QNwzdqczoODMj0wb22831/7EoPDGaXHqp7aQwDsxwA== dependencies: buffer "4.9.2" events "1.1.1" @@ -1070,7 +1055,7 @@ aws4@^1.10.1, aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2, axios@^0.21.2: +axios@^0.19.2, axios@^0.21.4: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -1092,12 +1077,7 @@ base64-arraybuffer@0.1.4: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1271,15 +1251,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.1.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffer@^5.2.1: +buffer@^5.1.0, buffer@^5.2.1: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -3211,12 +3183,12 @@ iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@~0.4.11: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@1.1.13, ieee754@^1.1.4: +ieee754@1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== From ba7c6e481361ee1f416f0a58df77d79745f07761 Mon Sep 17 00:00:00 2001 From: Denis Kyorov Date: Thu, 9 Dec 2021 18:34:26 +0200 Subject: [PATCH 0573/1276] naming typo error (#3388) --- cla-backend/cla/models/dynamo_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py index 416be252b..df7973a87 100644 --- a/cla-backend/cla/models/dynamo_models.py +++ b/cla-backend/cla/models/dynamo_models.py @@ -1541,7 +1541,7 @@ class Meta: user_gitlab_id = NumberAttribute(null=True) user_gitlab_username = UnicodeAttribute(null=True) user_gitlab_id_index = GitLabIDIndex() - user_github_username_index = GitLabUsernameIndex() + user_gitlab_username_index = GitLabUsernameIndex() user_ldap_id = UnicodeAttribute(null=True) user_github_id_index = GitHubUserIndex() github_user_external_id_index = GithubUserExternalIndex() From fb8ef36fa712a813cd84f7593198b3dcd64d61ec Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 12 Jan 2022 15:37:33 -0800 Subject: [PATCH 0574/1276] [3356] Resolves Authority Name Invalid Username Issue (#3394) --- cla-backend-go/swagger/cla.v2.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index a79de6e32..a36e54150 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -5475,8 +5475,11 @@ definitions: example: false description: send signing request as email. This should be set to true when requestor is not signatory. authority_name: + type: string + example: "Derk Miyamoto" description: the name of the CLA signatory - $ref: './common/properties/user-name.yaml' + minLength: 2 + maxLength: 255 authority_email: $ref: './common/properties/email.yaml' description: the email of the CLA Signatory From 192d4d6a726723f390a3e3d004ae0dd211515418 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 13 Jan 2022 17:03:53 -0800 Subject: [PATCH 0575/1276] Updated Gerrit URL Swagger Input Constraints (#3396) --- cla-backend-go/swagger/common/add-gerrit-input.yaml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/swagger/common/add-gerrit-input.yaml b/cla-backend-go/swagger/common/add-gerrit-input.yaml index eb38ce96d..073d6cce7 100644 --- a/cla-backend-go/swagger/common/add-gerrit-input.yaml +++ b/cla-backend-go/swagger/common/add-gerrit-input.yaml @@ -15,7 +15,7 @@ properties: pattern: '^[\w\p{L}][\w\s\p{L}\[\]\+\-\{\}\(\)\.\,\+\-]*$' gerritUrl: description: | - the gerrit url - must be one of the currently supported LF managed Gerrit instances: + the gerrit url - should be one of the supported LF managed Gerrit instances, examples are: https://gerrit.linuxfoundation.org https://gerrit.onap.org https://gerrit.o-ran-sc.org @@ -23,12 +23,9 @@ properties: https://gerrit.opnfv.org example: 'https://gerrit.onap.org' type: string - enum: - - https://gerrit.linuxfoundation.org - - https://gerrit.onap.org - - https://gerrit.o-ran-sc.org - - https://gerrit.tungsten.io - - https://gerrit.opnfv.org + minLength: 10 + maxLength: 255 + pattern: ^(?:http(s)?:\/\/).+$ groupIdCcla: type: string description: the LDAP group ID for CCLA From 7fca7de0f52e862a0c1479430596df8bdd8fcd28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:14:40 -0800 Subject: [PATCH 0576/1276] Bump log4js from 6.3.0 to 6.4.0 in /cla-landing-page (#3403) Bumps [log4js](https://github.com/log4js-node/log4js-node) from 6.3.0 to 6.4.0. - [Release notes](https://github.com/log4js-node/log4js-node/releases) - [Changelog](https://github.com/log4js-node/log4js-node/blob/master/CHANGELOG.md) - [Commits](https://github.com/log4js-node/log4js-node/compare/v6.3.0...v6.4.0) --- updated-dependencies: - dependency-name: log4js dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-landing-page/yarn.lock | 104 ++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 88a57b37d..db89ecad8 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -4096,15 +4096,10 @@ data-uri-to-buffer@1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" integrity sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ== -date-format@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" - integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== - -date-format@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" - integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== +date-format@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.3.tgz#f63de5dc08dc02efd8ef32bf2a6918e486f35873" + integrity sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ== dayjs@^1.10.4, dayjs@^1.10.6: version "1.10.6" @@ -4125,10 +4120,10 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -4146,13 +4141,6 @@ debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: dependencies: ms "^2.1.1" -debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -5420,11 +5408,16 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^2.0.1, flatted@^2.0.2: +flatted@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -5536,6 +5529,15 @@ fs-extra@4.0.2: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -5545,15 +5547,6 @@ fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" @@ -5853,11 +5846,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + graceful-fs@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" @@ -7081,11 +7079,11 @@ jsonfile@^4.0.0: graceful-fs "^4.1.6" jsonfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" - integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: - universalify "^1.0.0" + universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" @@ -7458,15 +7456,15 @@ log-utils@^0.2.1: warning-symbol "^0.1.0" log4js@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb" - integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw== + version "6.4.0" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.0.tgz#3f63ccfc8033c83cd617a4d2d50e48be5944eae9" + integrity sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ== dependencies: - date-format "^3.0.0" - debug "^4.1.1" - flatted "^2.0.1" - rfdc "^1.1.4" - streamroller "^2.2.4" + date-format "^4.0.3" + debug "^4.3.3" + flatted "^3.2.4" + rfdc "^1.3.0" + streamroller "^3.0.2" log@^6.0.0: version "6.0.0" @@ -10121,10 +10119,10 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rfdc@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" - integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rgb-regex@^1.0.1: version "1.0.1" @@ -11067,14 +11065,14 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -streamroller@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" - integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ== +streamroller@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.0.2.tgz#30418d0eee3d6c93ec897f892ed098e3a81e68b7" + integrity sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA== dependencies: - date-format "^2.1.0" + date-format "^4.0.3" debug "^4.1.1" - fs-extra "^8.1.0" + fs-extra "^10.0.0" strict-uri-encode@^2.0.0: version "2.0.0" From a1ae87fa3fe1fa78cb5e05297b37e1ff09587b39 Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 24 Jan 2022 09:51:58 -0800 Subject: [PATCH 0577/1276] Added PR Fetch Retry Logic (#3404) --- cla-backend/cla/models/github_models.py | 30 +++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index f7b01d39e..0b93e5d6b 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -6,6 +6,7 @@ """ import json import os +import time import uuid from typing import List, Union, Optional @@ -363,13 +364,28 @@ def update_change_request(self, installation_id, github_repository_id, change_re fn = 'update_change_request' # Queries GH for the complete pull request details, see: # https://developer.github.com/v3/pulls/#response-1 - 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: - cla.log.error(f'{fn} - Invalid PR: {change_request_id} . (Unable to cast to integer) ') - return + + # 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. + tries = 3 + for i in range(tries): + 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 PR from GitHub.') + if i <= tries: + cla.log.debug(f'{fn} - attempt {i + 1} - waiting to retry...') + time.sleep(2) + continue + else: + cla.log.debug(f'{fn} - attempt {i + 1} - exhausted retries - unable to load PR from GitHub.') + # 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}') # Get all unique users/authors involved in this PR - returns a list of From 93eb6ccd8149f9f3cd3bc7ea9390cb2a54c74f4d Mon Sep 17 00:00:00 2001 From: David Deal Date: Mon, 7 Feb 2022 17:14:54 -0800 Subject: [PATCH 0578/1276] Added Defensive Checks When Processing GitHub Organization Repositories (#3410) --- cla-backend-go/github/github_installation.go | 4 +- .../github_organizations/helpers.go | 2 +- .../github_organizations/service.go | 9 +++++ cla-backend-go/utils/project_helpers.go | 4 +- cla-backend-go/v2/cla_groups/service.go | 2 +- .../v2/github_organizations/service.go | 21 +++++++++- cla-backend-go/v2/project-service/client.go | 38 +++++++++++++++---- cla-backend/cla/models/github_models.py | 1 + 8 files changed, 66 insertions(+), 15 deletions(-) diff --git a/cla-backend-go/github/github_installation.go b/cla-backend-go/github/github_installation.go index d8f2b9fe2..79f845ff2 100644 --- a/cla-backend-go/github/github_installation.go +++ b/cla-backend-go/github/github_installation.go @@ -36,7 +36,7 @@ func GetInstallationRepositories(ctx context.Context, installationID int64) ([]* // See pagination examples: https://godoc.org/github.com/google/go-github/github opts := &github.ListOptions{ - PerPage: 50, + PerPage: 100, // Max 100 per the GitHub API } for { @@ -47,7 +47,7 @@ func GetInstallationRepositories(ctx context.Context, installationID int64) ([]* return nil, errors.New(msg) } - log.WithFields(f).Debugf("fetched %d records...", len(listReposResponse.Repositories)) + //log.WithFields(f).Debugf("fetched %d records...", len(listReposResponse.Repositories)) allRepos = append(allRepos, listReposResponse.Repositories...) if resp.NextPage == 0 { break diff --git a/cla-backend-go/github_organizations/helpers.go b/cla-backend-go/github_organizations/helpers.go index 0157ac0bf..c1610aa53 100644 --- a/cla-backend-go/github_organizations/helpers.go +++ b/cla-backend-go/github_organizations/helpers.go @@ -67,7 +67,7 @@ func buildGithubOrganizationListModels(ctx context.Context, githubOrganizations return } - log.WithFields(f).Debugf("found %d repositories from GitHUb using the installation id: %d...", + log.WithFields(f).Debugf("found %d repositories from GitHub using the installation id: %d...", len(list), ghorg.OrganizationInstallationID) for _, repoInfo := range list { ghorg.Repositories.List = append(ghorg.Repositories.List, &models.GithubRepositoryInfo{ diff --git a/cla-backend-go/github_organizations/service.go b/cla-backend-go/github_organizations/service.go index 4e0f50eea..907a10c37 100644 --- a/cla-backend-go/github_organizations/service.go +++ b/cla-backend-go/github_organizations/service.go @@ -62,6 +62,9 @@ func (s Service) AddGitHubOrganization(ctx context.Context, projectSFID string, log.WithFields(f).Warnf("problem fetching github organizations by projectSFID, error: %+v", projErr) return nil, projErr } + if parentProjectSFID == "" { + parentProjectSFID = projectSFID + } // check if valid cla group id is passed if input.AutoEnabledClaGroupID != "" { @@ -103,6 +106,9 @@ func (s Service) GetGitHubOrganizations(ctx context.Context, projectSFID string) log.WithFields(f).Warnf("problem fetching project parent SFID, error: %+v", projErr) return nil, projErr } + if parentProjectSFID == "" { + parentProjectSFID = projectSFID + } //Get SF Project projectDetails, projDetailsErr := v2ProjectService.GetClient().GetProject(projectSFID) @@ -189,6 +195,9 @@ func (s Service) DeleteGitHubOrganization(ctx context.Context, projectSFID strin log.WithFields(f).Warnf("problem fetching project parent SFID, error: %+v", projErr) return projErr } + if parentProjectSFID == "" { + parentProjectSFID = projectSFID + } err := s.ghRepository.GitHubDisableRepositoriesOfOrganization(ctx, parentProjectSFID, githubOrgName) if err != nil { diff --git a/cla-backend-go/utils/project_helpers.go b/cla-backend-go/utils/project_helpers.go index 2934f3821..22beec033 100644 --- a/cla-backend-go/utils/project_helpers.go +++ b/cla-backend-go/utils/project_helpers.go @@ -7,7 +7,7 @@ import "github.com/communitybridge/easycla/cla-backend-go/v2/project-service/mod // GetProjectParentSFID returns the project parent SFID if available, otherwise returns empty string func GetProjectParentSFID(project *models.ProjectOutputDetailed) string { - if project == nil || project.Foundation == nil || project.Foundation.ID == "" { + if project == nil || project.Foundation == nil || project.Foundation.ID == "" || project.Foundation.Name == "" || project.Foundation.Slug == "" { return "" } return project.Foundation.ID @@ -15,7 +15,7 @@ func GetProjectParentSFID(project *models.ProjectOutputDetailed) string { // IsProjectHaveParent returns true if the specified project has a parent func IsProjectHaveParent(project *models.ProjectOutputDetailed) bool { - return project != nil && project.Foundation != nil && project.Foundation.ID != "" && project.Foundation.Name != "" + return project != nil && project.Foundation != nil && project.Foundation.ID != "" && project.Foundation.Name != "" && project.Foundation.Slug != "" } // IsProjectHasRootParent determines if a given project has a root parent. A root parent is a parent that is empty parent or the parent is TLF or LFProjects diff --git a/cla-backend-go/v2/cla_groups/service.go b/cla-backend-go/v2/cla_groups/service.go index 51601dd53..d429fceb5 100644 --- a/cla-backend-go/v2/cla_groups/service.go +++ b/cla-backend-go/v2/cla_groups/service.go @@ -406,7 +406,7 @@ func (s *service) ListClaGroupsForFoundationOrProject(ctx context.Context, proje // Get Parent parentDetails, parentDetailErr = v2ProjectService.GetClient().GetProject(parentSFID) - if parentDetailErr != nil { + if parentDetailErr != nil || parentDetails == nil { return nil, parentDetailErr } } diff --git a/cla-backend-go/v2/github_organizations/service.go b/cla-backend-go/v2/github_organizations/service.go index 9c57eaaed..99eacc854 100644 --- a/cla-backend-go/v2/github_organizations/service.go +++ b/cla-backend-go/v2/github_organizations/service.go @@ -85,12 +85,23 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) log.WithFields(f).WithError(err).Warn("problem loading project details from the project service") return nil, err } + if project == nil { + log.WithFields(f).Warnf("unable to load project by project SFID: %s", projectSFID) + return nil, nil + } + f["projectName"] = project.Name + f["projectType"] = project.Type + f["projectStatus"] = project.Status var parentProjectSFID string - if !utils.IsProjectHaveParent(project) || utils.IsProjectHasRootParent(project) || utils.GetProjectParentSFID(project) == "" { + if !utils.IsProjectHaveParent(project) { parentProjectSFID = projectSFID } else { parentProjectSFID = utils.GetProjectParentSFID(project) + // If we don't have a valid parent project SFID... + if parentProjectSFID == "" { + parentProjectSFID = projectSFID + } } f["parentProjectSFID"] = parentProjectSFID @@ -170,7 +181,7 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) var repoList []*v1Models.GithubRepository for _, org := range orgs.List { orgRepos, orgReposErr := s.gitV1Repository.GitHubGetRepositoriesByOrganizationName(ctx, org.OrganizationName) - if orgReposErr != nil || orgRepos == nil { + if orgReposErr != nil || len(orgRepos) == 0 { if _, ok := orgReposErr.(*utils.GitHubRepositoryNotFound); ok { log.WithFields(f).Debug(orgReposErr) } else { @@ -184,6 +195,12 @@ func (s service) GetGithubOrganizations(ctx context.Context, projectSFID string) // Remove any duplicates log.WithFields(f).Debugf("processing %d github repositories...", len(repoList)) for _, repo := range repoList { + if repo == nil || repo.RepositoryOrganizationName == "" { + log.WithFields(f).Warnf("repositories record nil or is missing the organization name: %+v - skipping", repo) + continue + } + //log.WithFields(f).Debugf("processing repository: %s", repo.RepositoryURL) + rorg, ok := orgmap[repo.RepositoryOrganizationName] if !ok { log.WithFields(f).Warnf("repositories table contain stale data for organization %s", repo.RepositoryOrganizationName) diff --git a/cla-backend-go/v2/project-service/client.go b/cla-backend-go/v2/project-service/client.go index b0dd1822b..121c3eb1b 100644 --- a/cla-backend-go/v2/project-service/client.go +++ b/cla-backend-go/v2/project-service/client.go @@ -86,7 +86,7 @@ func (pmm *Client) GetProject(projectSFID string) (*models.ProjectOutputDetailed mutex.Unlock() if exists { - log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) + //log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) return existingModel, nil } log.WithFields(f).Debugf("cache miss - cache size: %d", len(projectServiceModels)) @@ -151,9 +151,13 @@ func (pmm *Client) GetParentProject(projectSFID string) (string, error) { // Use our helper function to find the parent, if it exists parentModel, err := pmm.GetParentProjectModel(projectSFID) - if err != nil || parentModel == nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) - return projectSFID, err + if err != nil { + log.WithFields(f).WithError(err).Debugf("unable to lookup parentProjectModel using projectSFID: '%s'", projectSFID) + return "", err + } + if parentModel == nil { + log.WithFields(f).Debugf("unable to lookup parentProjectModel using projectSFID: '%s' - parent project model is nil", projectSFID) + return "", err } return parentModel.ID, nil @@ -177,11 +181,13 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut existingModel, exists = projectServiceModels[projectSFID] mutex.Unlock() if exists { - log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) + //log.WithFields(f).Debugf("cache hit - cache size: %d", len(projectServiceModels)) if !utils.IsProjectHaveParent(existingModel) { + //log.WithFields(f).Debugf("project %+v does not have a parent", existingModel) return nil, nil } + log.WithFields(f).Debugf("project %+v has a parent", existingModel) //// Does this project they have a parent? projectModel.Parent is deprecated and no longer returned, use project.Foundation.ID/Name attribute instead //if existingModel.Foundation.Name == utils.TheLinuxFoundation || existingModel.Foundation.Name == utils.LFProjectsLLC { @@ -191,6 +197,10 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut // Grab the parent ID once projectParentSFID := utils.GetProjectParentSFID(existingModel) + if projectParentSFID == "" { + log.WithFields(f).Debugf("unable to determine project %+v parent", existingModel) + return nil, nil + } // Parent SFID in the cache? mutex.Lock() // exclusive lock to the shared project service model map @@ -207,6 +217,11 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut return nil, err } + if parentProjectModel == nil { + log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel with projectSFID: '%s' - project model is nil", projectParentSFID) + return nil, nil + } + // Save/Update our cache for next time mutex.Lock() // exclusive lock to the shared project service model map projectServiceModels[projectParentSFID] = parentProjectModel @@ -234,10 +249,11 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut // No parent if !utils.IsProjectHaveParent(projectModel) { + //log.WithFields(f).Debugf("project %+v does not have a parent", projectModel) return nil, nil } - // Do they have a parent? projectModel.Parent is deprecated and no longer returned + // Is the parent one of the root parents? if projectModel.Foundation.Name == utils.TheLinuxFoundation || projectModel.Foundation.Name == utils.LFProjectsLLC { log.WithFields(f).Debugf("no parent for projectSFID or %s or %s is the parent...", utils.TheLinuxFoundation, utils.LFProjectsLLC) return nil, nil @@ -245,6 +261,10 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut // Grab the parent ID once projectParentSFID := utils.GetProjectParentSFID(projectModel) + if projectParentSFID == "" { + log.WithFields(f).Debugf("unable to determine project %+v parent", projectModel) + return nil, nil + } // Parent in the cache? mutex.Lock() // exclusive lock to the shared project service model map @@ -257,9 +277,13 @@ func (pmm *Client) GetParentProjectModel(projectSFID string) (*models.ProjectOut // Parent project not in the cache - lookup parentProjectModel, err := pmm.GetProject(projectParentSFID) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to lookup parentProjectModel with projectSFID: '%s'", projectParentSFID) + log.WithFields(f).WithError(err).Debugf("unable to lookup parentProjectModel with projectSFID: '%s'", projectParentSFID) return nil, err } + if parentProjectModel == nil { + log.WithFields(f).WithError(err).Debugf("unable to lookup parentProjectModel with projectSFID: '%s' - project model is nil", projectParentSFID) + return nil, nil + } // Save/Update our cache for next time mutex.Lock() // exclusive lock to the shared project service model map diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 0b93e5d6b..fe21e5aea 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -368,6 +368,7 @@ def update_change_request(self, installation_id, github_repository_id, change_re # 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. + pull_request = {} tries = 3 for i in range(tries): try: From 1e3c3110924a472b0ee19cbae5c32146efffe77a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Feb 2022 08:36:51 -0800 Subject: [PATCH 0579/1276] Bump lxml from 4.6.3 to 4.6.5 in /cla-backend (#3389) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index 0a0545ab9..f0de75363 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -25,7 +25,7 @@ Jinja2==2.11.3 jmespath==0.9.4 lazy-object-proxy==1.4.3 Logbook==1.5.3 -lxml==4.6.3 +lxml==4.6.5 more-itertools==8.0.2 nose2==0.9.1 oauthlib==3.1.0 From 66bf4e01de2d22f32e96b7f0712ec0ca5977cd72 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 8 Feb 2022 09:23:37 -0800 Subject: [PATCH 0580/1276] Updated Documentation Links (#3413) --- cla-backend-go/utils/email.go | 2 +- cla-backend/cla/utils.py | 2 +- .../src/ionic/layout/cla-header/cla-header.html | 2 +- .../src/ionic/layout/cla-header/cla-header.html | 4 ++-- .../src/ionic/layout/cla-header/cla-header.html | 2 +- cla-landing-page/src/app/config/app-settings.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cla-backend-go/utils/email.go b/cla-backend-go/utils/email.go index 36047b891..cbc3b8e90 100644 --- a/cla-backend-go/utils/email.go +++ b/cla-backend-go/utils/email.go @@ -111,7 +111,7 @@ func GetCorporateURL(isV2Project bool) string { func GetEmailHelpContent(showV2HelpLink bool) string { if showV2HelpLink { return `

        If you need help or have questions about EasyCLA, you can -read the documentation or +read the documentation or reach out to us for support.

        ` } diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 30ba64544..9fa3a72a1 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1653,7 +1653,7 @@ def get_email_help_content(show_v2_help_link: bool) -> str: help_link = 'https://docs.linuxfoundation.org/lfx/easycla' if show_v2_help_link: # v2 help link - help_link = 'https://docs.linuxfoundation.org/lfx/v/v2/easycla' + help_link = 'https://docs.linuxfoundation.org/lfx/easycla' return f'

        If you need help or have questions about EasyCLA, you can read the documentation or reach out to us for support.

        ' diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html b/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html index 109f2c997..0ff467fe9 100644 --- a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html +++ b/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html @@ -1,6 +1,6 @@ + faqlink="https://docs.linuxfoundation.org/lfx/easycla/v2-current/getting-started/easycla-faqs" (toggled)="onToggled()"> diff --git a/cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.html b/cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.html index a54320230..4dc043f3f 100644 --- a/cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.html +++ b/cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.html @@ -1,6 +1,6 @@ + faqlink="https://docs.linuxfoundation.org/lfx/easycla/v2-current/getting-started/easycla-faqs" (toggled)="onToggled()"> @@ -21,4 +21,4 @@ {{title}} - \ No newline at end of file + diff --git a/cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.html b/cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.html index 109f2c997..ccec34192 100644 --- a/cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.html +++ b/cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.html @@ -1,6 +1,6 @@ + faqlink="https://docs.linuxfoundation.org/lfx/easycla/v2-current/getting-started/easycla-faqss" (toggled)="onToggled()"> diff --git a/cla-landing-page/src/app/config/app-settings.ts b/cla-landing-page/src/app/config/app-settings.ts index 0e62c4b04..bc914d1a9 100644 --- a/cla-landing-page/src/app/config/app-settings.ts +++ b/cla-landing-page/src/app/config/app-settings.ts @@ -16,6 +16,6 @@ export class AppSettings { public static PROJECT_CONSOLE_LINK_V2 = 'admin-v2-base'; public static CORPORATE_CONSOLE_LINK_V2 = 'corporate-v2-base'; public static REQUEST_ACCESS_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLSdnTk_9xjk7YoiX_FcPEqsFytsLMcT8OzYUbK6TsYopR1XhdA/viewform'; - public static CONTRIBUTORS_LEARN_MORE = 'https://docs.linuxfoundation.org/lfx/easycla/contributors'; + public static CONTRIBUTORS_LEARN_MORE = 'https://docs.linuxfoundation.org/lfx/easycla/v2-current/contributors'; public static TICKET_URL = 'https://jira.linuxfoundation.org/servicedesk/customer/portal/4/create/143'; } From 5d9422d7450206d7724baa218c242c21ef14b94d Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 8 Feb 2022 09:52:02 -0800 Subject: [PATCH 0581/1276] Resolved Link Error with Documentation (#3414) --- cla-backend-go/utils/email.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cla-backend-go/utils/email.go b/cla-backend-go/utils/email.go index cbc3b8e90..dec04107d 100644 --- a/cla-backend-go/utils/email.go +++ b/cla-backend-go/utils/email.go @@ -109,17 +109,16 @@ func GetCorporateURL(isV2Project bool) string { // GetEmailHelpContent returns the standard email help paragraph details. func GetEmailHelpContent(showV2HelpLink bool) string { - if showV2HelpLink { - return `

        If you need help or have questions about EasyCLA, you can + // We only support v2 help links as of late 2021/early2022 + helpLinkInfo := `

        If you need help or have questions about EasyCLA, you can read the documentation or reach out to us for support.

        ` + if showV2HelpLink { + return helpLinkInfo } - return `

        If you need help or have questions about EasyCLA, you can -read the documentation or -reach out to us for -support.

        ` + return helpLinkInfo } // GetEmailSignOffContent returns the standard email sign-off details From 2c1c26f825ffb2b59e2c90b0fb440e550be3d94d Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 9 Feb 2022 15:16:38 -0800 Subject: [PATCH 0582/1276] Resolved 3378 - Duplicate Missing ID Issue (#3418) --- cla-backend/cla/models/github_models.py | 30 ++++++++++++++++++++----- cla-backend/cla/utils.py | 4 +++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index fe21e5aea..7f9023df1 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -376,13 +376,17 @@ def update_change_request(self, installation_id, github_repository_id, change_re _ = 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 PR from GitHub.') + 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 <= tries: cla.log.debug(f'{fn} - attempt {i + 1} - waiting to retry...') time.sleep(2) continue else: - cla.log.debug(f'{fn} - attempt {i + 1} - exhausted retries - unable to load PR from GitHub.') + 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 @@ -403,7 +407,21 @@ def update_change_request(self, installation_id, github_repository_id, change_re f'id: {github_repository_id} in our DB - repository reference is None - ' 'Is this org/repo configured in the Project Console?' ' Unable to update status.') + # Optionally, we could add a comment or add a status to the PR informing the users that the EasyCLA + # app/bot is enabled in GitHub (which is why we received the event in the first place), but the + # repository is not setup/configured in EasyCLA from the administration console return + + # If the repository is not enabled in our database, we don't process it. + if not repository.get_enabled(): + cla.log.warning(f'{fn} - repository {repository.get_repository_url()} associated with ' + f'PR: {pull_request.number} is NOT enabled' + ' - ignoring PR request') + # Optionally, we could add a comment or add a status to the PR informing the users that the EasyCLA + # app/bot is enabled in GitHub (which is why we received the event in the first place), but the + # repository is NOT enabled in the administration console + return + except DoesNotExist: cla.log.warning(f'{fn} - PR: {pull_request.number}, could not find repository with the ' f'repository ID: {github_repository_id}') @@ -411,11 +429,11 @@ def update_change_request(self, installation_id, github_repository_id, change_re f'repository {github_repository_id} - returning') return - # Get Github Organization name that the repository is configured to. + # Get GitHub Organization name that the repository is configured to. organization_name = repository.get_repository_organization_name() cla.log.debug(f'{fn} - PR: {pull_request.number}, determined github organization is: {organization_name}') - # Check that the Github Organization exists. + # Check that the GitHub Organization exists. github_org = GitHubOrg() try: github_org.load(organization_name) @@ -982,7 +1000,7 @@ def has_check_previously_failed(pull_request: PullRequest): return True, comment if 'they must confirm their affiliation' in comment.body: return True, comment - if 'CLA Missing ID' in comment.body and 'is missing the User' in comment.body: + if 'is missing the User' in comment.body: return True, comment return False, None @@ -1109,7 +1127,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep f'signing url: {sign_url}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) else: - # error condition - should have a least one committer and they would be in one of the above + # error condition - should have at least one committer, and they would be in one of the above # lists: missing or signed state = 'failure' # For status, we change the context from author_name to 'communitybridge/cla' or the diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 9fa3a72a1..7a6339dec 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -915,7 +915,9 @@ def assemble_cla_comment(repository_type, installation_id, github_repository_id, :type project_version: string """ num_missing = len(missing) - missing_ids = list(filter(lambda x: (x[1] is None or (x[1] and x[1][0] is None)), missing)) + # fails to catch empty list + # missing_ids = list(filter(lambda x: (x[1] is None or (x[1] and x[1][0] is None)), missing)) + missing_ids = list(filter(lambda x: (x[1] is None or len(x[1]) == 0), missing)) no_user_id = len(missing_ids) > 0 # check if an unsigned committer has been approved by a CLA Manager, but not associated with a company # Logic not supported as we removed the DB query in the caller From 808611e7999091e4566b53166b23563d3b53629c Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 11 Feb 2022 13:11:35 -0800 Subject: [PATCH 0583/1276] Added Logic to Add Multiple GitHub Labels For Failed PR (#3424) Co-authored-by: David Deal Missing --- cla-backend/cla/utils.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 7a6339dec..6c93671c1 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -852,21 +852,33 @@ def get_comment_badge(repository_type, all_signed, sign_url, project_version, mi badge_hyperlink = os.path.join(badge_hyperlink, "#/") badge_hyperlink = append_project_version_to_url(address=badge_hyperlink, project_version=project_version) alt = "CLA Signed" + return (f'' + f'{alt}' + '
        ') else: + badge_hyperlink = sign_url + text = '' if missing_user_id: badge_url = f'{CLA_LOGO_URL}/cla-missing-id.svg' alt = 'CLA Missing ID' - elif is_approved_by_manager: + text = (f'{text} ' + f'{alt}' + '') + + if is_approved_by_manager: badge_url = f'{CLA_LOGO_URL}/cla-confirmation-needed.svg' alt = 'CLA Confirmation Needed' + text = (f'{text} ' + f'{alt}' + '') else: badge_url = f'{CLA_LOGO_URL}/cla-not-signed.svg' alt = "CLA Not Signed" - badge_hyperlink = sign_url - # return '[![CLA Check](' + badge_url + ')](' + badge_hyperlink + ')' - return (f'' - f'{alt}' - '
        ') + text = (f'{text} ' + f'{alt}' + '') + + return f'{text}
        ' def assemble_cla_status(author_name, signed=False): From 93ddbb711bcb2f24205512d461dd6d2c43effdd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:18:18 -0800 Subject: [PATCH 0584/1276] Bump vm2 from 3.9.3 to 3.9.7 in /cla-landing-page (#3437) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-landing-page/yarn.lock | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index db89ecad8..178e7f6f7 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1761,11 +1761,21 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-walk@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^6.2.1: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + adm-zip@^0.4.9: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -12095,9 +12105,12 @@ vm-browserify@^1.0.1: integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== vm2@^3.9.3: - version "3.9.3" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" - integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + version "3.9.7" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.7.tgz#bb87aa677c97c61e23a6cb6547e44e990517a6f6" + integrity sha512-g/GZ7V0Mlmch3eDVOATvAXr1GsJNg6kQ5PjvYy3HbJMCRn5slNbo/u73Uy7r5yUej1cRa3ZjtoVwcWSQuQ/fow== + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" void-elements@^2.0.0: version "2.0.1" From e2ad14edc13166a6527962d17bd3043761f82663 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:18:40 -0800 Subject: [PATCH 0585/1276] Bump vm2 from 3.9.3 to 3.9.7 in /cla-frontend-corporate-console (#3436) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index aad9f80bf..f9dfdb82f 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -493,6 +493,16 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-walk@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + adm-zip@^0.4.13, adm-zip@^0.4.16: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -6398,9 +6408,12 @@ verror@1.10.0: extsprintf "^1.2.0" vm2@^3.9.3: - version "3.9.3" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" - integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + version "3.9.7" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.7.tgz#bb87aa677c97c61e23a6cb6547e44e990517a6f6" + integrity sha512-g/GZ7V0Mlmch3eDVOATvAXr1GsJNg6kQ5PjvYy3HbJMCRn5slNbo/u73Uy7r5yUej1cRa3ZjtoVwcWSQuQ/fow== + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" warning-symbol@^0.1.0: version "0.1.0" From 3c0aca721ab88146be4d35b89dc8a2a8ec7cbeb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:18:59 -0800 Subject: [PATCH 0586/1276] Bump vm2 from 3.9.3 to 3.9.7 in /cla-frontend-contributor-console (#3435) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index b7a008873..9272ea9b9 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -493,6 +493,16 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-walk@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + adm-zip@^0.4.13, adm-zip@^0.4.16: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -6432,9 +6442,12 @@ verror@1.10.0: extsprintf "^1.2.0" vm2@^3.9.3: - version "3.9.3" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.3.tgz#29917f6cc081cc43a3f580c26c5b553fd3c91f40" - integrity sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q== + version "3.9.7" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.7.tgz#bb87aa677c97c61e23a6cb6547e44e990517a6f6" + integrity sha512-g/GZ7V0Mlmch3eDVOATvAXr1GsJNg6kQ5PjvYy3HbJMCRn5slNbo/u73Uy7r5yUej1cRa3ZjtoVwcWSQuQ/fow== + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" warning-symbol@^0.1.0: version "0.1.0" From 383a35c26050bb074f85f9d1778dce42f6a6ced6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:19:21 -0800 Subject: [PATCH 0587/1276] Bump vm2 from 3.9.5 to 3.9.7 in /cla-frontend-project-console (#3438) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index 83d4e005f..415cca5a9 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -498,6 +498,16 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-walk@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + adm-zip@^0.4.13, adm-zip@^0.4.16: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -6400,9 +6410,12 @@ verror@1.10.0: extsprintf "^1.2.0" vm2@^3.9.3: - version "3.9.5" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.5.tgz#5288044860b4bbace443101fcd3bddb2a0aa2496" - integrity sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng== + version "3.9.7" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.7.tgz#bb87aa677c97c61e23a6cb6547e44e990517a6f6" + integrity sha512-g/GZ7V0Mlmch3eDVOATvAXr1GsJNg6kQ5PjvYy3HbJMCRn5slNbo/u73Uy7r5yUej1cRa3ZjtoVwcWSQuQ/fow== + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" warning-symbol@^0.1.0: version "0.1.0" From 08bfd7cbfddf90d400d2d5e3308f7f9453b4f7d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:20:44 -0800 Subject: [PATCH 0588/1276] Bump follow-redirects from 1.14.4 to 1.14.8 in /cla-backend (#3426) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 89335c172..953b368e7 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -2404,9 +2404,9 @@ folder-hash@^3.3.0: minimatch "~3.0.4" follow-redirects@^1.14.0: - version "1.14.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" - integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== for-in@^0.1.3: version "0.1.8" From b602879c3d970816d6653b27685d2e4e31d76b66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 10:21:28 -0800 Subject: [PATCH 0589/1276] Bump follow-redirects from 1.13.0 to 1.14.8 in /cla-landing-page (#3434) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-landing-page/yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 178e7f6f7..ec09ed3d4 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -5436,15 +5436,10 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@^1.0.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" - integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== - -follow-redirects@^1.14.0: - version "1.14.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" - integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== +follow-redirects@^1.0.0, follow-redirects@^1.14.0: + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== for-in@^0.1.3: version "0.1.8" From 17797a2d1586a16cee5ff6bde0da35189603efb4 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Tue, 22 Feb 2022 00:13:11 +0200 Subject: [PATCH 0590/1276] [Snyk] Security upgrade url-parse from 1.5.3 to 1.5.9 (#3454) --- cla-frontend-project-console/src/package.json | 2 +- cla-frontend-project-console/src/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cla-frontend-project-console/src/package.json b/cla-frontend-project-console/src/package.json index 10f96381a..eaffeaa65 100644 --- a/cla-frontend-project-console/src/package.json +++ b/cla-frontend-project-console/src/package.json @@ -54,7 +54,7 @@ "rxjs": "5.5.2", "sw-toolbox": "3.6.0", "timsort": "^0.3.0", - "url-parse": "^1.5.2", + "url-parse": "^1.5.9", "zone.js": "0.8.18" }, "devDependencies": { diff --git a/cla-frontend-project-console/src/yarn.lock b/cla-frontend-project-console/src/yarn.lock index e452d1fc7..3b8b132b2 100644 --- a/cla-frontend-project-console/src/yarn.lock +++ b/cla-frontend-project-console/src/yarn.lock @@ -4196,10 +4196,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== +url-parse@^1.5.9: + version "1.5.9" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.9.tgz#05ff26484a0b5e4040ac64dcee4177223d74675e" + integrity sha512-HpOvhKBvre8wYez+QhHcYiVvVmeF6DVnuSOOPhe3cTum3BnqHhvKaZm8FU5yTiOu/Jut2ZpB2rA/SbBA1JIGlQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From ff1b2e5326d9e813eb9b2c99fe6186d0624134d3 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 22 Feb 2022 10:44:36 -0800 Subject: [PATCH 0591/1276] Landing Page - Resolved Lib Vulnerabilities (#3456) --- cla-backend/requirements.txt | 1 + cla-landing-page/package.json | 16 +- cla-landing-page/yarn.lock | 327 +++++++++++++++++++++------------- 3 files changed, 220 insertions(+), 124 deletions(-) diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index f0de75363..940909c2e 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -59,3 +59,4 @@ wcwidth==0.1.7 Werkzeug==0.15.5 wrapt==1.11.2 zipp==0.6.0 +markupsafe==2.0.1 diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index ebe96ab9f..5ee6c40ba 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -64,7 +64,7 @@ "serverless-plugin-tracing": "^2.0.0", "serverless-pseudo-parameters": "^2.5.0", "tslib": "^1.10.0", - "url-parse": "^1.5.2", + "url-parse": "^1.5.6", "zone.js": "~0.10.2" }, "devDependencies": { @@ -89,26 +89,38 @@ "typescript": "~3.8.3" }, "resolutions": { + "ansi-regex": "^5.0.1", + "aws-sdk": "^2.814.0", "axios": "^0.21.4", "browserslist": "^4.16.5", "dns-packet": "^5.2.2", "elliptic": "^6.5.4", + "engine.io": "^4.0.0", "glob-parent": "^5.1.2", "hosted-git-info": "^3.0.8", "ini": "^1.3.6", + "is-svg": "^4.3.0", + "json-schema": "^0.4.0", "jszip": "^3.7.0", + "karma": "^6.3.14", "lodash": "^4.17.21", + "node-fetch": "^2.6.7", + "node-forge": "^1.0.0", "normalize-url": "^6.0.1", + "nth-check": "^2.0.1", "pac-resolver": "^5.0.0", "path-parse": "^1.0.7", "postcss": "^7.0.36", "set-getter": "^0.1.1", + "set-value": "^4.0.1", + "simple-get": "^2.8.2", "socket.io": "^2.4.0", "socket.io-parser": "^3.4.1", "ssri": "^8.0.1", "tar": "^6.1.9", "ua-parser-js": "^0.7.24", - "url-parse": "^1.5.2", + "url-parse": "^1.5.6", + "webpack-subresource-integrity": "^1.5.1", "ws": "^7.4.6", "xmlhttprequest-ssl": "^1.6.1", "y18n": "^5.0.5", diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index ec09ed3d4..671226d1d 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -2076,25 +2076,10 @@ ansi-red@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^2.0.0, ansi-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^2.0.0, ansi-regex@^2.1.1, ansi-regex@^3.0.0, ansi-regex@^4.1.0, ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-reset@^0.1.1: version "0.1.1" @@ -2457,15 +2442,15 @@ autoprefixer@9.7.4: postcss "^7.0.26" postcss-value-parser "^4.0.2" -aws-sdk@^2.224.1: - version "2.775.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.775.0.tgz#707c369e84b79cf3b136819c01f849b93fb44eab" - integrity sha512-rlej1sgHmfhl+PJqpQ2qOOsbHEEnLBIKBmanMTUNGiEAfuS0MpFjXECXTpJIOrbUzl3OZuAYrGuBkg2qrBwRbQ== +aws-sdk@^2.224.1, aws-sdk@^2.814.0, aws-sdk@^2.979.0: + version "2.1078.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1078.0.tgz#5871750b38045f05e141e5217521f8ff8bf73a26" + integrity sha512-eJuiiCE4tomYzsxqfsjERmQ1WQkNAe5RUhOXUwJbGTEfwmbiQqq/HgVYrwcMswOHoURbtKpB5SSrTLNOBuyurA== dependencies: buffer "4.9.2" events "1.1.1" ieee754 "1.1.13" - jmespath "0.15.0" + jmespath "0.16.0" querystring "0.2.0" sax "1.2.1" url "0.10.3" @@ -2487,21 +2472,6 @@ aws-sdk@^2.885.0: uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.979.0: - version "2.980.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" - integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2708,7 +2678,7 @@ bonjour@^3.5.0: multicast-dns "^6.0.1" multicast-dns-service-types "^1.1.0" -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -3404,6 +3374,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-deep@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-1.0.0.tgz#b2f354444b5d4a0ce58faca337ef34da2b14a6c7" @@ -3540,7 +3519,7 @@ colors@1.3.x: resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== -colors@^1.2.1, colors@^1.4.0: +colors@1.4.0, colors@^1.2.1, colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -3786,6 +3765,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -4130,7 +4117,7 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@~4.3.1: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -4696,16 +4683,24 @@ engine.io-parser@~2.2.0: blob "0.0.5" has-binary2 "~1.0.2" -engine.io@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b" - integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA== +engine.io-parser@~4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.3.tgz#83d3a17acfd4226f19e721bb22a1ee8f7662d2f6" + integrity sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA== + dependencies: + base64-arraybuffer "0.1.4" + +engine.io@^4.0.0, engine.io@~3.5.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.2.tgz#f96ceb56d4b39cc7ca5bd29a20e9c99c1ad1a765" + integrity sha512-t5z6zjXuVLhXDMiFJPYsPOWEER8B0tIsD3ETgw19S1yg9zryvUfY3Vhtk3Gf4sihw/bQGIqQ//gjvVlu+Ca0bQ== dependencies: accepts "~1.3.4" base64id "2.0.0" cookie "~0.4.1" - debug "~4.1.0" - engine.io-parser "~2.2.0" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~4.0.0" ws "~7.4.2" enhanced-resolve@4.1.1: @@ -5211,6 +5206,13 @@ fast-text-encoding@^1.0.3: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== +fast-xml-parser@^3.19.0: + version "3.21.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736" + integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg== + dependencies: + strnum "^1.0.4" + fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" @@ -5680,7 +5682,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -5753,7 +5755,7 @@ glob-parent@^3.1.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, dependencies: is-glob "^4.0.1" -glob@7.1.6, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@7.1.6, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -5765,6 +5767,18 @@ glob@7.1.6, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6, glob@^7.1.7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -5851,12 +5865,12 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.9" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== @@ -6026,11 +6040,6 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - html-entities@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" @@ -6689,13 +6698,18 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" +is-primitive@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== + is-promise@^2.1, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -6718,12 +6732,12 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== +is-svg@^3.0.0, is-svg@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.2.tgz#a119e9932e1af53f6be1969d1790d6cc5fd947d3" + integrity sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw== dependencies: - html-comment-regex "^1.1.0" + fast-xml-parser "^3.19.0" is-symbol@^1.0.2: version "1.0.3" @@ -6774,10 +6788,10 @@ isarray@2.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= -isbinaryfile@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" - integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg== +isbinaryfile@^4.0.6, isbinaryfile@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf" + integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w== isexe@^2.0.0: version "2.0.0" @@ -6950,6 +6964,11 @@ jmespath@0.15.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + js-cookie@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -7047,10 +7066,10 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.2.3, json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stringify-safe@~5.0.1: version "5.0.1" @@ -7182,6 +7201,36 @@ karma-source-map-support@1.4.0: dependencies: source-map-support "^0.5.5" +karma@^6.3.14: + version "6.3.16" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.16.tgz#76d1a705fd1cf864ee5ed85270b572641e0958ef" + integrity sha512-nEU50jLvDe5yvXqkEJRf8IuvddUkOY2x5Xc4WXHz6dxINgGDrgD2uqQWeVrJs4hbfNaotn+HQ1LZJ4yOXrL7xQ== + dependencies: + body-parser "^1.19.0" + braces "^3.0.2" + chokidar "^3.5.1" + colors "1.4.0" + connect "^3.7.0" + di "^0.0.1" + dom-serialize "^2.2.1" + glob "^7.1.7" + graceful-fs "^4.2.6" + http-proxy "^1.18.1" + isbinaryfile "^4.0.8" + lodash "^4.17.21" + log4js "^6.4.1" + mime "^2.5.2" + minimatch "^3.0.4" + mkdirp "^0.5.5" + qjobs "^1.2.0" + range-parser "^1.2.1" + rimraf "^3.0.2" + socket.io "^4.2.0" + source-map "^0.6.1" + tmp "^0.2.1" + ua-parser-js "^0.7.30" + yargs "^16.1.1" + karma@~5.0.0: version "5.0.9" resolved "https://registry.yarnpkg.com/karma/-/karma-5.0.9.tgz#11a119b0c763a806fdc471b40c594a2240b5ca13" @@ -7460,10 +7509,10 @@ log-utils@^0.2.1: time-stamp "^1.0.1" warning-symbol "^0.1.0" -log4js@^6.2.1: - version "6.4.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.0.tgz#3f63ccfc8033c83cd617a4d2d50e48be5944eae9" - integrity sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ== +log4js@^6.2.1, log4js@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.1.tgz#9d3a8bf2c31c1e213fe3fc398a6053f7a2bc53e8" + integrity sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg== dependencies: date-format "^4.0.3" debug "^4.3.3" @@ -7791,11 +7840,16 @@ mime@1.6.0, mime@^1.2.11, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.4, mime@^2.4.5, mime@^2.4.6: +mime@^2.4.4, mime@^2.4.6: version "2.4.6" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== +mime@^2.4.5, mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -8122,15 +8176,17 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@^2.6.0, node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^0.10.0, node-forge@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" + integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== node-libs-browser@^2.2.1: version "2.2.1" @@ -8307,12 +8363,12 @@ npmlog@^4.0.1: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== +nth-check@^1.0.2, nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== dependencies: - boolbase "~1.0.0" + boolbase "^1.0.0" num2fraction@^1.2.2: version "1.2.2" @@ -8329,7 +8385,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -10553,22 +10609,13 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -set-value@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90" - integrity sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA== +set-value@^2.0.0, set-value@^2.0.1, set-value@^3.0.0, set-value@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" + is-primitive "^3.0.1" setimmediate@^1.0.4: version "1.0.5" @@ -10638,10 +10685,10 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== +simple-get@^2.7.0, simple-get@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.2.tgz#5708fb0919d440657326cd5fe7d2599d07705019" + integrity sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw== dependencies: decompress-response "^3.3.0" once "^1.3.1" @@ -10760,7 +10807,7 @@ socket.io-parser@^3.4.1, socket.io-parser@~3.3.0, socket.io-parser@~3.4.0: debug "~4.1.0" isarray "2.0.1" -socket.io@^2.3.0, socket.io@^2.4.0: +socket.io@^2.3.0, socket.io@^2.4.0, socket.io@^4.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2" integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w== @@ -10948,7 +10995,7 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== -split-string@^3.0.1, split-string@^3.0.2: +split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== @@ -11216,6 +11263,11 @@ strip-outer@^1.0.1: dependencies: escape-string-regexp "^1.0.2" +strnum@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + strtok3@^6.2.4: version "6.2.4" resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.2.4.tgz#302aea64c0fa25d12a0385069ba66253fdc38a81" @@ -11550,7 +11602,7 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@0.2.1: +tmp@0.2.1, tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== @@ -11649,6 +11701,11 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" @@ -11807,7 +11864,7 @@ typescript@~3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@0.7.21, ua-parser-js@^0.7.24: +ua-parser-js@0.7.21, ua-parser-js@^0.7.24, ua-parser-js@^0.7.30: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== @@ -11963,10 +12020,10 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3, url-parse@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== +url-parse@^1.4.3, url-parse@^1.5.6: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" @@ -12075,7 +12132,7 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -12174,6 +12231,11 @@ webdriver-manager@^12.0.6: semver "^5.3.0" xml2js "^0.4.17" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webpack-dev-middleware@3.7.2, webpack-dev-middleware@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" @@ -12247,10 +12309,10 @@ webpack-sources@1.4.3, webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-s source-list-map "^2.0.0" source-map "~0.6.1" -webpack-subresource-integrity@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.4.0.tgz#44963a64c9a214ad729158e7f46d52c2525cc88a" - integrity sha512-GB1kB/LwAWC3CxwcedGhMkxGpNZxSheCe1q+KJP1bakuieAdX/rGHEcf5zsEzhKXpqsGqokgsDoD9dIkr61VDQ== +webpack-subresource-integrity@1.4.0, webpack-subresource-integrity@^1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.5.2.tgz#e40b6578d3072e2d24104975249c52c66e9a743e" + integrity sha512-GBWYBoyalbo5YClwWop9qe6Zclp8CIXYGIz12OPclJhIrSplDxs1Ls1JDMH8xBPPrg1T6ISaTW9Y6zOrwEiAzw== dependencies: webpack-sources "^1.3.0" @@ -12304,6 +12366,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + when@~3.6.x: version "3.6.4" resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e" @@ -12528,7 +12598,7 @@ yamljs@^0.3.0: argparse "^1.0.7" glob "^7.0.5" -yargs-parser@^11.1.1, yargs-parser@^13.1.2, yargs-parser@^18.1.0, yargs-parser@^18.1.2: +yargs-parser@^11.1.1, yargs-parser@^13.1.2, yargs-parser@^18.1.0, yargs-parser@^18.1.2, yargs-parser@^20.2.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -12604,6 +12674,19 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From a397ded25c3f60482bcaf0e0fb16556ca78d4180 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 22 Feb 2022 11:49:17 -0800 Subject: [PATCH 0592/1276] Updated Gerrit Swagger Spec (#3457) - Updated add/get gerrit API to allow LDAP values/ranges to support single digit ID values, added regex to support positive integer values, updated description Signed-off-by: David Deal --- cla-backend-go/swagger/common/add-gerrit-input.yaml | 10 ++++++---- cla-backend-go/swagger/common/gerrit.yaml | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cla-backend-go/swagger/common/add-gerrit-input.yaml b/cla-backend-go/swagger/common/add-gerrit-input.yaml index 073d6cce7..9ad977a16 100644 --- a/cla-backend-go/swagger/common/add-gerrit-input.yaml +++ b/cla-backend-go/swagger/common/add-gerrit-input.yaml @@ -28,16 +28,18 @@ properties: pattern: ^(?:http(s)?:\/\/).+$ groupIdCcla: type: string - description: the LDAP group ID for CCLA + description: the LDAP group ID for CCLA encoded as a string value example: '1902' - minLength: 3 + minLength: 1 maxLength: 12 + pattern: ^[1-9]\d{0,11}$ groupIdIcla: type: string - description: the LDAP group ID for ICLA + description: the LDAP group ID for ICLA encoded as a string value example: '1903' - minLength: 3 + minLength: 1 maxLength: 12 + pattern: ^[1-9]\d{0,11}$ version: type: string description: the version associated with the gerrit record diff --git a/cla-backend-go/swagger/common/gerrit.yaml b/cla-backend-go/swagger/common/gerrit.yaml index e7119c2b0..589cc40c2 100644 --- a/cla-backend-go/swagger/common/gerrit.yaml +++ b/cla-backend-go/swagger/common/gerrit.yaml @@ -35,16 +35,18 @@ properties: format: uri groupIdCcla: type: string - description: the LDAP group ID for CCLA + description: the LDAP group ID for CCLA encoded as a string value example: '1902' - minLength: 3 + minLength: 1 maxLength: 12 + pattern: ^[1-9]\d{0,11}$ groupIdIcla: type: string - description: the LDAP group ID for ICLA + description: the LDAP group ID for ICLA encoded as a string value example: '1903' - minLength: 3 + minLength: 1 maxLength: 12 + pattern: ^[1-9]\d{0,11}$ groupNameCcla: type: string description: the LDAP group name for CCLA From a687fe530cc9372756a683b08b43beb7eb7b8dcd Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 23 Feb 2022 11:37:15 -0800 Subject: [PATCH 0593/1276] Added Company Lookup/Load Error Handling (#3459) --- cla-backend/cla/utils.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 6c93671c1..7ec6f7cc5 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -24,7 +24,8 @@ from cla.models import DoesNotExist from cla.models.dynamo_models import User, Signature, Repository, \ Company, Project, Document, \ - GitHubOrg, Gerrit, UserPermissions, Event, CompanyInvite, ProjectCLAGroup, CCLAWhitelistRequest, CLAManagerRequest, GitlabOrg + GitHubOrg, Gerrit, UserPermissions, Event, CompanyInvite, ProjectCLAGroup, CCLAWhitelistRequest, CLAManagerRequest, \ + GitlabOrg from cla.models.event_types import EventType API_BASE_URL = os.environ.get('CLA_API_BASE', '') @@ -669,9 +670,14 @@ def user_signed_project_signature(user: User, project: Project) -> bool: cla.log.debug(f'{fn} - CCLA signature check - located employee acknowledgement - ' f'signature id: {employee_signature.get_signature_id()}') - cla.log.debug(f'{fn} - CCLA signature check - loading company record by id: {company_id}...') company = get_company_instance() - company.load(company_id) + try: + cla.log.debug(f'{fn} - CCLA signature check - loading company record by id: {company_id}...') + company.load(company_id) + except DoesNotExist as err: + cla.log.debug(f'{fn} - CCLA signature check failed - user is NOT associated with a valid company - ' + f'company with id does not exist: {company_id}.') + return False # Get CCLA signature of company to access whitelist cla.log.debug(f'{fn} - CCLA signature check - loading signed CCLA for project|company, ' @@ -1216,7 +1222,7 @@ def get_active_signature_return_url(user_id, metadata=None): if metadata is None: cla.log.warning('Could not find active signature for user {}, return URL request failed'.format(user_id)) return None - + # Factor in Gitlab flow process if "merge_request_id" in metadata.keys(): return metadata['return_url'] @@ -1254,6 +1260,7 @@ def get_installation_id_from_github_repository(github_repository_id): # Get this organization's installation ID return organization.get_organization_installation_id() + def get_organization_id_from_gitlab_repository(gitlab_repository_id): # Get repository ID that references the gitlab ID. try: @@ -1267,8 +1274,8 @@ def get_organization_id_from_gitlab_repository(gitlab_repository_id): except DoesNotExist: cla.log.debug(f"unable to get gitlab org by name: {repository.get_repository_organization_name()}") return None - - #return GitLab organization ID + + # return GitLab organization ID return gitLabOrg.get_organization_id() @@ -1313,6 +1320,7 @@ def get_individual_signature_callback_url(user_id, metadata=None): return os.path.join(API_BASE_URL, 'v2/signed/individual', str(installation_id), str(metadata['repository_id']), str(metadata['pull_request_id'])) + def get_individual_signature_callback_url_gitlab(user_id, metadata=None): """ Helper function to get a user's active signature callback URL. @@ -1341,7 +1349,8 @@ def get_individual_signature_callback_url_gitlab(user_id, metadata=None): gitlab_repository_id) return None - return os.path.join(API_BASE_URL, 'v2/signed/gitlab/individual',str(user_id), str(organization_id), str(metadata['repository_id']), + return os.path.join(API_BASE_URL, 'v2/signed/gitlab/individual', str(user_id), str(organization_id), + str(metadata['repository_id']), str(metadata['merge_request_id'])) @@ -1404,9 +1413,10 @@ def lookup_user_gitlab_username(user_gitlab_id: int) -> Optional[str]: return gitlab_user['id'] else: cla.log.warning('Malformed HTTP response from GitLab - expecting "id" attribute ' - f'- response: {gitlab_user}') + f'- response: {gitlab_user}') return None + def lookup_user_gitlab_id(user_gitlab_username: str) -> Optional[str]: """ Given a user gitlab username, looks up the user's gitlab id. @@ -1426,7 +1436,7 @@ def lookup_user_gitlab_id(user_gitlab_username: str) -> Optional[str]: return gitlab_user['username'] else: cla.log.warning('Malformed HTTP response from GitLab - expecting "username" attribute ' - f'- response: {gitlab_user}') + f'- response: {gitlab_user}') return None @@ -1511,6 +1521,7 @@ def lookup_github_organizations(github_username: str): return {'error': 'Could not get user github org: {}'.format(err)} return [github_org['login'] for github_org in r.json()] + def lookup_gitlab_org_members(organization_id): # Use the v2 Endpoint thats a wrapper for Gitlab Group member query try: From f55f9571c76917e148b3fecb601df78295ce2add Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Mar 2022 11:57:13 -0800 Subject: [PATCH 0594/1276] [3464] Resolves Missing Commit Info in GitHub Status (#3465) --- cla-backend/cla/controllers/github.py | 2 +- cla-backend/cla/models/github_models.py | 304 +++++++++--------- cla-backend/cla/routes.py | 3 +- .../cla/tests/unit/test_github_models.py | 24 +- cla-backend/cla/user.py | 59 +++- cla-backend/cla/utils.py | 29 +- 6 files changed, 236 insertions(+), 185 deletions(-) diff --git a/cla-backend/cla/controllers/github.py b/cla-backend/cla/controllers/github.py index 992259bed..928ca1118 100644 --- a/cla-backend/cla/controllers/github.py +++ b/cla-backend/cla/controllers/github.py @@ -372,7 +372,7 @@ def handle_pull_request_comment_event(action: str, body: dict): result = service.process_easycla_command_comment(body) return result except ValueError as ex: - cla.log.warning(f"process_easycla_command_comment failed with : {str(ex)}") + cla.log.debug(f'{func_name} - ignoring github pull_request comment: {str(ex)}') return None else: cla.log.debug(f'{func_name} - ignoring github pull_request comment activity for action: {action}') diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 7f9023df1..e2ec5425b 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -21,6 +21,7 @@ from cla.models import repository_service_interface, DoesNotExist from cla.models.dynamo_models import Repository, GitHubOrg from cla.utils import get_project_instance, append_project_version_to_url +from cla.user import UserCommitSummary # some emails we want to exclude when we register the users EXCLUDE_GITHUB_EMAILS = ["noreply.github.com"] @@ -393,8 +394,7 @@ def update_change_request(self, installation_id, github_repository_id, change_re break cla.log.debug(f'{fn} - retrieved pull request: {pull_request}') - # Get all unique users/authors involved in this PR - returns a list of - # (commit_sha_string, (author_id, author_username, author_email) tuples + # Get all unique users/authors involved in this PR - returns a List[UserCommitSummary] objects commit_authors = get_pull_request_commit_authors(pull_request) try: @@ -465,19 +465,11 @@ def update_change_request(self, installation_id, github_repository_id, change_re cla.log.debug(f'{fn} - PR: {pull_request.number}, scanning users - ' 'determining who has signed a CLA an who has not.') - for commit_sha, author_info in commit_authors: - # Extract the author info tuple details - if author_info: - author_id = author_info[0] - author_username = author_info[1] - author_email = author_info[2] - cla.log.debug(f'{fn} - PR: {pull_request.number}, ' - f'processing sha: {commit_sha} ' - f'from author id: {author_id}, username: {author_username}, email: {author_email}') - else: - cla.log.debug( - f'{fn} - PR: {pull_request.number}, processing sha: {commit_sha} with invalid author details') - handle_commit_from_user(project, commit_sha, author_info, signed, missing) + for user_commit_summary in commit_authors: + cla.log.debug(f'{fn} - PR: {pull_request.number} for user: {user_commit_summary}') + handle_commit_from_user(project, user_commit_summary, signed, missing) + + # At this point, the signed and missing lists are now filled and updated with the commit user info cla.log.debug(f'{fn} - PR: {pull_request.number}, ' f'updating github pull request for repo: {github_repository_id}, ' @@ -754,70 +746,63 @@ def create_repository(data): return None -def handle_commit_from_user(project, commit_sha, author_info, signed, missing): # pylint: disable=too-many-arguments +def handle_commit_from_user(project, user_commit_summary: UserCommitSummary, signed: List[UserCommitSummary], + missing: List[UserCommitSummary]): # pylint: disable=too-many-arguments """ Helper method to triage commits between signed and not-signed user signatures. - :param project: The project model for this github PR organization. - :type project: Project - :param commit_sha: Commit has as a string - :type commit_sha: string - :param author_info: the commit author details, including id, name, email (if available) - :type author_info: tuple of (author_id, author_username, author_email) - :param signed: Reference to a list of signed authors so far. Should be modified - in-place to add a signer if found. - :type signed: list of strings - :param missing: Reference to a list of authors who have not signed yet. - Should be modified in-place to add a missing signer if found. - :type missing: list of strings + :param: project: The project model for this GitHub PR organization. + :type: project: Project + :param: user_commit_summary: a user commit summary object + :type: UserCommitSummary + :param signed: A list of authors who have signed. + Should be modified in-place to add the signer information. + :type: List[UserCommitSummary] + :param missing: A list of authors who have not signed yet. + Should be modified in-place to add the missing signer information. + :type: List[UserCommitSummary] """ + fn = 'cla.models.github_models.handle_commit_from_user' # handle edge case of non existant users - if author_info is None: - missing.append((commit_sha, [])) + if not user_commit_summary.is_valid_user(): + missing.append(user_commit_summary) return - # Extract the author_info tuple details - author_id = author_info[0] - author_username = author_info[1] - author_email = author_info[2] - cla.log.debug(f'Looking up GitHub user (author_id: {author_id}, ' - f'author_username: {author_username}, ' - f'auth_email: {author_email})') # attempt to lookup the user in our database by GH id - # may return multiple users that match this author_id - users = cla.utils.get_user_instance().get_user_by_github_id(author_id) + users = cla.utils.get_user_instance().get_user_by_github_id(user_commit_summary.author_id) if users is None: # GitHub user not in system yet, signature does not exist for this user. - cla.log.debug(f'GitHub user (id: {author_id}, ' - f'user: {author_username}, ' - f'email: {author_email}) lookup by github id not found in our database, ' + cla.log.debug(f'{fn} - User commit summary: {user_commit_summary} ' + f'lookup by github numeric id not found in our database, ' 'attempting to looking up user by email...') # Try looking up user by email as a fallback - users = cla.utils.get_user_instance().get_user_by_email(author_email) + users = cla.utils.get_user_instance().get_user_by_email(user_commit_summary.author_email) # Got one or more records by searching the email if users is not None: - cla.log.debug(f'Found {len(users)} GitHub user(s) matching github email: {author_email}') + cla.log.debug(f'{fn} - Found {len(users)} GitHub user(s) matching ' + f'github email: {user_commit_summary.author_email}') + for user in users: - cla.log.debug(f'GitHub user found in our database: {user}') + cla.log.debug(f'{fn} - GitHub user found in our database: {user}') # For now, accept non-github users as legitimate users. # Does this user have a signed signature for this project? If so, add to the signed list and return, # no reason to continue looking if cla.utils.user_signed_project_signature(user, project): - signed.append((commit_sha, author_username)) + signed.append(user_commit_summary) return # Didn't find a signed signature for this project - add to our missing bucket list - # author_info consists of: [author_id, author_username, author_email] - missing.append((commit_sha, list(author_info))) + # author_info consists of: [author_id, author_login, author_username, author_email] + missing.append(user_commit_summary) else: # Not seen this user before - no record on file in our user's database - cla.log.debug(f'GitHub user (id: {author_id}, ' - f'user: {author_username}, ' - f'email: {author_email}) lookup by email in our database failed - not found') + cla.log.debug(f'{fn} - User commit summary: {user_commit_summary} ' + f'lookup by email in our database failed - not found') # This bit of logic below needs to be reconsidered - query logic takes a very long time for large # projects like CNCF which significantly delays updating the GH PR status. @@ -849,32 +834,35 @@ def handle_commit_from_user(project, commit_sha, author_info, signed, missing): # For now - we'll just return the author info as a list without the flag to indicate that they have been on # the approved list for any company/signature - # author_info consists of: [author_id, author_username, author_email] - missing.append((commit_sha, list(author_info))) + # author_info consists of: [author_id, author_login, author_username, author_email] + missing.append(user_commit_summary) else: - cla.log.debug(f'Found {len(users)} GitHub user(s) matching github id: {author_id} in our database') + cla.log.debug(f'{fn} - Found {len(users)} GitHub user(s) matching ' + f'github id: {user_commit_summary.author_id} in our database') if len(users) > 1: - cla.log.warning(f'more than 1 user found in our user database - user: {users} - ' + cla.log.warning(f'{fn} - more than 1 user found in our user database - user: {users} - ' f'will ONLY evaluate the first one') # Just review the first user that we were able to fetch from our DB user = users[0] - cla.log.debug(f'GitHub user found in our database: {user}') + cla.log.debug(f'{fn} - GitHub user found in our database: {user}') # Does this user have a signed signature for this project? If so, add to the signed list and return, # no reason to continue looking if cla.utils.user_signed_project_signature(user, project): - signed.append((commit_sha, author_username)) + signed.append(user_commit_summary) return - list_author_info = list(author_info) - # If the user does not have a company ID assigned, then they have not been associated with a company as # part of the Contributor console workflow if user.get_user_company_id() is None: - missing.append((commit_sha, list_author_info)) + # User is not affiliated with a company + missing.append(user_commit_summary) return + # Mark the user as having a company affiliation + user_commit_summary.affiliated = True + # Perform a specific search for the user's project + company + CCLA signatures = cla.utils.get_signature_instance().get_signatures_by_project( project_id=project.get_project_id(), @@ -887,32 +875,30 @@ def handle_commit_from_user(project, commit_sha, author_info, signed, missing): ) # Should only return one signature record - cla.log.debug(f'Found {len(signatures)} CCLA signatures for company: {user.get_user_company_id()}, ' + cla.log.debug(f'{fn} - Found {len(signatures)} CCLA signatures for company: {user.get_user_company_id()}, ' f'project: {project.get_project_id()} in our database.') # Should never happen - warn if we see this if len(signatures) > 1: - cla.log.warning(f'more than 1 CCLA signature record found in our database - signatures: {signatures}') + cla.log.warning( + f'{fn} - more than 1 CCLA signature record found in our database - signatures: {signatures}') for signature in signatures: if cla.utils.is_approved( signature, - email=author_email, - github_id=author_id, - github_username=author_username + email=user_commit_summary.author_email, + github_id=user_commit_summary.author_id, + github_username=user_commit_summary.author_login # double check this... ): - # Append whitelisted flag to the author info list - cla.log.debug(f'Github user(id:{author_id}, ' - f'user: {author_username}, ' - f'email {author_email}) is on the approved list, ' - 'but not affiliated with a company') - - list_author_info.append(True) + cla.log.debug(f'{fn} - User Commit Summary: {user_commit_summary}, ' + 'is on one of the approval lists, but not affiliated with a company') + user_commit_summary.authorized = True break - missing.append((commit_sha, list_author_info)) + + missing.append(user_commit_summary) -def get_pull_request_commit_authors(pull_request): +def get_pull_request_commit_authors(pull_request) -> List[UserCommitSummary]: """ Helper function to extract all committer information for a GitHub PR. @@ -923,61 +909,74 @@ def get_pull_request_commit_authors(pull_request): For activity callback, see: https://developer.github.com/v3/activity/events/types/#pullrequestevent - :param pull_request: A GitHub pull request to examine. - :type pull_request: GitHub.PullRequest - :return: A list of tuples containing a tuple of (commit_sha_string, (author_id, author_username, author_email)) - - the second item is another tuple of author info. - :rtype: [(commit_sha_string, (author_id, author_username, author_email)] + :param: pull_request: A GitHub pull request to examine. + :type: pull_request: GitHub.PullRequest + :return: A list of User Commit Summary objects containing the commit sha and available user information + :rtype: List[UserCommitSummary] """ + + fn = 'cla.models.github_models.get_pull_request_commit_authors' cla.log.debug('Querying pull request commits for author information...') commit_authors = [] for commit in pull_request.get_commits(): - cla.log.debug('Processing commit while looking for authors, commit: {}'.format(commit.sha)) + cla.log.debug(f'{fn} - Processing commit while looking for authors, commit: {commit.sha}') # Note: we can get the author info in two different ways: + # https://pygithub.readthedocs.io/en/latest/github_objects/NamedUser.html if commit.author: try: - # commit.author is a github.NamedUser.NamedUser type object - # https://pygithub.readthedocs.io/en/latest/github_objects/NamedUser.html - if commit.author.name is not None: - cla.log.debug('PR: {}, GitHub commit.author.name author found for commit SHA {}, ' - 'author id: {}, name: {}, email: {}'. - format(pull_request.number, commit.sha, commit.author.id, - commit.author.name, commit.author.email)) - commit_authors.append((commit.sha, (commit.author.id, commit.author.name, commit.author.email))) - elif commit.author.login is not None: - cla.log.debug('PR: {}, GitHub commit.author.login author found for commit SHA {}, ' - 'author id: {}, login: {}, email: {}'. - format(pull_request.number, commit.sha, commit.author.id, - commit.author.login, commit.author.email)) - commit_authors.append((commit.sha, (commit.author.id, commit.author.login, commit.author.email))) - else: - cla.log.debug(f'PR: {pull_request.number}, GitHub commit.author.name and commit.author.login ' - f'author information NOT found for commit SHA {commit.sha}, ' - f'author id: {commit.author.id}, ' - f'name: {commit.author.name}, ' - f'login: {commit.author.login}, ' - f'email: {commit.author.email}') - commit_authors.append((commit.sha, None)) + commit_author_summary = UserCommitSummary( + pull_request.number, + commit.author.id, + commit.author.login, + commit.author.name, + commit.author.email, + False, False # default not authorized - will be evaluated and updated later + ) + cla.log.debug(f'{fn} - PR: {pull_request.number}, {commit_author_summary}') + commit_authors.append(commit_author_summary) except (GithubException, IncompletableObject) as ex: cla.log.debug(f'Commit sha: {commit.sha} exception: {ex}') try: - cla.log.debug('github.GitAuthor.GitAuthor object: {}'.format(commit.commit.author)) # commit.commit.author is a github.GitAuthor.GitAuthor object type - object # only has date, name and email attributes - no ID attribute/value # https://pygithub.readthedocs.io/en/latest/github_objects/GitAuthor.html - cla.log.debug('PR: {}, GitHub NamedUser author NOT found for commit SHA {}, ' - 'however, found GitAuthor author id: None, name: {}, email: {}'. - format(pull_request.number, commit.sha, - commit.commit.author.name, commit.commit.author.email)) - commit_authors.append((commit.sha, (None, commit.commit.author.name, commit.commit.author.email))) + commit_author_summary = UserCommitSummary( + pull_request.number, + None, + None, + commit.commit.author.name, + commit.commit.author.email, + False, False # default not authorized - will be evaluated and updated later + ) + cla.log.debug(f'{fn} - github.GitAuthor.GitAuthor object: {commit.commit.author}') + cla.log.debug(f'{fn} - PR: {pull_request.number}, ' + f'GitHub NamedUser author NOT found for commit SHA {commit_author_summary} ' + f'however, we did find GitAuthor info') + commit_authors.append(commit_author_summary) except (GithubException, IncompletableObject): - cla.log.warning( - 'PR: {}, could not find any commit author for SHA {}'.format(pull_request.number, commit.sha)) - commit_authors.append((commit.sha, None)) + commit_author_summary = UserCommitSummary( + pull_request.number, + None, + None, + None, + None, + False, False # default not authorized - will be evaluated and updated later + ) + cla.log.warning(f'{fn} - PR: {pull_request.number}, ' + f'could not find any commit author for SHA {commit_author_summary}') + commit_authors.append(commit_author_summary) else: - cla.log.warning('PR: {}, could not find any commit author for SHA {}'. - format(pull_request.number, commit.sha)) - commit_authors.append((commit.sha, None)) + commit_author_summary = UserCommitSummary( + pull_request.number, + None, + None, + None, + None, + False, False # default not authorized - will be evaluated and updated later + ) + cla.log.warning(f'{fn} - PR: {pull_request.number}, ' + f'could not find any commit author for SHA {commit_author_summary}') + commit_authors.append(commit_author_summary) return commit_authors @@ -1005,28 +1004,28 @@ def has_check_previously_failed(pull_request: PullRequest): return False, None -def update_pull_request(installation_id, github_repository_id, pull_request, repository_name, signed, - missing, project_version): # pylint: disable=too-many-locals +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 """ Helper function to update a PR's comment and status based on the list of signers. - :param installation_id: The ID of the GitHub installation - :type installation_id: int - :param github_repository_id: The ID of the GitHub repository this PR belongs to. - :type github_repository_id: int - :param pull_request: The GitHub PullRequest object for this PR. - :type pull_request: GitHub.PullRequest - :param repository_name: The GitHub repository name for this PR. - :type repository_name: string - :param signed: The list of (commit hash, author name) tuples that have signed an - signature for this PR. - :type signed: [(string, string)] - :param missing: The list of (commit hash, author name) tuples that have not signed - an signature for this PR. - :type missing: [(string, list)] - :param project_version: Project version associated with PR - :type missing: string + :param: installation_id: The ID of the GitHub installation + :type: installation_id: int + :param: github_repository_id: The ID of the GitHub repository this PR belongs to. + :type: github_repository_id: int + :param: pull_request: The GitHub PullRequest object for this PR. + :type: pull_request: GitHub.PullRequest + :param: repository_name: The GitHub repository name for this PR. + :type: repository_name: string + :param: signed: The list of User Commit Summary objects for this PR. + :type: signed: List[UserCommitSummary] + :param: missing: The list of User Commit Summary objects for this PR. + :type: missing: List[UserCommitSummary] + :param: project_version: Project version associated with PR + :type: missing: string """ + fn = 'cla.models.github_models.update_pull_request' notification = cla.conf['GITHUB_PR_NOTIFICATION'] both = notification == 'status+comment' or notification == 'comment+status' last_commit = pull_request.get_commits().reversed[0] @@ -1036,30 +1035,21 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep # Create check run for users that haven't yet signed and/or affiliated if missing: text = "" - for authors in missing: + for user_commit_summary in missing: # Check for valid github id - if authors[1] is None or (authors[1] and authors[1][0] is None): + # old tuple: (sha, (author_id, author_login_or_name, author_email, optionalTrue)) + if not user_commit_summary.is_valid_user(): help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" else: help_url = cla.utils.get_full_sign_url('github', str(installation_id), github_repository_id, pull_request.number, project_version) + client = GitHubInstallation(installation_id) # check if unsigned user is whitelisted - commit_sha = authors[0] - if commit_sha != last_commit.sha: + if user_commit_summary.commit_sha != last_commit.sha: continue - if authors[1]: - author_email = authors[1][2] - author_id = authors[1][0] - if author_id: - if len(authors[1]) == 4: - text += f'{author_email} must confirm corporate affiliation.\n' - else: - text += f'{author_email} is not authorized under a signed CLA.\n' - else: - text += f'{author_email} is not linked to this commit. \n' - else: - text += 'Invalid author details. \n' + + text += user_commit_summary.get_display_text() payload = { "name": "CLA check", @@ -1084,20 +1074,21 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep # After Issue #167 wsa in place, they decided via Issue #289 that we # DO want to update the comment, but only after we've previously failed if previously_failed: - cla.log.debug('Found previously failed checks - updating CLA comment in PR.') + cla.log.debug(f'{fn} - Found previously failed checks - updating CLA comment in PR.') comment.edit(body) - cla.log.debug('EasyCLA App checks pass for PR: {} with authors: {}'.format(pull_request.number, signed)) + cla.log.debug(f'{fn} - EasyCLA App checks pass for PR: {pull_request.numbe} with authors: {signed}') else: # Per Issue #167, only add a comment if check fails # update_cla_comment(pull_request, body) if previously_failed: - cla.log.debug('Found previously failed checks - updating CLA comment in PR.') + cla.log.debug(f'{fn} - Found previously failed checks - updating CLA comment in PR.') comment.edit(body) else: pull_request.create_issue_comment(body) - cla.log.debug('EasyCLA App checks fail for PR: {}. CLA signatures with signed authors: {} and ' - 'with missing authors: {}'.format(pull_request.number, signed, missing)) + cla.log.debug(f'{fn} - EasyCLA App checks fail for PR: {pull_request.number}. ' + f'CLA signatures with signed authors: {signed} and ' + f'with missing authors: {missing}') if both or notification == 'status': context_name = os.environ.get('GH_STATUS_CTX_NAME') @@ -1112,7 +1103,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep context, body = cla.utils.assemble_cla_status(context_name, signed=False) sign_url = cla.utils.get_full_sign_url( 'github', str(installation_id), github_repository_id, pull_request.number, project_version) - cla.log.debug(f'Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + cla.log.debug(f'{fn} - Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' f'signing url: {sign_url}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) elif signed is not None and len(signed) > 0: @@ -1123,7 +1114,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep sign_url = cla.conf["CLA_LANDING_PAGE"] # Remove this once signature detail page ready. sign_url = os.path.join(sign_url, "#/") sign_url = append_project_version_to_url(address=sign_url, project_version=project_version) - cla.log.debug(f'Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + cla.log.debug(f'{fn} - Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' f'signing url: {sign_url}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) else: @@ -1135,9 +1126,10 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep context, body = cla.utils.assemble_cla_status(context_name, signed=False) sign_url = cla.utils.get_full_sign_url( 'github', str(installation_id), github_repository_id, pull_request.number, project_version) - cla.log.debug(f'Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + cla.log.debug(f'{fn} - Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' f'signing url: {sign_url}') - cla.log.warning('This is an error condition - should have at least one committer in one of these lists: ' + cla.log.warning('{fn} - This is an error condition - ' + f'should have at least one committer in one of these lists: ' f'{len(signed)} passed, {missing}') create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py index c4e051621..5a413a836 100755 --- a/cla-backend/cla/routes.py +++ b/cla-backend/cla/routes.py @@ -1646,8 +1646,7 @@ def github_app_activity(body, request, response): cla.log.error(f'{fn} - v4 golang api failed with : 500 : {ex}') response.status = HTTP_500 return {"status": f'v4_easycla_github_activity failed {ex}'} - cla.log.debug(f'{fn} - not forwarding event type: \'{event_type}\' with action: \'{action}\'.' - 'Will handle it here...') + cla.log.debug(f'{fn} - not forwarding event type: \'{event_type}\' with action: \'{action}\'.') if event_type is None: cla.log.error(f"{fn} - unable to determine the event type from request headers: {request.headers}") diff --git a/cla-backend/cla/tests/unit/test_github_models.py b/cla-backend/cla/tests/unit/test_github_models.py index f7d02d17d..17d593700 100644 --- a/cla-backend/cla/tests/unit/test_github_models.py +++ b/cla-backend/cla/tests/unit/test_github_models.py @@ -2,14 +2,15 @@ # SPDX-License-Identifier: MIT import logging import unittest -from unittest.mock import Mock, patch, MagicMock +from unittest.mock import patch, MagicMock from github import Github import cla -from cla.models.github_models import get_pull_request_commit_authors, handle_commit_from_user, MockGitHub from cla.models.dynamo_models import Signature, Project from cla.models.github_models import GitHub as GithubModel +from cla.models.github_models import get_pull_request_commit_authors, handle_commit_from_user, MockGitHub +from cla.user import UserCommitSummary class TestGitHubModels(unittest.TestCase): @@ -56,7 +57,7 @@ def test_commit_authors_with_named_user(self) -> None: commit_authors = get_pull_request_commit_authors(pr) # cla.log.info("Result: {}".format(commit_authors)) # cla.log.info([author_info[1] for commit, author_info in commit_authors]) - self.assertTrue(4779759 in [author_info[0] for commit, author_info in commit_authors]) + self.assertTrue(4779759 in [user_commit_summary.author_id for user_commit_summary in commit_authors]) def test_commit_authors_no_named_user(self) -> None: """ @@ -89,26 +90,27 @@ def test_handle_commit_author_whitelisted(self) -> None: self.mock_user_get.return_value.get_user_by_email.return_value = None self.mock_signature_get.return_value.get_signatures_by_project.return_value = [Signature()] self.mock_utils_get.return_value.is_approved.return_value = True + user_commit_summary = UserCommitSummary('fake_sha', 123, 'foo', None, 'foo@gmail.com', True, True) missing = [] signed = [] project = Project() project.set_project_id('fake_project_id') - handle_commit_from_user(project, 'fake_sha', (123, 'foo', 'foo@gmail.com'), signed, missing) + handle_commit_from_user(project, user_commit_summary, signed, missing) # We commented out this functionality for now - re-enable if we add it back - # self.assertListEqual(missing, [('fake_sha', [123, 'foo', 'foo@gmail.com', True])]) + self.assertEqual(missing, [user_commit_summary]) self.assertEqual(signed, []) - + def test_handle_invalid_author(self) -> None: """ - Test case handling non existant author tagged to a given commit + Test case handling non existent author tagged to a given commit """ project = Project() - author_info = None - signed = [] + author_info = UserCommitSummary('fake_sha', None, None, None, None, False, False) + signed = [] missing = [] - handle_commit_from_user(project, 'fake_sha', author_info, signed, missing) + handle_commit_from_user(project, author_info, signed, missing) self.assertEqual(signed, []) - self.assertEqual(missing, [('fake_sha', [])]) + self.assertEqual(missing, [author_info]) class TestGithubModelsPrComment(unittest.TestCase): diff --git a/cla-backend/cla/user.py b/cla-backend/cla/user.py index e36298262..21c262ab4 100644 --- a/cla-backend/cla/user.py +++ b/cla-backend/cla/user.py @@ -5,10 +5,15 @@ user.py contains the user class and hug directive. """ +from dataclasses import dataclass +from typing import Optional + from hug.directives import _built_in_directive -import cla from jose import jwt +import cla + + @_built_in_directive def cla_user(default=None, request=None, **kwargs): """Returns the current logged in CLA user""" @@ -18,7 +23,7 @@ def cla_user(default=None, request=None, **kwargs): cla.log.error('Error reading headers') return default - bearer_token = headers.get('Authorization') or headers.get('AUTHORIZATION') + bearer_token = headers.get('Authorization') or headers.get('AUTHORIZATION') if bearer_token is None: cla.log.error('Error reading authorization header') @@ -49,3 +54,53 @@ def __init__(self, data): self.family_name = data.get('family_name', None) self.email = data.get('email', None) self.roles = data.get('realm_access', {}).get('roles', []) + + +@dataclass +class UserCommitSummary: + commit_sha: str + author_id: Optional[int] # numeric ID of the user + author_login: Optional[str] # login identifier of the user + author_name: Optional[str] # english name of the user, typically First name Last name format. + author_email: Optional[str] # public email address of the user + authorized: bool + affiliated: bool + + def __str__(self) -> str: + return (f'User Commit Summary, ' + f'commit SHA: {self.commit_sha}, ' + f'author id: {self.author_id}, ' + f'login: {self.author_login}, ' + f'name: {self.author_name}, ' + f'email: {self.author_email}.') + + def is_valid_user(self) -> bool: + return self.author_id is not None and (self.author_login is not None or self.author_name is not None) + + def get_display_text(self) -> str: + text = '' + + if not self.author_id: + return f'{self.author_email} is not linked to this commit.\n' + + # Build up the user text + if self.author_login: + text += f'login: {self.author_login} / ' + if self.author_name: + text += f'name: {self.author_name} / ' + if self.author_email: + text += f'email: {self.author_email} ' + + if not self.is_valid_user(): + return 'Invalid author details.\n' + + if self.authorized and self.affiliated: + text += 'is authorized.\n' + return text + + if self.affiliated: + text += 'is associated with a company, but not on an approval list.\n' + else: + text += 'is not associated with a company.\n' + + return text diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 7ec6f7cc5..d24ab4a57 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -27,6 +27,7 @@ GitHubOrg, Gerrit, UserPermissions, Event, CompanyInvite, ProjectCLAGroup, CCLAWhitelistRequest, CLAManagerRequest, \ GitlabOrg from cla.models.event_types import EventType +from cla.user import UserCommitSummary API_BASE_URL = os.environ.get('CLA_API_BASE', '') CLA_LOGO_URL = os.environ.get('CLA_BUCKET_LOGO_URL', '') @@ -906,7 +907,8 @@ def assemble_cla_status(author_name, signed=False): return author_name, 'Missing CLA Authorization.' -def assemble_cla_comment(repository_type, installation_id, github_repository_id, change_request_id, signed, missing, +def assemble_cla_comment(repository_type, installation_id, github_repository_id, change_request_id, + signed: List[UserCommitSummary], missing: List[UserCommitSummary], project_version): """ Helper function to generate a CLA comment based on a a change request. @@ -923,20 +925,21 @@ def assemble_cla_comment(repository_type, installation_id, github_repository_id, :type github_repository_id: int :param change_request_id: The repository service's ID of this change request. :type change_request_id: id - :param signed: The list of commit hashes and authors that have signed an signature for this + :param signed: The list of user commit summary objects indicating which authors that have signed an signature for + this change request. + :type signed: List[UserCommitSummary] + :param missing: The list of user commit summary objects indicating which authors have not signed for this change request. - :type signed: [(string, string)] - :param missing: The list of commit hashes and authors that have not signed for this - change request. - :type missing: [(string, list)] + :type missing: List[UserCommitSummary] :param project_version: Project version associated with PR comment :type project_version: string """ - num_missing = len(missing) - # fails to catch empty list - # missing_ids = list(filter(lambda x: (x[1] is None or (x[1] and x[1][0] is None)), missing)) - missing_ids = list(filter(lambda x: (x[1] is None or len(x[1]) == 0), missing)) - no_user_id = len(missing_ids) > 0 + + # missing_ids = list(filter(lambda x: (x[1] is None or len(x[1]) == 0), missing)) + + # Test to see if any of the users in the missing category are missing their user id + no_user_id = len(list(filter(lambda x: (x.author_id is None), missing))) > 0 + # check if an unsigned committer has been approved by a CLA Manager, but not associated with a company # Logic not supported as we removed the DB query in the caller # approved_ids = list(filter(lambda x: len(x[1]) == 4 and x[1][3] is True, missing)) @@ -944,7 +947,7 @@ def assemble_cla_comment(repository_type, installation_id, github_repository_id, sign_url = get_full_sign_url(repository_type, installation_id, github_repository_id, change_request_id, project_version) comment = get_comment_body(repository_type, sign_url, signed, missing) - all_signed = num_missing == 0 + all_signed = len(missing) == 0 badge = get_comment_badge( repository_type=repository_type, all_signed=all_signed, @@ -1004,7 +1007,7 @@ def get_comment_body(repository_type, sign_url, signed, missing): committers[name] = [] committers[name].append(commit) # Check case for whitelisted unsigned user - if len(author) == 4: + if len(author) == 5: committers[name].append(True) # Print author commit information. From f644d8e9196342ef229be60c0b1b7eadde8a48ed Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Mar 2022 13:12:09 -0800 Subject: [PATCH 0595/1276] Updated the GitHub Comment Body Logic (#3466) --- .../cla/tests/unit/test_github_models.py | 2 +- cla-backend/cla/user.py | 13 ++ cla-backend/cla/utils.py | 119 ++++++++++-------- 3 files changed, 80 insertions(+), 54 deletions(-) diff --git a/cla-backend/cla/tests/unit/test_github_models.py b/cla-backend/cla/tests/unit/test_github_models.py index 17d593700..cea0d600f 100644 --- a/cla-backend/cla/tests/unit/test_github_models.py +++ b/cla-backend/cla/tests/unit/test_github_models.py @@ -102,7 +102,7 @@ def test_handle_commit_author_whitelisted(self) -> None: def test_handle_invalid_author(self) -> None: """ - Test case handling non existent author tagged to a given commit + Test case handling non-existent author tagged to a given commit """ project = Project() author_info = UserCommitSummary('fake_sha', None, None, None, None, False, False) diff --git a/cla-backend/cla/user.py b/cla-backend/cla/user.py index 21c262ab4..26ed2206b 100644 --- a/cla-backend/cla/user.py +++ b/cla-backend/cla/user.py @@ -5,6 +5,7 @@ user.py contains the user class and hug directive. """ +import re from dataclasses import dataclass from typing import Optional @@ -77,6 +78,18 @@ def __str__(self) -> str: def is_valid_user(self) -> bool: return self.author_id is not None and (self.author_login is not None or self.author_name is not None) + def get_user_info(self) -> str: + user_info = '' + if self.author_login: + user_info += f'login: {self.author_login} / ' + if self.author_name: + user_info += f'name: {self.author_name} / ' + if self.author_email: + user_info += f'email: {self.author_email}' + + pattern = r'/ $' + return re.sub(pattern, '', user_info) + def get_display_text(self) -> str: text = '' diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index d24ab4a57..26ab9b4f9 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -957,19 +957,18 @@ def assemble_cla_comment(repository_type, installation_id, github_repository_id, return badge + '
        ' + comment -def get_comment_body(repository_type, sign_url, signed, missing): +def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], missing: List[UserCommitSummary]): """ Returns the CLA comment that will appear on the repository provider's change request item. - :param repository_type: The repository type where this comment will be posted ('github', - 'gitlab', etc). - :type repository_type: string - :param sign_url: The URL for the user to click in order to initiate signing. - :type sign_url: string - :param signed: List of tuples containing the commit and author name of signers. - :type signed: [(string, string)] - :param missing: List of tuples containing the commit and author name of not-signed users. - :type missing: [(string, list)] + :param: repository_type: The repository type where this comment will be posted ('github', 'gitlab', etc). + :type: repository_type: string + :param: sign_url: The URL for the user to click in order to initiate signing. + :type: sign_url: string + :param: signed: List of user commit summary objects containing the commit and author name of signers. + :type: signed: List[UserCommitSummary] + :param: missing: List of user commit summary objects containing the commit and author name of not-signed users. + :type: missing: List[UserCommitSummary] """ fn = "utils.get_comment_body" cla.log.info(f"{fn} - Getting comment body for repository type: %s", repository_type) @@ -982,41 +981,51 @@ def get_comment_body(repository_type, sign_url, signed, missing): if num_signed > 0: # Group commits by author. committers = {} - for commit, author in signed: - if author is None: - author = "Unknown" - if author not in committers: - committers[author] = [] - committers[author].append(commit) + for user_commit_summary in signed: + if user_commit_summary.is_valid_user(): + author_info = user_commit_summary.get_user_info() + else: + author_info = "Unknown" + + if author_info not in committers: + committers[author_info] = [] + + # user commit summary includes the author information and the corresponding commit hash + committers[author_info].append(user_commit_summary) + # Print author commit information. committers_comment += "
          " - for author, commit_hashes in committers.items(): - committers_comment += "
        • " + success + " " + author + " (" + ", ".join(commit_hashes) + ")
        • " + for author_info, user_commit_summaries in committers.items(): + # build a quick list of just the commit hash values + commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] + committers_comment += f'
        • {success} {author_info} ({", ".join(commit_shas)})
        • ' committers_comment += "
        " if num_missing > 0: support_url = "https://jira.linuxfoundation.org/servicedesk/customer/portal/4" # Group commits by author. committers = {} - # Consider the case where github Id does not exist - for commit, author in missing: - name = "Unknown" - if author and author[0]: - name = author[1] - if name not in committers: - committers[name] = [] - committers[name].append(commit) - # Check case for whitelisted unsigned user - if len(author) == 5: - committers[name].append(True) + for user_commit_summary in missing: + if user_commit_summary.is_valid_user(): + author_info = user_commit_summary.get_user_info() + else: + author_info = "Unknown" + + if author_info not in committers: + committers[author_info] = [] + + # user commit summary includes the author information and the corresponding commit hash + committers[author_info].append(user_commit_summary) # Print author commit information. committers_comment += "
          " github_help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" - for author, commit_hashes in committers.items(): - if author == "Unknown": + for author_info, user_commit_summaries in committers.items(): + if author_info == "Unknown": + # build a quick list of just the commit hash values + commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
        • {failed} The commit ({' ,'.join(commit_hashes)}) " + f"
        • {failed} The commit ({' ,'.join(commit_shas)}) " + f"is missing the User's ID, preventing the EasyCLA check. " + f"Consult GitHub Help to resolve." + f"For further assistance with EasyCLA, " @@ -1024,30 +1033,34 @@ def get_comment_body(repository_type, sign_url, signed, missing): + "
        • " ) else: - if True in commit_hashes: - cla.log.info(f"{fn} filter True in commit hash listing: {commit_hashes}") - filtered_commits = [commit_hash for commit_hash in commit_hashes if commit_hash != True] + missing_affiliation = [user_commit_summary.affiliation for user_commit_summary in user_commit_summaries + if not user_commit_summary.affiliation] + if len(missing_affiliation) > 0: + # build a quick list of just the commit hash values for users missing company affiliations + commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries + if not user_commit_summary.affiliation] + cla.log.info(f"{fn} SHAs for users with missing company affiliations: {commit_shas}") committers_comment += ( - f"
        • {author} ({' ,'.join(filtered_commits)}) " - + f"is authorized, but they must confirm their affiliation with their company. " - + f"Start the authorization process " - + f" by clicking here, click \"Corporate\"," - + f"select the appropriate company from the list, then confirm " - + f"your affiliation on the page that appears. " - + f"For further assistance with EasyCLA, " - + f"please submit a support request ticket." - + "
        • " - ) + f'
        • {author_info} ({" ,".join(commit_shas)}) ' + f'is authorized, but they must confirm their affiliation with their company. ' + f'Start the authorization process ' + f" by clicking here, click \"Corporate\"," + f'select the appropriate company from the list, then confirm ' + f'your affiliation on the page that appears. ' + f'For further assistance with EasyCLA, ' + f"please submit a support request ticket." + '
        • ') else: + # build a quick list of just the commit hash values + commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
        • " - + f"{failed} - " - + f"{author} The commit ({' ,'.join(commit_hashes)}) is not authorized under a signed CLA. " - + f"Please click here to be authorized. " - + f"For further assistance with EasyCLA, " - + f"please submit a support request ticket." - + "
        • " - ) + f'
        • ' + f"{failed} - " + f"{author_info} The commit ({' ,'.join(commit_shas)}) is not authorized under a signed CLA. " + f"Please click here to be authorized. " + f"For further assistance with EasyCLA, " + f"please submit a support request ticket." + "
        • ") committers_comment += "
        " return committers_comment From 18add19b1f2447929af27ddc21787abc25ea6a2d Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Mar 2022 15:25:07 -0800 Subject: [PATCH 0596/1276] Bug/user commit summary (#3467) --- cla-backend/.gitignore | 1 + .../tests/unit/test_user_commit_summary.py | 38 +++++++++++++++++++ cla-backend/cla/tests/unit/test_user_event.py | 4 +- cla-backend/cla/utils.py | 33 ++++++++-------- 4 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 cla-backend/cla/tests/unit/test_user_commit_summary.py diff --git a/cla-backend/.gitignore b/cla-backend/.gitignore index 359233f88..da4d52a37 100644 --- a/cla-backend/.gitignore +++ b/cla-backend/.gitignore @@ -14,4 +14,5 @@ _env.json .mypy_cache .venv .vscode/ +.coverage* diff --git a/cla-backend/cla/tests/unit/test_user_commit_summary.py b/cla-backend/cla/tests/unit/test_user_commit_summary.py new file mode 100644 index 000000000..b191272b5 --- /dev/null +++ b/cla-backend/cla/tests/unit/test_user_commit_summary.py @@ -0,0 +1,38 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +import unittest + +from cla.user import UserCommitSummary +from cla.utils import get_comment_body + + +class TestUserCommitSummary(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def setUp(self) -> None: + pass + + def tearDown(self) -> None: + pass + + def test_user_commit_summary_is_valid(self) -> None: + t = UserCommitSummary("some_sha", 1234, 'login_value', 'author name', 'foo@bar.com', False, False) + self.assertTrue(t.is_valid_user()) + t = UserCommitSummary("some_sha", 1234, None, None, 'foo@bar.com', False, False) + self.assertFalse(t.is_valid_user()) + + def test_user_commit_summary_get_comment_body(self) -> None: + s = UserCommitSummary("some_sha", 1234, 'login_value', 'author name', 'foo@bar.com', True, True) + signed = [s] + m = UserCommitSummary("some_other_sha", 123456, 'login_value2', 'author name2', 'foo2@bar.com', False, False) + missing = [m] + body = get_comment_body('github', 'https://foo.com', signed, missing) + self.assertTrue(':white_check_mark:' in body) + self.assertTrue(':x:' in body) diff --git a/cla-backend/cla/tests/unit/test_user_event.py b/cla-backend/cla/tests/unit/test_user_event.py index e935e877d..4351a4567 100644 --- a/cla-backend/cla/tests/unit/test_user_event.py +++ b/cla-backend/cla/tests/unit/test_user_event.py @@ -2,14 +2,12 @@ # SPDX-License-Identifier: MIT from unittest.mock import patch, Mock -import unittest import pytest +from cla.controllers import user as user_controller from cla.models.dynamo_models import User, Project, Company, CCLAWhitelistRequest, CompanyInvite from cla.models.event_types import EventType -from cla.controllers import user as user_controller -from cla.auth import AuthUser @pytest.fixture diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 26ab9b4f9..a0678038a 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -994,12 +994,12 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers[author_info].append(user_commit_summary) # Print author commit information. - committers_comment += "
          " + committers_comment += '
            ' for author_info, user_commit_summaries in committers.items(): # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += f'
          • {success} {author_info} ({", ".join(commit_shas)})
          • ' - committers_comment += "
          " + committers_comment += '
        ' if num_missing > 0: support_url = "https://jira.linuxfoundation.org/servicedesk/customer/portal/4" @@ -1009,7 +1009,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], if user_commit_summary.is_valid_user(): author_info = user_commit_summary.get_user_info() else: - author_info = "Unknown" + author_info = 'Unknown' if author_info not in committers: committers[author_info] = [] @@ -1018,30 +1018,29 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers[author_info].append(user_commit_summary) # Print author commit information. - committers_comment += "
          " + committers_comment += '
            ' github_help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" for author_info, user_commit_summaries in committers.items(): - if author_info == "Unknown": + if author_info == 'Unknown': # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( f"
          • {failed} The commit ({' ,'.join(commit_shas)}) " - + f"is missing the User's ID, preventing the EasyCLA check. " - + f"Consult GitHub Help to resolve." - + f"For further assistance with EasyCLA, " - + f"please submit a support request ticket." - + "
          • " - ) + f"is missing the User's ID, preventing the EasyCLA check. " + f"Consult GitHub Help to resolve." + f'For further assistance with EasyCLA, ' + f"please submit a support request ticket." + '') else: - missing_affiliation = [user_commit_summary.affiliation for user_commit_summary in user_commit_summaries - if not user_commit_summary.affiliation] - if len(missing_affiliation) > 0: + missing_affiliations = [user_commit_summary.affiliated for user_commit_summary in user_commit_summaries + if not user_commit_summary.affiliated] + if len(missing_affiliations) > 0: # build a quick list of just the commit hash values for users missing company affiliations commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries - if not user_commit_summary.affiliation] - cla.log.info(f"{fn} SHAs for users with missing company affiliations: {commit_shas}") + if not user_commit_summary.affiliated] + cla.log.info(f'{fn} SHAs for users with missing company affiliations: {commit_shas}') committers_comment += ( - f'
          • {author_info} ({" ,".join(commit_shas)}) ' + f'
          • {failed} {author_info} ({" ,".join(commit_shas)}) ' f'is authorized, but they must confirm their affiliation with their company. ' f'Start the authorization process ' f" by clicking here, click \"Corporate\"," From 49aab180c9917453a4e026842c202c0d5a5dfcb4 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Mar 2022 16:11:06 -0800 Subject: [PATCH 0597/1276] Working to Resolve Type Error When Generating GitHub Body Text (#3468) --- cla-backend/cla/tests/unit/test_user_commit_summary.py | 8 ++++++-- cla-backend/cla/utils.py | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cla-backend/cla/tests/unit/test_user_commit_summary.py b/cla-backend/cla/tests/unit/test_user_commit_summary.py index b191272b5..eb823b927 100644 --- a/cla-backend/cla/tests/unit/test_user_commit_summary.py +++ b/cla-backend/cla/tests/unit/test_user_commit_summary.py @@ -29,10 +29,14 @@ def test_user_commit_summary_is_valid(self) -> None: self.assertFalse(t.is_valid_user()) def test_user_commit_summary_get_comment_body(self) -> None: - s = UserCommitSummary("some_sha", 1234, 'login_value', 'author name', 'foo@bar.com', True, True) - signed = [s] + s1 = UserCommitSummary("abc1234xyz-123", 1234, 'login_value', 'author name', 'foo@bar.com', True, True) + s2 = UserCommitSummary("abc1234xyz-456", 1234, 'login_value', 'author name', 'foo@bar.com', True, True) + signed = [s1, s2] + m = UserCommitSummary("some_other_sha", 123456, 'login_value2', 'author name2', 'foo2@bar.com', False, False) missing = [m] + body = get_comment_body('github', 'https://foo.com', signed, missing) + print(f'body: {body}') self.assertTrue(':white_check_mark:' in body) self.assertTrue(':x:' in body) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index a0678038a..f5fefc658 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -985,7 +985,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], if user_commit_summary.is_valid_user(): author_info = user_commit_summary.get_user_info() else: - author_info = "Unknown" + author_info = 'Unknown' if author_info not in committers: committers[author_info] = [] @@ -998,7 +998,8 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], for author_info, user_commit_summaries in committers.items(): # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] - committers_comment += f'
          • {success} {author_info} ({", ".join(commit_shas)})
          • ' + cla.log.info(f'{fn} SHAs for signed users: {commit_shas}') + committers_comment += f'
          • {success} {author_info} ({", ".join(str(commit_shas))})
          • ' committers_comment += '
          ' if num_missing > 0: From c0893b1ff8d40ff7f809754625c4e7c6993598ae Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Mar 2022 17:11:20 -0800 Subject: [PATCH 0598/1276] Resolved Typo in PR Checks Debug Statement (#3469) --- cla-backend/cla/models/github_models.py | 2 +- cla-backend/cla/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index e2ec5425b..14e9b537d 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -1076,7 +1076,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep if previously_failed: cla.log.debug(f'{fn} - Found previously failed checks - updating CLA comment in PR.') comment.edit(body) - cla.log.debug(f'{fn} - EasyCLA App checks pass for PR: {pull_request.numbe} with authors: {signed}') + cla.log.debug(f'{fn} - EasyCLA App checks pass for PR: {pull_request.number} with authors: {signed}') else: # Per Issue #167, only add a comment if check fails # update_cla_comment(pull_request, body) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index f5fefc658..6fddcae45 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1026,7 +1026,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
        • {failed} The commit ({' ,'.join(commit_shas)}) " + f"
        • {failed} The commit ({' ,'.join(str(commit_shas))}) " f"is missing the User's ID, preventing the EasyCLA check. " f"Consult GitHub Help to resolve." f'For further assistance with EasyCLA, ' @@ -1041,7 +1041,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], if not user_commit_summary.affiliated] cla.log.info(f'{fn} SHAs for users with missing company affiliations: {commit_shas}') committers_comment += ( - f'
        • {failed} {author_info} ({" ,".join(commit_shas)}) ' + f'
        • {failed} {author_info} ({" ,".join(str(commit_shas))}) ' f'is authorized, but they must confirm their affiliation with their company. ' f'Start the authorization process ' f" by clicking here, click \"Corporate\"," From eb2dbe1f8d6e729359615de132185dc2a55cef92 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 1 Mar 2022 17:36:37 -0800 Subject: [PATCH 0599/1276] Resolved Commit SHA Issue with User Commit Summary Object (#3470) --- cla-backend/cla/models/github_models.py | 10 ++++++---- cla-backend/cla/utils.py | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 14e9b537d..008d4f023 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -793,6 +793,7 @@ def handle_commit_from_user(project, user_commit_summary: UserCommitSummary, sig # Does this user have a signed signature for this project? If so, add to the signed list and return, # no reason to continue looking if cla.utils.user_signed_project_signature(user, project): + user_commit_summary.authorized = True signed.append(user_commit_summary) return @@ -850,6 +851,7 @@ def handle_commit_from_user(project, user_commit_summary: UserCommitSummary, sig # Does this user have a signed signature for this project? If so, add to the signed list and return, # no reason to continue looking if cla.utils.user_signed_project_signature(user, project): + user_commit_summary.authorized = True signed.append(user_commit_summary) return @@ -925,7 +927,7 @@ def get_pull_request_commit_authors(pull_request) -> List[UserCommitSummary]: if commit.author: try: commit_author_summary = UserCommitSummary( - pull_request.number, + commit.sha, commit.author.id, commit.author.login, commit.author.name, @@ -941,7 +943,7 @@ def get_pull_request_commit_authors(pull_request) -> List[UserCommitSummary]: # only has date, name and email attributes - no ID attribute/value # https://pygithub.readthedocs.io/en/latest/github_objects/GitAuthor.html commit_author_summary = UserCommitSummary( - pull_request.number, + commit.sha, None, None, commit.commit.author.name, @@ -955,7 +957,7 @@ def get_pull_request_commit_authors(pull_request) -> List[UserCommitSummary]: commit_authors.append(commit_author_summary) except (GithubException, IncompletableObject): commit_author_summary = UserCommitSummary( - pull_request.number, + commit.sha, None, None, None, @@ -967,7 +969,7 @@ def get_pull_request_commit_authors(pull_request) -> List[UserCommitSummary]: commit_authors.append(commit_author_summary) else: commit_author_summary = UserCommitSummary( - pull_request.number, + commit.sha, None, None, None, diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 6fddcae45..a2138ed2d 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -999,7 +999,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] cla.log.info(f'{fn} SHAs for signed users: {commit_shas}') - committers_comment += f'
        • {success} {author_info} ({", ".join(str(commit_shas))})
        • ' + committers_comment += f'
        • {success} {author_info} ({", ".join(commit_shas)})
        • ' committers_comment += '
        ' if num_missing > 0: @@ -1026,7 +1026,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
      • {failed} The commit ({' ,'.join(str(commit_shas))}) " + f"
      • {failed} The commit ({' ,'.join(commit_shas)}) " f"is missing the User's ID, preventing the EasyCLA check. " f"Consult GitHub Help to resolve." f'For further assistance with EasyCLA, ' @@ -1041,7 +1041,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], if not user_commit_summary.affiliated] cla.log.info(f'{fn} SHAs for users with missing company affiliations: {commit_shas}') committers_comment += ( - f'
      • {failed} {author_info} ({" ,".join(str(commit_shas))}) ' + f'
      • {failed} {author_info} ({" ,".join(commit_shas)}) ' f'is authorized, but they must confirm their affiliation with their company. ' f'Start the authorization process ' f" by clicking here, click \"Corporate\"," From 19d9c0e41fab54959ac7912df3098affeff5518f Mon Sep 17 00:00:00 2001 From: Harold Wanyama <81645226+nickmango@users.noreply.github.com> Date: Wed, 2 Mar 2022 18:30:32 +0300 Subject: [PATCH 0600/1276] Bug/User sign update (#3472) Co-authored-by: Harold Wanyama --- cla-backend/cla/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index a2138ed2d..dc2000e96 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1033,9 +1033,9 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], f"please submit a support request ticket." '
      • ') else: - missing_affiliations = [user_commit_summary.affiliated for user_commit_summary in user_commit_summaries + missing_affiliations = [user_commit_summary.affiliated and user_commit_summary.authorized for user_commit_summary in user_commit_summaries if not user_commit_summary.affiliated] - if len(missing_affiliations) > 0: + if len(missing_affiliations) > 0 : # build a quick list of just the commit hash values for users missing company affiliations commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries if not user_commit_summary.affiliated] From 6a008d7a496a5075bf866a68b3b8cb73cc7b73c9 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 2 Mar 2022 08:06:33 -0800 Subject: [PATCH 0601/1276] Reformatted the GitHub comment block text (#3473) --- cla-backend/cla/user.py | 15 ++++----------- cla-backend/cla/utils.py | 33 ++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cla-backend/cla/user.py b/cla-backend/cla/user.py index 26ed2206b..5cf69f371 100644 --- a/cla-backend/cla/user.py +++ b/cla-backend/cla/user.py @@ -91,29 +91,22 @@ def get_user_info(self) -> str: return re.sub(pattern, '', user_info) def get_display_text(self) -> str: - text = '' if not self.author_id: return f'{self.author_email} is not linked to this commit.\n' - # Build up the user text - if self.author_login: - text += f'login: {self.author_login} / ' - if self.author_name: - text += f'name: {self.author_name} / ' - if self.author_email: - text += f'email: {self.author_email} ' + text = self.get_user_info() if not self.is_valid_user(): return 'Invalid author details.\n' if self.authorized and self.affiliated: - text += 'is authorized.\n' + text += ' is authorized.\n' return text if self.affiliated: - text += 'is associated with a company, but not on an approval list.\n' + text += ' is associated with a company, but not on an approval list.\n' else: - text += 'is not associated with a company.\n' + text += ' is not associated with a company.\n' return text diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index dc2000e96..da59db862 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -977,6 +977,11 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers_comment = "" num_signed = len(signed) num_missing = len(missing) + text = '' + + # Start of the HTML to render the list of committers + if len(signed) > 0 or len(missing) > 0: + committers_comment += '
          ' if num_signed > 0: # Group commits by author. @@ -994,17 +999,16 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers[author_info].append(user_commit_summary) # Print author commit information. - committers_comment += '
            ' for author_info, user_commit_summaries in committers.items(): # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] cla.log.info(f'{fn} SHAs for signed users: {commit_shas}') committers_comment += f'
          • {success} {author_info} ({", ".join(commit_shas)})
          • ' - committers_comment += '
          ' if num_missing > 0: support_url = "https://jira.linuxfoundation.org/servicedesk/customer/portal/4" - # Group commits by author. + + # Build a lookup table to group all the commits by author. committers = {} for user_commit_summary in missing: if user_commit_summary.is_valid_user(): @@ -1018,16 +1022,15 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # user commit summary includes the author information and the corresponding commit hash committers[author_info].append(user_commit_summary) - # Print author commit information. - committers_comment += '
            ' + # Print the author commit information. github_help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" for author_info, user_commit_summaries in committers.items(): if author_info == 'Unknown': # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
          • {failed} The commit ({' ,'.join(commit_shas)}) " - f"is missing the User's ID, preventing the EasyCLA check. " + f"
          • {failed} The commit ({' ,'.join(commit_shas)}). " + f"This user is missing the User's ID, preventing the EasyCLA check. " f"Consult GitHub Help to resolve." f'For further assistance with EasyCLA, ' f"please submit a support request ticket." @@ -1041,8 +1044,8 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], if not user_commit_summary.affiliated] cla.log.info(f'{fn} SHAs for users with missing company affiliations: {commit_shas}') committers_comment += ( - f'
          • {failed} {author_info} ({" ,".join(commit_shas)}) ' - f'is authorized, but they must confirm their affiliation with their company. ' + f'
          • {failed} {author_info} ({" ,".join(commit_shas)}). ' + f'This user is authorized, but they must confirm their affiliation with their company. ' f'Start the authorization process ' f" by clicking here, click \"Corporate\"," f'select the appropriate company from the list, then confirm ' @@ -1056,15 +1059,19 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers_comment += ( f'
          • ' f"{failed} - " - f"{author_info} The commit ({' ,'.join(commit_shas)}) is not authorized under a signed CLA. " + f"{author_info}. The commit ({' ,'.join(commit_shas)}) " + "is not authorized under a signed CLA. " f"Please click here to be authorized. " f"For further assistance with EasyCLA, " f"please submit a support request ticket." "
          • ") - committers_comment += "
          " - return committers_comment - text = "The committers are authorized under a signed CLA." + if len(signed) > 0 or len(missing) > 0: + committers_comment += '
        ' + + if len(signed) > 0 and len(missing) == 0: + text = "The committers listed above are authorized under a signed CLA." + return text + committers_comment From 70abf60167a82e35f29490fc38fd660dcd96d9a8 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 2 Mar 2022 09:10:30 -0800 Subject: [PATCH 0602/1276] Fixed Git SHA Printout in GH (#3474) --- cla-backend/cla/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index da59db862..3f03d7389 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1029,7 +1029,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
      • {failed} The commit ({' ,'.join(commit_shas)}). " + f"
      • {failed} The commit ({', '.join(commit_shas)}). " f"This user is missing the User's ID, preventing the EasyCLA check. " f"Consult GitHub Help to resolve." f'For further assistance with EasyCLA, ' @@ -1044,7 +1044,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], if not user_commit_summary.affiliated] cla.log.info(f'{fn} SHAs for users with missing company affiliations: {commit_shas}') committers_comment += ( - f'
      • {failed} {author_info} ({" ,".join(commit_shas)}). ' + f'
      • {failed} {author_info} ({", ".join(commit_shas)}). ' f'This user is authorized, but they must confirm their affiliation with their company. ' f'Start the authorization process ' f" by clicking here, click \"Corporate\"," @@ -1059,7 +1059,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers_comment += ( f'
      • ' f"{failed} - " - f"{author_info}. The commit ({' ,'.join(commit_shas)}) " + f"{author_info}. The commit ({', '.join(commit_shas)}) " "is not authorized under a signed CLA. " f"Please click here to be authorized. " f"For further assistance with EasyCLA, " From c5a5462dcc883ba2dccabf8b81659226bbf0c25d Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 2 Mar 2022 10:56:24 -0800 Subject: [PATCH 0603/1276] GitHub Commit - Hide User Public Email (#3476) --- cla-backend/cla/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cla-backend/cla/user.py b/cla-backend/cla/user.py index 5cf69f371..8bced801d 100644 --- a/cla-backend/cla/user.py +++ b/cla-backend/cla/user.py @@ -84,8 +84,8 @@ def get_user_info(self) -> str: user_info += f'login: {self.author_login} / ' if self.author_name: user_info += f'name: {self.author_name} / ' - if self.author_email: - user_info += f'email: {self.author_email}' + # if self.author_email: + # user_info += f'email: {self.author_email}' pattern = r'/ $' return re.sub(pattern, '', user_info) From b516cb0c1ff53ba1eba754bef4c78caf02637e52 Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 15 Mar 2022 15:13:26 -0700 Subject: [PATCH 0604/1276] Added User Tag to GitHub Comment When Missing Authorization (#3480) --- .../tests/unit/test_user_commit_summary.py | 23 ++++++++++++++++++- cla-backend/cla/user.py | 4 ++-- cla-backend/cla/utils.py | 5 ++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/cla-backend/cla/tests/unit/test_user_commit_summary.py b/cla-backend/cla/tests/unit/test_user_commit_summary.py index eb823b927..75f759ed4 100644 --- a/cla-backend/cla/tests/unit/test_user_commit_summary.py +++ b/cla-backend/cla/tests/unit/test_user_commit_summary.py @@ -37,6 +37,27 @@ def test_user_commit_summary_get_comment_body(self) -> None: missing = [m] body = get_comment_body('github', 'https://foo.com', signed, missing) - print(f'body: {body}') self.assertTrue(':white_check_mark:' in body) self.assertTrue(':x:' in body) + + def test_user_commit_summary_tag_not_in_get_comment_body(self) -> None: + s1 = UserCommitSummary("abc1234xyz-123", 1234, 'login_value', 'author name', 'foo@bar.com', True, True) + s2 = UserCommitSummary("abc1234xyz-456", 1234, 'login_value', 'author name', 'foo@bar.com', True, True) + signed = [s1, s2] + + missing = [] + + body = get_comment_body('github', 'https://foo.com', signed, missing) + self.assertTrue(':white_check_mark:' in body) + self.assertTrue('login_value' in body) + self.assertFalse('@login_value' in body) # users should not be tagged in signed use case + + def test_user_commit_summary_tag_in_get_comment_body(self) -> None: + signed = [] + + m = UserCommitSummary("some_other_sha", 123456, 'login_value2', 'author name2', 'foo2@bar.com', False, False) + missing = [m] + + body = get_comment_body('github', 'https://foo.com', signed, missing) + self.assertTrue(':x:' in body) + self.assertTrue('@login_value2' in body) # users should be tagged in missing use case diff --git a/cla-backend/cla/user.py b/cla-backend/cla/user.py index 8bced801d..737332488 100644 --- a/cla-backend/cla/user.py +++ b/cla-backend/cla/user.py @@ -78,10 +78,10 @@ def __str__(self) -> str: def is_valid_user(self) -> bool: return self.author_id is not None and (self.author_login is not None or self.author_name is not None) - def get_user_info(self) -> str: + def get_user_info(self, tag_user:bool = False) -> str: user_info = '' if self.author_login: - user_info += f'login: {self.author_login} / ' + user_info += f'login: {"@" if tag_user else ""}{self.author_login} / ' if self.author_name: user_info += f'name: {self.author_name} / ' # if self.author_email: diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 3f03d7389..b15fe8a1b 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1011,8 +1011,9 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # Build a lookup table to group all the commits by author. committers = {} for user_commit_summary in missing: + tag_user = True if user_commit_summary.is_valid_user(): - author_info = user_commit_summary.get_user_info() + author_info = user_commit_summary.get_user_info(tag_user) else: author_info = 'Unknown' @@ -1047,7 +1048,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], f'
      • {failed} {author_info} ({", ".join(commit_shas)}). ' f'This user is authorized, but they must confirm their affiliation with their company. ' f'Start the authorization process ' - f" by clicking here, click \"Corporate\"," + f" by clicking here, click \"Corporate\", " f'select the appropriate company from the list, then confirm ' f'your affiliation on the page that appears. ' f'For further assistance with EasyCLA, ' From e3d864284d43816f0b869bd222dc7ebb0b4b63a7 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 16 Mar 2022 07:57:34 -0700 Subject: [PATCH 0605/1276] Added Additional User Tagging Logic for CLA Gating Failures (#3482) --- cla-backend/cla/models/github_models.py | 7 +++-- cla-backend/cla/user.py | 20 ++++--------- cla-backend/cla/utils.py | 40 ++++++++++++------------- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index 008d4f023..22f1f76da 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -1036,9 +1036,10 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep # knows if it is pass/fail. # Create check run for users that haven't yet signed and/or affiliated if missing: - text = "" + text = '' + help_url = '' for user_commit_summary in missing: - # Check for valid github id + # Check for valid GitHub id # old tuple: (sha, (author_id, author_login_or_name, author_email, optionalTrue)) if not user_commit_summary.is_valid_user(): help_url = "https://help.github.com/en/github/committing-changes-to-your-project/why-are-my-commits-linked-to-the-wrong-user" @@ -1051,7 +1052,7 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep if user_commit_summary.commit_sha != last_commit.sha: continue - text += user_commit_summary.get_display_text() + text += user_commit_summary.get_display_text(tag_user=True) payload = { "name": "CLA check", diff --git a/cla-backend/cla/user.py b/cla-backend/cla/user.py index 737332488..93ff2df50 100644 --- a/cla-backend/cla/user.py +++ b/cla-backend/cla/user.py @@ -78,35 +78,27 @@ def __str__(self) -> str: def is_valid_user(self) -> bool: return self.author_id is not None and (self.author_login is not None or self.author_name is not None) - def get_user_info(self, tag_user:bool = False) -> str: + def get_user_info(self, tag_user: bool = False) -> str: user_info = '' if self.author_login: user_info += f'login: {"@" if tag_user else ""}{self.author_login} / ' if self.author_name: user_info += f'name: {self.author_name} / ' - # if self.author_email: - # user_info += f'email: {self.author_email}' - pattern = r'/ $' - return re.sub(pattern, '', user_info) + return re.sub(r'/ $', '', user_info) - def get_display_text(self) -> str: + def get_display_text(self, tag_user: bool = False) -> str: if not self.author_id: return f'{self.author_email} is not linked to this commit.\n' - text = self.get_user_info() - if not self.is_valid_user(): return 'Invalid author details.\n' if self.authorized and self.affiliated: - text += ' is authorized.\n' - return text + return self.get_user_info(tag_user) + ' is authorized.\n' if self.affiliated: - text += ' is associated with a company, but not on an approval list.\n' + return self.get_user_info(tag_user) + ' is associated with a company, but not on an approval list.\n' else: - text += ' is not associated with a company.\n' - - return text + return self.get_user_info(tag_user) + ' is not associated with a company.\n' diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index b15fe8a1b..be37125c6 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -988,7 +988,7 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], committers = {} for user_commit_summary in signed: if user_commit_summary.is_valid_user(): - author_info = user_commit_summary.get_user_info() + author_info = user_commit_summary.get_user_info(tag_user=False) else: author_info = 'Unknown' @@ -1011,9 +1011,8 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # Build a lookup table to group all the commits by author. committers = {} for user_commit_summary in missing: - tag_user = True if user_commit_summary.is_valid_user(): - author_info = user_commit_summary.get_user_info(tag_user) + author_info = user_commit_summary.get_user_info(tag_user=True) else: author_info = 'Unknown' @@ -1030,16 +1029,17 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f"
      • {failed} The commit ({', '.join(commit_shas)}). " - f"This user is missing the User's ID, preventing the EasyCLA check. " - f"Consult GitHub Help to resolve." - f'For further assistance with EasyCLA, ' - f"please submit a support request ticket." - '
      • ') + f"
      • {failed} The commit ({', '.join(commit_shas)}). " + f"This user is missing the User's ID, preventing the EasyCLA check. " + f"Consult GitHub Help to resolve." + f'For further assistance with EasyCLA, ' + f"please submit a support request ticket." + '
      • ') else: - missing_affiliations = [user_commit_summary.affiliated and user_commit_summary.authorized for user_commit_summary in user_commit_summaries - if not user_commit_summary.affiliated] - if len(missing_affiliations) > 0 : + missing_affiliations = [user_commit_summary.affiliated and user_commit_summary.authorized for + user_commit_summary in user_commit_summaries + if not user_commit_summary.affiliated] + if len(missing_affiliations) > 0: # build a quick list of just the commit hash values for users missing company affiliations commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries if not user_commit_summary.affiliated] @@ -1058,14 +1058,14 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], # build a quick list of just the commit hash values commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries] committers_comment += ( - f'
      • ' - f"{failed} - " - f"{author_info}. The commit ({', '.join(commit_shas)}) " - "is not authorized under a signed CLA. " - f"Please click here to be authorized. " - f"For further assistance with EasyCLA, " - f"please submit a support request ticket." - "
      • ") + f'
      • ' + f"{failed} - " + f"{author_info}. The commit ({', '.join(commit_shas)}) " + "is not authorized under a signed CLA. " + f"Please click here to be authorized. " + f"For further assistance with EasyCLA, " + f"please submit a support request ticket." + "
      • ") if len(signed) > 0 or len(missing) > 0: committers_comment += '
      ' From d23b44709673238a65038f175c72cedcdaf1839f Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 16 Mar 2022 10:01:30 -0700 Subject: [PATCH 0606/1276] [3481] Updated Missing Affiliations Logic (#3483) --- cla-backend/cla/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index be37125c6..2f3349b13 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1036,9 +1036,8 @@ def get_comment_body(repository_type, sign_url, signed: List[UserCommitSummary], f"please submit a support request ticket." '') else: - missing_affiliations = [user_commit_summary.affiliated and user_commit_summary.authorized for - user_commit_summary in user_commit_summaries - if not user_commit_summary.affiliated] + missing_affiliations = [user_commit_summary for user_commit_summary in user_commit_summaries + if not user_commit_summary.affiliated and user_commit_summary.authorized] if len(missing_affiliations) > 0: # build a quick list of just the commit hash values for users missing company affiliations commit_shas = [user_commit_summary.commit_sha for user_commit_summary in user_commit_summaries From fad7a91651467f0420af7ce56d4f151197b3507e Mon Sep 17 00:00:00 2001 From: David Deal Date: Tue, 5 Apr 2022 12:47:47 -0700 Subject: [PATCH 0607/1276] Added Pagination to the Company Contriburtor Query (#3497) --- cla-backend-go/signatures/repository.go | 29 +++++--- cla-backend-go/signatures/service.go | 8 +- cla-backend-go/swagger/cla.v2.yaml | 2 + .../common/corporate-contributors-list.yaml | 8 ++ cla-backend-go/v2/company/handlers.go | 3 +- cla-backend-go/v2/company/service.go | 74 +++++++++++++------ cla-backend-go/v2/gitlab-activity/service.go | 3 +- 7 files changed, 89 insertions(+), 38 deletions(-) diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index 87329aa03..c1b9e1e8d 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -75,7 +75,7 @@ type SignatureRepository interface { CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) - GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, pageSize int64) (*models.Signatures, error) + GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) @@ -1489,7 +1489,7 @@ func (repo repository) InvalidateProjectRecord(ctx context.Context, signatureID, } // GetProjectCompanyEmployeeSignatures returns a list of employee signatures for the specified project and specified company -func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, pageSize int64) (*models.Signatures, error) { +func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) { f := logrus.Fields{ "functionName": "v1.signatures.repository.GetProjectCompanyEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), @@ -1497,9 +1497,14 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, "companyID": params.CompanyID, "nextKey": aws.StringValue(params.NextKey), "sortOrder": aws.StringValue(params.SortOrder), - "pageSize": aws.Int64Value(params.PageSize), } + pageSize := int64(HugePageSize) + if params.PageSize != nil { + pageSize = utils.Int64Value(params.PageSize) + } + f["pageSize"] = pageSize + // This is the keys we want to match condition := expression.Key("signature_user_ccla_company_id").Equal(expression.Value(params.CompanyID)).And( expression.Key("signature_project_id").Equal(expression.Value(params.ProjectID))) @@ -1526,6 +1531,8 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, filter = addAndCondition(filter, expression.Name("user_email").Equal(expression.Value(criteria.UserEmail)), &filterAdded) } + beforeQuery, _ := utils.CurrentTime() + log.WithFields(f).Debugf("running signature query on table: %s", repo.signatureTableName) // Use the nice builder to create the expression expr, err := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter).WithProjection(buildProjection()).Build() if err != nil { @@ -1543,6 +1550,7 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, ProjectionExpression: expr.Projection(), TableName: aws.String(repo.signatureTableName), IndexName: aws.String("signature-user-ccla-company-index"), // Name of a secondary index to scan + Limit: aws.Int64(pageSize), } // If we have the next key, set the exclusive start key value @@ -1569,7 +1577,6 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, // Loop until we have all the records for ok := true; ok; ok = lastEvaluatedKey != "" { // Make the DynamoDB Query API call - //log.WithFields(f).Debugf("Running signature project company query using queryInput: %+v", queryInput) results, errQuery := repo.dynamoDBClient.Query(queryInput) if errQuery != nil { log.WithFields(f).Warnf("error retrieving project company employee signature ID for project: %s with company: %s, error: %v", @@ -1585,7 +1592,7 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, return nil, modelErr } - // Add to the signatures response model to the list + // Add to the signature response model to the list sigs = append(sigs, signatureList...) // log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey["signature_id"]) @@ -1600,6 +1607,7 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, break } } + log.WithFields(f).Debugf("finished signature query on table: %s - duration: %+v", repo.signatureTableName, time.Since(beforeQuery)) // How many total records do we have - may not be up-to-date as this value is updated only periodically describeTableInput := &dynamodb.DescribeTableInput{ @@ -2092,7 +2100,6 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model log.WithFields(f).Debug("querying database for approval list details") approved, signed := true, true - pageSize := int64(10) // Get CCLA signature - For Approval List info cclaSignature, err := repo.GetCorporateSignature(ctx, projectID, companyID, &approved, &signed) @@ -2135,6 +2142,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model employeeSignatureParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ ProjectID: projectID, CompanyID: companyID, + PageSize: utils.Int64(10), } authUser := auth.User{ @@ -2215,7 +2223,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model UserEmail: email, } log.WithFields(f).Debugf("Updating signature records for email approval list: %+v ", params.RemoveEmailApprovalList) - signs, appErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) + signs, appErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria) if appErr != nil { log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", appErr) return @@ -2321,10 +2329,11 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model companyProjectParams := signatures.GetProjectCompanyEmployeeSignaturesParams{ CompanyID: approvalList.CompanyID, ProjectID: approvalList.ClaGroupID, + PageSize: utils.Int64(10), } criteria := ApprovalCriteria{} - eclas, eclaErr := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, &criteria, int64(10)) + eclas, eclaErr := repo.GetProjectCompanyEmployeeSignatures(ctx, companyProjectParams, &criteria) if eclaErr != nil { log.WithFields(f).Warnf("unable to get cclas for company: %s and project: %s ", approvalList.CompanyID, approvalList.ClaGroupID) } @@ -2389,7 +2398,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model GitHubUsername: ghUsername, } log.WithFields(f).Debugf("Updating signature records for github username apporval list: %+v ", params.RemoveGithubUsernameApprovalList) - signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) + signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria) if ghUserErr != nil { log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) return @@ -2541,7 +2550,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model GitlabUsername: gitLabUsername, } log.WithFields(f).Debugf("Updating signature records for gitlab username apporval list: %+v ", params.RemoveGitlabUsernameApprovalList) - signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria, pageSize) + signs, ghUserErr := repo.GetProjectCompanyEmployeeSignatures(ctx, employeeSignatureParams, criteria) if ghUserErr != nil { log.WithFields(f).Debugf("unable to get Company Employee signatures : %+v ", ghUserErr) return diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index 8daeea497..9246c8091 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -143,13 +143,11 @@ func (s service) GetProjectCompanySignatures(ctx context.Context, params signatu // GetProjectCompanyEmployeeSignatures returns the list of employee signatures associated with the specified project func (s service) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) { - const defaultPageSize int64 = 10 - var pageSize = defaultPageSize - if params.PageSize != nil { - pageSize = *params.PageSize + if params.PageSize == nil { + params.PageSize = utils.Int64(10) } - projectSignatures, err := s.repo.GetProjectCompanyEmployeeSignatures(ctx, params, criteria, pageSize) + projectSignatures, err := s.repo.GetProjectCompanyEmployeeSignatures(ctx, params, criteria) if err != nil { return nil, err } diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index a36e54150..3603f1a74 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -3757,6 +3757,8 @@ paths: - $ref: "#/parameters/path-companyID" - $ref: "#/parameters/path-projectSFID" - $ref: "#/parameters/searchTerm" + - $ref: '#/parameters/nextKey' + - $ref: '#/parameters/pageSize' responses: '200': description: 'Success' diff --git a/cla-backend-go/swagger/common/corporate-contributors-list.yaml b/cla-backend-go/swagger/common/corporate-contributors-list.yaml index 5212d7e3b..da34f8982 100644 --- a/cla-backend-go/swagger/common/corporate-contributors-list.yaml +++ b/cla-backend-go/swagger/common/corporate-contributors-list.yaml @@ -4,6 +4,14 @@ type: object title: corporate contributors list properties: + nextKey: + type: string + title: Next Key + description: the next key to provide on subsequent API calls to fetch the next page of records + resultCount: + type: integer + format: int64 + x-omitempty: false list: type: array items: diff --git a/cla-backend-go/v2/company/handlers.go b/cla-backend-go/v2/company/handlers.go index 408021e2b..93c269e79 100644 --- a/cla-backend-go/v2/company/handlers.go +++ b/cla-backend-go/v2/company/handlers.go @@ -275,7 +275,8 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo } log.WithFields(f).Debugf("querying for employee contributors...") - result, err := service.GetCompanyProjectContributors(ctx, params.ProjectSFID, params.CompanyID, utils.StringValue(params.SearchTerm)) + //result, err := service.GetCompanyProjectContributors(ctx, params.ProjectSFID, params.CompanyID, utils.StringValue(params.SearchTerm)) + result, err := service.GetCompanyProjectContributors(ctx, ¶ms) if err != nil { if companyErr, ok := err.(*utils.CompanyNotFound); ok { msg := fmt.Sprintf("Company not found with ID: %s", companyErr.CompanyID) diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index 7d958b663..ec1b02548 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -85,7 +85,8 @@ const ( type Service interface { GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyModel *models.Company, projectSFID string) (*models.CompanyClaManagers, error) GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) - GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) + //GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) + GetCompanyProjectContributors(ctx context.Context, params *v2Ops.GetCompanyProjectContributorsParams) (*models.CorporateContributorList, error) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) CreateCompany(ctx context.Context, params *v2Ops.CreateCompanyParams) (*models.CompanyOutput, error) CreateCompanyFromSFModel(ctx context.Context, orgModel *orgModels.Organization, authUser *auth.User) (*models.CompanyOutput, error) @@ -328,41 +329,52 @@ func (s *service) GetCompanyProjectActiveCLAs(ctx context.Context, companyID str return &out, nil } -func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID string, companyID string, searchTerm string) (*models.CorporateContributorList, error) { +// GetCompanyProjectContributors by the specified parameters which include the project SFID, company ID and any additional search terms with pagination details +func (s *service) GetCompanyProjectContributors(ctx context.Context, params *v2Ops.GetCompanyProjectContributorsParams) (*models.CorporateContributorList, error) { f := logrus.Fields{ "functionName": "v2.company.service.GetCompanyProjectContributors", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectSFID": projectSFID, - "companyID": companyID, - "searchTerm": searchTerm, + "projectSFID": params.ProjectSFID, + "companyID": params.CompanyID, + } + if params.SearchTerm != nil { + f["searchTerm"] = utils.StringValue(params.SearchTerm) + } + if params.PageSize != nil { + f["pageSize"] = utils.Int64Value(params.PageSize) + } + if params.NextKey != nil { + f["nextKey"] = utils.StringValue(params.NextKey) } list := make([]*models.CorporateContributor, 0) log.WithFields(f).Debugf("querying for employee contributors...") - sigs, err := s.getAllCompanyProjectEmployeeSignatures(ctx, companyID, projectSFID) + sigResponse, err := s.getAllCompanyProjectEmployeeSignatures(ctx, params) if err != nil { log.WithFields(f).WithError(err).Warn("problem fetching all company project employee signatures") return nil, err } - if len(sigs) == 0 { + if len(sigResponse.Signatures) == 0 { log.WithFields(f).Debug("not signatures found - returning emtpy list") return &models.CorporateContributorList{ List: list, }, nil } - log.WithFields(f).Debugf("found %d signatures", len(sigs)) + log.WithFields(f).Debugf("found %d signatures", len(sigResponse.Signatures)) + beforeQuery, _ := utils.CurrentTime() var wg sync.WaitGroup result := make(chan *models.CorporateContributor) - wg.Add(len(sigs)) + wg.Add(len(sigResponse.Signatures)) go func() { wg.Wait() + log.WithFields(f).Debugf("done additional corporate contributor details for %d signatures...duration: %+v", len(sigResponse.Signatures), time.Since(beforeQuery)) close(result) }() - log.WithFields(f).Debugf("adding additional corporate contributor details for %d signatures...", len(sigs)) - for _, sig := range sigs { - go fillCorporateContributorModel(&wg, s.userRepo, sig, result, searchTerm) + log.WithFields(f).Debugf("adding additional corporate contributor details for %d signatures...", len(sigResponse.Signatures)) + for _, sig := range sigResponse.Signatures { + go fillCorporateContributorModel(&wg, s.userRepo, sig, result, utils.StringValue(params.SearchTerm)) } for corpContributor := range result { @@ -370,7 +382,9 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, projectSFID } return &models.CorporateContributorList{ - List: list, + List: list, + NextKey: sigResponse.LastKeyScanned, + ResultCount: sigResponse.ResultCount, }, nil } @@ -1541,28 +1555,46 @@ func fillCorporateContributorModel(wg *sync.WaitGroup, usersRepo users.UserRepos result <- &contributor } -func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, companyID string, projectSFID string) ([]*v1Models.Signature, error) { +func (s *service) getAllCompanyProjectEmployeeSignatures(ctx context.Context, params *v2Ops.GetCompanyProjectContributorsParams) (*v1Models.Signatures, error) { f := logrus.Fields{ "functionName": "v2.company.service.getAllCompanyProjectEmployeeSignatures", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "companyID": companyID, - "projectSFID": projectSFID, + "projectSFID": params.ProjectSFID, + "companyID": params.CompanyID, + } + if params.SearchTerm != nil { + f["searchTerm"] = utils.StringValue(params.SearchTerm) + } + if params.PageSize != nil { + f["pageSize"] = utils.Int64Value(params.PageSize) } + if params.NextKey != nil { + f["nextKey"] = utils.StringValue(params.NextKey) + } + log.WithFields(f).Debug("querying company and project...") - _, claGroup, err := s.getCompanyAndClaGroup(ctx, companyID, projectSFID) + _, claGroup, err := s.getCompanyAndClaGroup(ctx, params.CompanyID, params.ProjectSFID) if err != nil { return nil, err } - params := v1SignatureParams.GetProjectCompanyEmployeeSignaturesParams{ + queryParams := v1SignatureParams.GetProjectCompanyEmployeeSignaturesParams{ HTTPRequest: nil, - CompanyID: companyID, + CompanyID: params.CompanyID, ProjectID: claGroup.ProjectID, } - sigs, err := s.signatureRepo.GetProjectCompanyEmployeeSignatures(ctx, params, nil, HugePageSize) + // Pass along any query parameters from the caller + if params.PageSize != nil { + queryParams.PageSize = params.PageSize + } + if params.NextKey != nil { + queryParams.NextKey = params.NextKey + } + + sigs, err := s.signatureRepo.GetProjectCompanyEmployeeSignatures(ctx, queryParams, nil) if err != nil { return nil, err } - return sigs.Signatures, nil + return sigs, nil } // get company and project in parallel diff --git a/cla-backend-go/v2/gitlab-activity/service.go b/cla-backend-go/v2/gitlab-activity/service.go index ba3855f5e..4d0ebcd38 100644 --- a/cla-backend-go/v2/gitlab-activity/service.go +++ b/cla-backend-go/v2/gitlab-activity/service.go @@ -416,7 +416,8 @@ func (s service) hasUserSigned(ctx context.Context, claGroupID string, gitlabUse employeeSignatures, err := s.signaturesRepository.GetProjectCompanyEmployeeSignatures(ctx, signatures1.GetProjectCompanyEmployeeSignaturesParams{ CompanyID: companyID, ProjectID: claGroupID, - }, approvalCriteria, 100) + PageSize: utils.Int64(100), + }, approvalCriteria) if err != nil { msg := fmt.Sprintf("can't load employee signature records : %s for user : %s association : %v", companyID, userModel.UserID, err) From 0f6b4b7590326f19d2f9372ddf6bb19fa0629427 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 20:32:32 -0700 Subject: [PATCH 0608/1276] Bump moment in /cla-frontend-contributor-console/src (#3506) Bumps [moment](https://github.com/moment/moment) from 2.24.0 to 2.29.2. - [Release notes](https://github.com/moment/moment/releases) - [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) - [Commits](https://github.com/moment/moment/compare/2.24.0...2.29.2) --- updated-dependencies: - dependency-name: moment dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index 7359874d0..b7e1d3431 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -2495,8 +2495,9 @@ mixin-deep@^1.2.0: minimist "0.0.8" moment@^2.10.2: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + version "2.29.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" + integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== ms@2.0.0: version "2.0.0" From e7e6abcebe6ffba9b002ba92fa0a7909745473c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 16:13:32 -0700 Subject: [PATCH 0609/1276] Bump async from 2.6.2 to 2.6.4 in /cla-frontend-contributor-console/src (#3516) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/src/yarn.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cla-frontend-contributor-console/src/yarn.lock b/cla-frontend-contributor-console/src/yarn.lock index b7e1d3431..ddfc27a5a 100644 --- a/cla-frontend-contributor-console/src/yarn.lock +++ b/cla-frontend-contributor-console/src/yarn.lock @@ -369,10 +369,11 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" async@^2.1.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: - lodash "^4.17.11" + lodash "^4.17.14" asynckit@^0.4.0: version "0.4.0" @@ -2280,7 +2281,7 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@>=4.17.19, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.15, lodash@~4.17.10: +lodash@>=4.17.19, lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.15, lodash@~4.17.10: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== From a46f2ea6aaf0619c7288f68674860688060bc348 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 19:08:03 -0700 Subject: [PATCH 0610/1276] Bump async from 2.6.3 to 2.6.4 in /cla-landing-page (#3522) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-landing-page/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 671226d1d..f1d210cc4 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -2390,9 +2390,9 @@ async-each@^1.0.1: integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async@^2.5.0, async@^2.6.1, async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" From 2ffb7fb8d31aa4c865bb3773d38c2d27c706c75b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 19:52:26 -0700 Subject: [PATCH 0611/1276] Bump async from 2.6.3 to 2.6.4 in /cla-frontend-contributor-console (#3523) Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4. - [Release notes](https://github.com/caolan/async/releases) - [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md) - [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4) --- updated-dependencies: - dependency-name: async dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-contributor-console/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cla-frontend-contributor-console/yarn.lock b/cla-frontend-contributor-console/yarn.lock index 9272ea9b9..86c6226f4 100644 --- a/cla-frontend-contributor-console/yarn.lock +++ b/cla-frontend-contributor-console/yarn.lock @@ -1014,9 +1014,9 @@ async-each@^1.0.0: integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async@^2.0.0, async@^2.6.1, async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" From 44a9544072bddf5654d1e6c2a3145eaea4ca0929 Mon Sep 17 00:00:00 2001 From: David Deal Date: Thu, 2 Jun 2022 14:03:08 -0700 Subject: [PATCH 0612/1276] Added TotalCount for Company Contributors Response (#3531) --- cla-backend-go/Makefile | 50 +++++++++++-------- cla-backend-go/cmd/server.go | 4 +- cla-backend-go/go.mod | 2 +- cla-backend-go/go.sum | 2 + .../common/corporate-contributors-list.yaml | 4 ++ cla-backend-go/v2/company/service.go | 25 ++++++---- 6 files changed, 53 insertions(+), 34 deletions(-) diff --git a/cla-backend-go/Makefile b/cla-backend-go/Makefile index 11249b9f9..7e14b436f 100644 --- a/cla-backend-go/Makefile +++ b/cla-backend-go/Makefile @@ -20,13 +20,24 @@ COMMIT := $(shell sh -c 'git rev-parse --short HEAD') LDFLAGS=-ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.branch=$(BRANCH) -X main.buildDate=$(BUILD_TIME)" BUILD_TAGS=-tags aws_lambda +ifeq "$(shell uname -s)" "Darwin" + BUILD_HOST=darwin +endif +ifeq "$(shell uname -s)" "Linux" + BUILD_HOST=linux +endif + LINT_TOOL=$(shell go env GOPATH)/bin/golangci-lint -LINT_VERSION=v1.41.1 +LINT_VERSION=v1.46.2 +SWAGGER_DIR=$(ROOT_DIR)/swagger +SWAGGER_BIN_DIR=/usr/local/bin SWAGGER_TOOL_VERSION=v0.24.0 +SWAGGER_ASSET="swagger_$(BUILD_HOST)_amd64" +SWAGGER_ASSET_URL="https://github.com/go-swagger/go-swagger/releases/download/$(SWAGGER_TOOL_VERSION)/$(SWAGGER_ASSET)" GO_PKGS=$(shell go list ./... | grep -v /vendor/ | grep -v /node_modules/) GO_FILES=$(shell find . -type f -name '*.go' -not -path './vendor/*') -.PHONY: generate setup tool-setup setup-dev setup-deploy clean-all clean swagger up fmt test run deps build build-mac build-aws-lambda user-subscribe-lambda qc lint +.PHONY: generate setup setup-dev setup-deploy clean-all clean swagger up fmt test run deps build build-mac build-aws-lambda user-subscribe-lambda qc lint all: all-mac all-mac: clean swagger deps fmt build-mac build-aws-lambda-mac build-user-subscribe-lambda-mac build-metrics-lambda-mac build-dynamo-events-lambda-mac build-zipbuilder-scheduler-lambda-mac build-zipbuilder-lambda-mac build-gitlab-repository-check-lambda-mac test lint @@ -40,27 +51,26 @@ generate: swagger setup: $(LINT_TOOL) setup-dev setup-deploy -tool-setup: - @echo "Installing gobin for installing tools..." - @# gobin is the equivalent of 'go get' whilst in module-aware mode but this does not modify your go.mod - #GO111MODULE=off go get -u github.com/myitcv/gobin - go get -u github.com/myitcv/gobin +.PHONY: swagger-tool +swagger-tool: + @echo "Removing old swagger binary in $(SWAGGER_BIN_DIR)..." + @sudo rm -Rf $(SWAGGER_BIN_DIR)/swagger + @echo "Downloading $(SWAGGER_ASSET_URL)" + sudo curl -o $(SWAGGER_BIN_DIR)/swagger -L'#' $(SWAGGER_ASSET_URL) + sudo chmod +x $(SWAGGER_BIN_DIR)/swagger + $(SWAGGER_BIN_DIR)/swagger version setup_dev: setup-dev -setup-dev: tool-setup - @echo "Removing previously install version of swagger..." - @rm -Rf $(shell echo $(GOPATH))/bin/swagger $(shell echo $(GOPATH))/src/github.com/go-swagger - @echo "Installing swagger version: '$(SWAGGER_TOOL_VERSION)'..." - gobin github.com/go-swagger/go-swagger/cmd/swagger@$(SWAGGER_TOOL_VERSION) - @echo "Installing goimports..." - gobin golang.org/x/tools/cmd/goimports - @echo "Installing cover..." - gobin golang.org/x/tools/cmd/cover - @echo "Installing multi-file-swagger tool..." - @echo "Downloading golangci-lint version $(LINT_VERSION)..." +setup-dev: swagger-tool + pushd /tmp && echo "Installing goimport..." && go install golang.org/x/tools/cmd/goimports@latest && echo "Installation coverage tools..." && go install golang.org/x/tools/cmd/cover@latest && popd + + @echo "Installing linter..." @# Latest releases: https://github.com/golangci/golangci-lint/releases - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin $(LINT_VERSION) - cd $(dir $(realpath $(firstword $(MAKEFILE_LIST))))swagger && pip3 install virtualenv && virtualenv .venv && source .venv/bin/activate && pip3 install -r requirements.txt + go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(LINT_VERSION) + echo "golangci-lint version:" && golangci-lint version + + @echo "Installing multi-file-swagger tool..." + cd $(dir $(realpath $(firstword $(MAKEFILE_LIST))))swagger && pip3 install virtualenv && virtualenv .venv && source .venv/bin/activate && python -m pip install --upgrade pip && pip3 install -r requirements.txt setup_deploy: setup-deploy setup-deploy: diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 44b370871..cd98fdab4 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -634,7 +634,7 @@ func createUserFromRequest(authorizer auth.Authorizer, usersService users.Servic // search if user exist in database by username userModel, err := usersService.GetUserByLFUserName(claUser.LFUsername) if err != nil { - if err, ok := err.(*utils.UserNotFound); ok { + if _, ok := err.(*utils.UserNotFound); ok { log.WithFields(f).Debug("unable to locate user by lf-email") } else { log.WithFields(f).WithError(err).Warn("searching user by lf-username failed") @@ -649,7 +649,7 @@ func createUserFromRequest(authorizer auth.Authorizer, usersService users.Servic // search if user exist in database by username userModel, err = usersService.GetUserByEmail(claUser.LFEmail) if err != nil { - if err, ok := err.(*utils.UserNotFound); ok { + if _, ok := err.(*utils.UserNotFound); ok { log.WithFields(f).Debug("unable to locate user by lf-email") } else { log.WithFields(f).WithError(err).Warn("searching user by lf-email failed") diff --git a/cla-backend-go/go.mod b/cla-backend-go/go.mod index 6c0c82a8f..798c05a01 100644 --- a/cla-backend-go/go.mod +++ b/cla-backend-go/go.mod @@ -103,7 +103,7 @@ require ( github.com/pelletier/go-toml v1.9.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/cla-backend-go/go.sum b/cla-backend-go/go.sum index d9ff15622..cce8397c0 100644 --- a/cla-backend-go/go.sum +++ b/cla-backend-go/go.sum @@ -598,6 +598,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= diff --git a/cla-backend-go/swagger/common/corporate-contributors-list.yaml b/cla-backend-go/swagger/common/corporate-contributors-list.yaml index da34f8982..93d0e2e79 100644 --- a/cla-backend-go/swagger/common/corporate-contributors-list.yaml +++ b/cla-backend-go/swagger/common/corporate-contributors-list.yaml @@ -12,6 +12,10 @@ properties: type: integer format: int64 x-omitempty: false + totalCount: + type: integer + format: int64 + x-omitempty: false list: type: array items: diff --git a/cla-backend-go/v2/company/service.go b/cla-backend-go/v2/company/service.go index ec1b02548..8970a7678 100644 --- a/cla-backend-go/v2/company/service.go +++ b/cla-backend-go/v2/company/service.go @@ -71,12 +71,14 @@ var ( // constants const ( - // used when we want to query all data from dependent service. + // HugePageSize is used when we want to query all data from dependent service HugePageSize = int64(10000) - // LoadRepoDetails = true + DontLoadRepoDetails = false - //NoAccount + + // NoAccount constant NoAccount = "Individual - No Account" + //OrgAssociated stating whether user has user association with another org OrgAssociated = "are already associated with other organization" ) @@ -85,7 +87,6 @@ const ( type Service interface { GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyModel *models.Company, projectSFID string) (*models.CompanyClaManagers, error) GetCompanyProjectActiveCLAs(ctx context.Context, companyID string, projectSFID string) (*models.ActiveClaList, error) - //GetCompanyProjectContributors(ctx context.Context, projectSFID string, companySFID string, searchTerm string) (*models.CorporateContributorList, error) GetCompanyProjectContributors(ctx context.Context, params *v2Ops.GetCompanyProjectContributorsParams) (*models.CorporateContributorList, error) GetCompanyProjectCLA(ctx context.Context, authUser *auth.User, companySFID, projectSFID string, companyID *string) (*models.CompanyProjectClaList, error) CreateCompany(ctx context.Context, params *v2Ops.CreateCompanyParams) (*models.CompanyOutput, error) @@ -102,7 +103,7 @@ type Service interface { GetCompanyAdmins(ctx context.Context, companyID string) (*models.CompanyAdminList, error) RequestCompanyAdmin(ctx context.Context, userID string, claManagerEmail string, claManagerName string, contributorName string, contributorEmail string, projectName string, companyName string, lFxPortalURL string) error - // org service lookup + // GetCompanyLookup uses the org service to lookup the value GetCompanyLookup(ctx context.Context, companyName string, websiteName string) (*models.Lookup, error) } @@ -137,7 +138,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod "signingEntityName": v1CompanyModel.SigningEntityName, } - // TODO: DAD - separate go routine + // TODO: DAD - consider using separate go routine log.WithFields(f).Debugf("locating CLA Group(s) under project or foundation...") var err error claGroups, err := s.getCLAGroupsUnderProjectOrFoundation(ctx, projectSFID) @@ -146,7 +147,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod return nil, err } - // TODO: DAD - separate go routine + // TODO: DAD - consider using separate go routine // get the org client for org info filling orgClient := orgService.GetClient() orgModel, err := orgClient.GetOrganization(ctx, v1CompanyModel.CompanyExternalID) @@ -154,7 +155,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod return nil, fmt.Errorf("fetching org model failed for companySFID : %s : %w", v1CompanyModel.CompanyExternalID, err) } - // TODO: DAD - separate go routine + // TODO: DAD - consider using separate go routine signed, approved := true, true maxLoad := int64(10) var sigs []*v1Models.Signature @@ -202,7 +203,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod return &models.CompanyClaManagers{List: claManagers}, nil } - // TODO: DAD - separate go routine + // TODO: DAD - consider using separate go routine // get userinfo and project info var usermap map[string]*v2UserServiceModels.User usermap, err = getUsersInfo(lfUsernames.List()) @@ -216,7 +217,7 @@ func (s *service) GetCompanyProjectCLAManagers(ctx context.Context, v1CompanyMod // fill project info fillProjectInfo(claManagers, claGroups) - // TODO: DAD - separate go routine + // TODO: DAD - consider using separate go routine // fetch the cla_manager.added events so can fill the addedOn field claManagerAddedEvents, err := s.eventService.GetCompanyEvents(v1CompanyModel.CompanyID, events.ClaManagerCreated, nil, aws.Int64(100), true) if err != nil { @@ -337,6 +338,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, params *v2O "projectSFID": params.ProjectSFID, "companyID": params.CompanyID, } + if params.SearchTerm != nil { f["searchTerm"] = utils.StringValue(params.SearchTerm) } @@ -360,7 +362,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, params *v2O List: list, }, nil } - log.WithFields(f).Debugf("found %d signatures", len(sigResponse.Signatures)) + log.WithFields(f).Debugf("found %d signatures matching filter critiera - total in database is: %d", len(sigResponse.Signatures), sigResponse.TotalCount) beforeQuery, _ := utils.CurrentTime() var wg sync.WaitGroup @@ -385,6 +387,7 @@ func (s *service) GetCompanyProjectContributors(ctx context.Context, params *v2O List: list, NextKey: sigResponse.LastKeyScanned, ResultCount: sigResponse.ResultCount, + TotalCount: sigResponse.TotalCount, }, nil } From ee19e42fe2cf2160d7bd352e2de79ceb865ec055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jun 2022 21:03:52 +0000 Subject: [PATCH 0613/1276] Bump pyjwt from 1.7.1 to 2.4.0 in /tests/rest Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 1.7.1 to 2.4.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/1.7.1...2.4.0) --- updated-dependencies: - dependency-name: pyjwt dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tests/rest/requirements.freeze.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rest/requirements.freeze.txt b/tests/rest/requirements.freeze.txt index 1032b7a10..69e9b9afe 100644 --- a/tests/rest/requirements.freeze.txt +++ b/tests/rest/requirements.freeze.txt @@ -17,7 +17,7 @@ paho-mqtt==1.3.1 pbr==5.4.3 pluggy==0.13.0 py==1.8.0 -PyJWT==1.7.1 +PyJWT==2.4.0 pykwalify==1.7.0 pyparsing==2.4.2 pytest==4.5.0 From c4db878f7eea350657fd0b815e17d9b372fcad9a Mon Sep 17 00:00:00 2001 From: David Deal Date: Fri, 3 Jun 2022 11:32:32 -0700 Subject: [PATCH 0614/1276] Updated Total Count Logic (#3536) --- cla-backend-go/signatures/projections.go | 8 ++ cla-backend-go/signatures/repository.go | 107 ++++++++++++++++++++--- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/cla-backend-go/signatures/projections.go b/cla-backend-go/signatures/projections.go index de7b0e1e5..1692c5265 100644 --- a/cla-backend-go/signatures/projections.go +++ b/cla-backend-go/signatures/projections.go @@ -42,6 +42,14 @@ func buildProjection() expression.ProjectionBuilder { ) } +// buildCountProject is a helper function to build a simple count projection for the total count query +func buildCountProjection() expression.ProjectionBuilder { + // These are the columns we want returned - we only care about the signature_id + return expression.NamesList( + expression.Name("signature_id"), + ) +} + // buildSignatureACLProject is a helper function to build a signature ACL response/projection func buildSignatureACLProjection() expression.ProjectionBuilder { // These are the columns we want returned diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index c1b9e1e8d..21e522708 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -76,6 +76,7 @@ type SignatureRepository interface { GetProjectCompanySignature(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, pageSize *int64) (*models.Signature, error) GetProjectCompanySignatures(ctx context.Context, companyID, projectID string, approved, signed *bool, nextKey *string, sortOrder *string, pageSize *int64) (*models.Signatures, error) GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error) + getProjectCompanyEmployeeSignatureCount(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, responseChannel chan int64) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) @@ -1499,6 +1500,9 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, "sortOrder": aws.StringValue(params.SortOrder), } + totalCountChannel := make(chan int64, 1) + go repo.getProjectCompanyEmployeeSignatureCount(ctx, params, criteria, totalCountChannel) + pageSize := int64(HugePageSize) if params.PageSize != nil { pageSize = utils.Int64Value(params.PageSize) @@ -1595,7 +1599,6 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, // Add to the signature response model to the list sigs = append(sigs, signatureList...) - // log.WithFields(f).Debugf("LastEvaluatedKey: %+v", results.LastEvaluatedKey["signature_id"]) if results.LastEvaluatedKey["signature_id"] != nil { lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S queryInput.ExclusiveStartKey = results.LastEvaluatedKey @@ -1609,23 +1612,13 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, } log.WithFields(f).Debugf("finished signature query on table: %s - duration: %+v", repo.signatureTableName, time.Since(beforeQuery)) - // How many total records do we have - may not be up-to-date as this value is updated only periodically - describeTableInput := &dynamodb.DescribeTableInput{ - TableName: &repo.signatureTableName, - } - describeTableResult, err := repo.dynamoDBClient.DescribeTable(describeTableInput) - if err != nil { - log.WithFields(f).Warnf("error retrieving total record count for project: %s, error: %v", params.ProjectID, err) - return nil, err - } - // Meta-data for the response - totalCount := *describeTableResult.Table.ItemCount if int64(len(sigs)) > pageSize { sigs = sigs[0:pageSize] lastEvaluatedKey = sigs[pageSize-1].SignatureID } + totalCount := <-totalCountChannel return &models.Signatures{ ProjectID: params.ProjectID, ResultCount: int64(len(sigs)), @@ -1635,6 +1628,96 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context, }, nil } +// getProjectCompanyEmployeeSignatureCount returns the total count of employee signatures for the specified project and specified company +func (repo repository) getProjectCompanyEmployeeSignatureCount(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria, responseChannel chan int64) { + f := logrus.Fields{ + "functionName": "v1.signatures.repository.getProjectCompanyEmployeeSignatureCount", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "projectID": params.ProjectID, + "companyID": params.CompanyID, + "nextKey": aws.StringValue(params.NextKey), + "sortOrder": aws.StringValue(params.SortOrder), + } + + // Ignore the provided page count in the parameters - we're focused on getting the total count + pageSize := int64(HugePageSize) + f["pageSize"] = pageSize + + // This is the keys we want to match + condition := expression.Key("signature_user_ccla_company_id").Equal(expression.Value(params.CompanyID)).And( + expression.Key("signature_project_id").Equal(expression.Value(params.ProjectID))) + + var filterAdded bool + var filter expression.ConditionBuilder + + // Check for approved signatures + filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded) + filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded) + + if criteria != nil && criteria.GitHubUsername != "" { + filter = addAndCondition(filter, expression.Name(SignatureUserGitHubUsername).Equal(expression.Value(criteria.GitHubUsername)), &filterAdded) + } + + if criteria != nil && criteria.GitHubUsername != "" { + filter = addAndCondition(filter, expression.Name(SignatureUserGitlabUsername).Equal(expression.Value(criteria.GitlabUsername)), &filterAdded) + } + + if criteria != nil && criteria.UserEmail != "" { + filter = addAndCondition(filter, expression.Name("user_email").Equal(expression.Value(criteria.UserEmail)), &filterAdded) + } + + beforeQuery, _ := utils.CurrentTime() + log.WithFields(f).Debugf("running total signature count query on table: %s", repo.signatureTableName) + // Use the nice builder to create the expression + expr, err := expression.NewBuilder().WithKeyCondition(condition).WithFilter(filter).WithProjection(buildCountProjection()).Build() + if err != nil { + log.WithFields(f).Warnf("error building expression for project signature ID query, project: %s, error: %v", + params.ProjectID, err) + responseChannel <- 0 + return + } + + // Assemble the query input parameters - ignore the provided exclusive start key, we're only interested in the total count + queryInput := &dynamodb.QueryInput{ + ExpressionAttributeNames: expr.Names(), + ExpressionAttributeValues: expr.Values(), + KeyConditionExpression: expr.KeyCondition(), + FilterExpression: expr.Filter(), + ProjectionExpression: expr.Projection(), + TableName: aws.String(repo.signatureTableName), + IndexName: aws.String("signature-user-ccla-company-index"), // Name of a secondary index to scan + Limit: aws.Int64(pageSize), + } + + var lastEvaluatedKey string + var totalCount int64 + + // Loop until we have all the records + for ok := true; ok; ok = lastEvaluatedKey != "" { + // Make the DynamoDB Query API call + results, errQuery := repo.dynamoDBClient.Query(queryInput) + if errQuery != nil { + log.WithFields(f).Warnf("error retrieving project company employee signature ID for project: %s with company: %s, error: %v", + params.ProjectID, params.CompanyID, errQuery) + responseChannel <- 0 + return + } + + // Add to our total count + totalCount += *results.Count + + if results.LastEvaluatedKey["signature_id"] != nil { + lastEvaluatedKey = *results.LastEvaluatedKey["signature_id"].S + queryInput.ExclusiveStartKey = results.LastEvaluatedKey + } else { + lastEvaluatedKey = "" + } + } + log.WithFields(f).Debugf("finished signature total count query on table: %s - duration: %+v", repo.signatureTableName, time.Since(beforeQuery)) + + responseChannel <- totalCount +} + // GetCompanySignatures returns a list of company signatures for the specified company func (repo repository) GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error) { f := logrus.Fields{ From 8c9083b1a9ec46ba0ee97475ca496d33f7db046a Mon Sep 17 00:00:00 2001 From: pranab-bajpai <57362064+pranab-bajpai@users.noreply.github.com> Date: Tue, 14 Jun 2022 09:24:56 -0500 Subject: [PATCH 0615/1276] [Snyk] Security upgrade serverless from 2.57.0 to 2.58.0 (#3525) Co-authored-by: snyk-bot --- cla-backend/package.json | 2 +- cla-backend/yarn.lock | 532 +++++++++++++++++++++++++++------------ 2 files changed, 378 insertions(+), 156 deletions(-) diff --git a/cla-backend/package.json b/cla-backend/package.json index 850defbe3..2a01cd458 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -33,7 +33,7 @@ "install": "^0.13.0", "node.extend": "^2.0.2", "request": "^2.88.0", - "serverless": "^2.57.0", + "serverless": "^2.58.0", "serverless-domain-manager": "^5.1.0", "serverless-finch": "^2.6.0", "serverless-layers": "^2.5.1", diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 953b368e7..c6bb41410 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -118,10 +118,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@serverless/cli@^1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.5.2.tgz#7741d84ea8b5f6dcf18e21406300f01ece2865da" - integrity sha512-FMACx0qPD6Uj8U+7jDmAxEe1tdF9DsuY5VsG45nvZ3olC9xYJe/PMwxWsjXfK3tg1HUNywYAGCsy7p5fdXhNzw== +"@serverless/cli@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@serverless/cli/-/cli-1.6.0.tgz#d020f67748401ca209a4fc6407929581810573e2" + integrity sha512-1Muw/KhS4sZ6+ZrXBdmVY9zAwZh03lF7v1DKtaZ0cmxjqQBwPLoO40rOGXlxR97pyufe2NS6rD/p+ri8NGqeXg== dependencies: "@serverless/core" "^1.1.2" "@serverless/template" "^1.1.3" @@ -133,7 +133,7 @@ figures "^3.2.0" minimist "^1.2.5" prettyoutput "^1.2.0" - strip-ansi "^5.2.0" + strip-ansi "^6.0.1" "@serverless/component-metrics@^1.0.8": version "1.0.8" @@ -143,10 +143,10 @@ node-fetch "^2.6.0" shortid "^2.2.14" -"@serverless/components@^3.16.0": - version "3.16.0" - resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.16.0.tgz#a161481f73ffa9e1d62c447b3d8b6bacbf701ba6" - integrity sha512-vgsfR0V4dierB97GKgtfFp/s5XN3zmEQlhRfshWLHjoXvJe2HvWOZXy5Bqyzb1WMr6S0dp/wXQzcnVStbiyBaw== +"@serverless/components@^3.18.2": + version "3.18.2" + resolved "https://registry.yarnpkg.com/@serverless/components/-/components-3.18.2.tgz#628d78f96c1ce5f8dc836587f566a404a38d65e7" + integrity sha512-jQSgd3unajU94R6vjzD0l+PS5lVcky0vrE1DOfb28VPgmaS48+I/niavWR7+SOt0mYjIkUlwBI73a2ZuqeYK6Q== dependencies: "@serverless/platform-client" "^4.2.2" "@serverless/platform-client-china" "^2.2.0" @@ -189,18 +189,18 @@ ramda "^0.26.1" semver "^6.1.1" -"@serverless/dashboard-plugin@^5.4.4": - version "5.4.4" - resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.4.4.tgz#787d0214175f520776c46ae0ca38bf7a981f8d54" - integrity sha512-1lLChYK/zwrF5SEAubVr9Oz/xGnq1Yjbw36X1iz0j/+jwbtpt1AeumksArA3UVAgDSecVOfaksxUta2cc10pRA== +"@serverless/dashboard-plugin@^5.5.4": + version "5.5.4" + resolved "https://registry.yarnpkg.com/@serverless/dashboard-plugin/-/dashboard-plugin-5.5.4.tgz#2bbe1d5e2e5fa79b0f95422fd5b7558c248ce689" + integrity sha512-qqZnT/RXhBcWXwYnpF+GMeNvSUi6mnyIqsAHj8+f7jJ7N9qa5Qrb14+dUh78sdgRBG5Peub3m3Mlx1n5V9yHDw== dependencies: "@serverless/event-mocks" "^1.1.1" "@serverless/platform-client" "^4.3.0" - "@serverless/utils" "^5.7.0" + "@serverless/utils" "^5.20.3" chalk "^4.1.2" child-process-ext "^2.1.1" - chokidar "^3.5.2" - cli-color "^2.0.0" + chokidar "^3.5.3" + cli-color "^2.0.1" flat "^5.0.2" fs-extra "^9.1.0" js-yaml "^4.1.0" @@ -209,10 +209,10 @@ memoizee "^0.4.15" ncjsm "^4.2.0" node-dir "^0.1.17" - node-fetch "^2.6.1" + node-fetch "^2.6.7" open "^7.4.2" semver "^7.3.5" - simple-git "^2.44.0" + simple-git "^2.48.0" uuid "^8.3.2" yamljs "^0.3.0" @@ -266,6 +266,27 @@ traverse "^0.6.6" ws "^7.5.3" +"@serverless/platform-client@^4.3.1": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@serverless/platform-client/-/platform-client-4.3.2.tgz#10cd3ad8cf452a33528cfb14bbb6003d30a74805" + integrity sha512-DAa5Z0JAZc6UfrTZLYwqoZxgAponZpFwaqd7WzzMA+loMCkYWyJNwxrAmV6cr2UUJpkko4toPZuJ3vM9Ie+NDA== + dependencies: + adm-zip "^0.5.5" + archiver "^5.3.0" + axios "^0.21.1" + fast-glob "^3.2.7" + https-proxy-agent "^5.0.0" + ignore "^5.1.8" + isomorphic-ws "^4.0.1" + js-yaml "^3.14.1" + jwt-decode "^2.2.0" + minimatch "^3.0.4" + querystring "^0.2.1" + run-parallel-limit "^1.1.0" + throat "^5.0.0" + traverse "^0.6.6" + ws "^7.5.3" + "@serverless/template@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@serverless/template/-/template-1.1.3.tgz#7b9e3736cc1124f176c4823fa08977cae62ae971" @@ -322,29 +343,38 @@ uuid "^8.3.2" write-file-atomic "^3.0.3" -"@serverless/utils@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.7.0.tgz#eeef23bc85c16501716c107c262b80d127bf3562" - integrity sha512-4/9lTag4NNMtgoK7qRSoP//VplnKUTqgKMJ5pjvuXHFTBNoGYbdi5Cr1UmbHwnG8FfYBUy95jNUHjSEeUXDqgg== +"@serverless/utils@^5.20.3": + version "5.20.3" + resolved "https://registry.yarnpkg.com/@serverless/utils/-/utils-5.20.3.tgz#5ab15cf8eceb81934946f2deeee028c6b9b6e270" + integrity sha512-MG3DQJdto+LaeVY9gh/z0xloAfT0h1Y3Pa4/yYcKe8Dy5HYtSujuav0MvTOH18+s2outjKKJDxTh6tZuyNqFDQ== dependencies: archive-type "^4.0.0" chalk "^4.1.2" - ci-info "^3.2.0" - content-disposition "^0.5.3" + ci-info "^3.3.0" + cli-progress-footer "^2.3.0" + content-disposition "^0.5.4" + d "^1.0.1" decompress "^4.2.1" + event-emitter "^0.3.5" + ext "^1.6.0" ext-name "^5.0.0" file-type "^16.5.3" filenamify "^4.3.0" get-stream "^6.0.1" - got "^11.8.2" + got "^11.8.3" inquirer "^7.3.3" js-yaml "^4.1.0" jwt-decode "^3.1.2" lodash "^4.17.21" + log "^6.3.1" + log-node "^8.0.3" make-dir "^3.1.0" + memoizee "^0.4.15" ncjsm "^4.2.0" p-event "^4.2.0" + supports-color "^8.1.1" type "^2.5.0" + uni-global "^1.0.0" uuid "^8.3.2" write-file-atomic "^3.0.3" @@ -694,7 +724,7 @@ ansi-red@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^2.0.0, ansi-regex@^2.1.1: +ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= @@ -714,6 +744,11 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-reset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-reset/-/ansi-reset-0.1.1.tgz#e7e71292c3c7ddcd4d62ef4a6c7c05980911c3b7" @@ -952,25 +987,25 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -aws-sdk@^2.756.0: - version "2.787.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.787.0.tgz#4d8966d11c7dbe770de26632e552c97b2d91e340" - integrity sha512-3WlUdWqUB8Vhdvj/7TENr/7SEmQzxmnHxOJ8l2WjZbcMRSuI0/9Ym4p1TC3hf21VDVDhkdGlw60QqpZQ1qb+Mg== +aws-sdk@^2.1078.0: + version "2.1135.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1135.0.tgz#8c14aa6894be529cb5fb7b6d19f3dc70e4f35816" + integrity sha512-bl9n4QgrEh52hmQ+Jo76BgJXM/p+PwfVZvImEQHFeel/33H/PDLcTJquEw5bzxM1HRNI24iH+FNPwyWLMrttTw== dependencies: buffer "4.9.2" events "1.1.1" ieee754 "1.1.13" - jmespath "0.15.0" + jmespath "0.16.0" querystring "0.2.0" sax "1.2.1" url "0.10.3" uuid "3.3.2" xml2js "0.4.19" -aws-sdk@^2.979.0: - version "2.980.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.980.0.tgz#9ec9095e343a9668a04683dd61dbe9616c2732ca" - integrity sha512-jPtCZngNOW4+rE9mPQd4fp2bU1c2mkRRrZcpa6jaSDo/WtjnfR7wtIrjKZYfyR2s0LNFqZRJadblYlroT3o8ww== +aws-sdk@^2.756.0: + version "2.787.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.787.0.tgz#4d8966d11c7dbe770de26632e552c97b2d91e340" + integrity sha512-3WlUdWqUB8Vhdvj/7TENr/7SEmQzxmnHxOJ8l2WjZbcMRSuI0/9Ym4p1TC3hf21VDVDhkdGlw60QqpZQ1qb+Mg== dependencies: buffer "4.9.2" events "1.1.1" @@ -1081,16 +1116,16 @@ bluebird@^3.4.1, bluebird@^3.4.7, bluebird@^3.5.3, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -boxen@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" - integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== +boxen@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== dependencies: ansi-align "^3.0.0" camelcase "^6.2.0" chalk "^4.1.0" cli-boxes "^2.2.1" - string-width "^4.2.0" + string-width "^4.2.2" type-fest "^0.20.2" widest-line "^3.1.0" wrap-ansi "^7.0.0" @@ -1203,6 +1238,19 @@ cacheable-request@^7.0.1: normalize-url "^4.1.0" responselike "^2.0.0" +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -1303,7 +1351,7 @@ chokidar@^3.4.1: optionalDependencies: fsevents "~2.1.2" -chokidar@^3.5.1, chokidar@^3.5.2: +chokidar@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -1318,6 +1366,21 @@ chokidar@^3.5.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.0.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -1333,33 +1396,25 @@ ci-info@^3.1.1, ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== +ci-info@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.1.tgz#58331f6f472a25fe3a50a351ae3052936c2c7f32" + integrity sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg== + cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== -cli-color@^1.4: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" - integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w== - dependencies: - ansi-regex "^2.1.1" - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - memoizee "^0.4.14" - timers-ext "^0.1.5" - -cli-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" - integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== +cli-color@^2.0.1, cli-color@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.2.tgz#e295addbae470800def0254183c648531cdf4e3f" + integrity sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw== dependencies: - ansi-regex "^2.1.1" d "^1.0.1" - es5-ext "^0.10.51" + es5-ext "^0.10.59" es6-iterator "^2.0.3" - memoizee "^0.4.14" + memoizee "^0.4.15" timers-ext "^0.1.7" cli-cursor@^2.1.0: @@ -1376,16 +1431,28 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-progress-footer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz#8aa2292f5cca4f6639e3e0107ef04d897d620563" - integrity sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg== +cli-progress-footer@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/cli-progress-footer/-/cli-progress-footer-2.3.1.tgz#14272d1105078ef6203439caf69328bf95591284" + integrity sha512-urD1hiEIQeZadVABtW5ExM8wse1phnmz15oJ4QEe46GQN87v1VBa0lZQ7gXkPELMzP6At4VY6v07baAiyztulw== dependencies: - cli-color "^1.4" - d "1" - es5-ext "^0.10.47" - process-utils "^2.0.1" + cli-color "^2.0.2" + d "^1.0.1" + es5-ext "^0.10.59" + mute-stream "0.0.8" + process-utils "^4.0.0" timers-ext "^0.1.7" + type "^2.6.0" + +cli-sprintf-format@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cli-sprintf-format/-/cli-sprintf-format-1.1.1.tgz#ec69955c89ef1c61243b52e68015b75c08fb9188" + integrity sha512-BbEjY9BEdA6wagVwTqPvmAwGB24U93rQPBFZUT8lNCDxXzre5LFHQUTJc70czjgUomVg8u8R5kW8oY9DYRFNeg== + dependencies: + cli-color "^2.0.1" + es5-ext "^0.10.53" + sprintf-kit "^2.0.1" + supports-color "^6.1.0" cli-width@^2.0.0: version "2.2.1" @@ -1595,12 +1662,12 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-disposition@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== +content-disposition@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: - safe-buffer "5.1.2" + safe-buffer "5.2.1" cookiejar@^2.1.0: version "2.1.2" @@ -1707,12 +1774,17 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@^1.10.4, dayjs@^1.10.6: +dayjs@^1.10.4: version "1.10.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== -debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: +dayjs@^1.10.7: + version "1.11.2" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5" + integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw== + +debug@4, debug@^4.0.1, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -1733,6 +1805,13 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -2014,7 +2093,7 @@ error-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= -es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.47, es5-ext@^0.10.49, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: +es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.47, es5-ext@^0.10.49, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== @@ -2023,6 +2102,15 @@ es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@ es6-symbol "~3.1.3" next-tick "~1.0.0" +es5-ext@^0.10.59: + version "0.10.61" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269" + integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -2056,7 +2144,7 @@ es6-symbol@3.1.1: d "1" es5-ext "~0.10.14" -es6-symbol@^3.1.1, es6-symbol@~3.1.3: +es6-symbol@^3.1.1, es6-symbol@^3.1.3, es6-symbol@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== @@ -2092,10 +2180,12 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -essentials@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.1.1.tgz#03befbfbee7078301741279b38a806b6ca624821" - integrity sha512-SmaxoAdVu86XkZQM/u6TYSu96ZlFGwhvSk1l9zAkznFuQkMb9mRDS2iq/XWDow7R8OwBwdYH8nLyDKznMD+GWw== +essentials@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/essentials/-/essentials-1.2.0.tgz#c6361fb648f5c8c0c51279707f6139e521a05807" + integrity sha512-kP/j7Iw7KeNE8b/o7+tr9uX2s1wegElGOoGZ2Xm35qBr4BbbEcH3/bxR2nfH9l9JANCq9AUrvKw+gRuHtZp0HQ== + dependencies: + uni-global "^1.0.0" event-emitter@^0.3.5, event-emitter@~0.3.5: version "0.3.5" @@ -2142,19 +2232,26 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" -ext@^1.1.0, ext@^1.4.0, ext@^1.5.0: +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + +ext@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.5.0.tgz#e93b97ae0cb23f8370380f6107d2d2b7887687ad" integrity sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q== dependencies: type "^2.5.0" -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== +ext@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52" + integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== dependencies: - type "^2.0.0" + type "^2.5.0" extend-shallow@^2.0.0, extend-shallow@^2.0.1: version "2.0.1" @@ -2192,7 +2289,7 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.4: +fast-glob@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== @@ -2215,6 +2312,17 @@ fast-glob@^3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2361,10 +2469,10 @@ filenamify@^4.3.0: strip-outer "^1.0.1" trim-repeated "^1.0.0" -filesize@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.0.tgz#532db71cb8a04df7d403da054a28de1b648534e0" - integrity sha512-sb690gQx3y/5KZIztgWAKM/r4Hf1V3R8mkAE0OhasMw2FDYduFTYCji8YN9BVpsGoMxrHPFvia1BMxwfLHX+fQ== +filesize@^8.0.7: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^7.0.1: version "7.0.1" @@ -2681,16 +2789,16 @@ global-prefix@^0.1.4: is-windows "^0.2.0" which "^1.2.12" -globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" got@^11.8.2: @@ -2710,6 +2818,23 @@ got@^11.8.2: p-cancelable "^2.0.0" responselike "^2.0.0" +got@^11.8.3: + version "11.8.3" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" + integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -2732,11 +2857,16 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.1.4, graceful-fs@^4.2.8, graceful-fs@~4.2.0: +graceful-fs@^4.1.4, graceful-fs@~4.2.0: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + graphlib@^2.1.7, graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" @@ -2864,6 +2994,11 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -3230,6 +3365,11 @@ jmespath@0.15.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== + js-yaml@^3.13.1, js-yaml@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" @@ -3495,6 +3635,20 @@ lodash@4.17.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.1 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-node@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/log-node/-/log-node-8.0.3.tgz#441bf1a72f9f1c28b62f5bf42e9eb3765af74d73" + integrity sha512-1UBwzgYiCIDFs8A0rM2QdBFo8Wd8UQ0HrSTu/MNI+/2zN3NoHRj2fhplurAyuxTYUXu3Oohugq1jAn5s05u1MQ== + dependencies: + ansi-regex "^5.0.1" + cli-color "^2.0.1" + cli-sprintf-format "^1.1.1" + d "^1.0.1" + es5-ext "^0.10.53" + sprintf-kit "^2.0.1" + supports-color "^8.1.1" + type "^2.5.0" + log-ok@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/log-ok/-/log-ok-0.1.1.tgz#bea3dd36acd0b8a7240d78736b5b97c65444a334" @@ -3528,6 +3682,19 @@ log@^6.0.0: sprintf-kit "^2.0.0" type "^1.0.1" +log@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/log/-/log-6.3.1.tgz#fcf9bd71fe2274a51ad608dc95c549dd7512146b" + integrity sha512-McG47rJEWOkXTDioZzQNydAVvZNeEkSyLJ1VWkFwfW+o1knW+QSi8D1KjPn/TnctV+q99lkvJNe1f0E1IjfY2A== + dependencies: + d "^1.0.1" + duration "^0.2.2" + es5-ext "^0.10.53" + event-emitter "^0.3.5" + sprintf-kit "^2.0.1" + type "^2.5.0" + uni-global "^1.0.0" + logform@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" @@ -3642,7 +3809,7 @@ memoizee@^0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -3835,6 +4002,20 @@ ncjsm@^4.2.0: fs2 "^0.3.9" type "^2.5.0" +ncjsm@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ncjsm/-/ncjsm-4.3.0.tgz#ec2301ad67475f414a50de34fae00ebc31527e38" + integrity sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw== + dependencies: + builtin-modules "^3.2.0" + deferred "^0.7.11" + es5-ext "^0.10.53" + es6-set "^0.1.5" + ext "^1.6.0" + find-requires "^1.0.0" + fs2 "^0.3.9" + type "^2.5.0" + nested-error-stacks@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" @@ -3869,7 +4050,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -3892,7 +4073,7 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0, normalize-url@^4.5.1: +normalize-url@^4.1.0, normalize-url@^4.5.1, normalize-url@^6.0.1: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== @@ -3931,11 +4112,6 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" - integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -4214,14 +4390,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process-utils@^2.0.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-2.6.0.tgz#0f0d3b2633ec1f4656def20d36425f92ec60cf23" - integrity sha512-2zKFADQDvHiUDyJQTsBTdu1+Q2D/WtReBotZwXmD9oUueb0kNv4rXulK/78hMM+nclBNFZ/ZlHOJtobt8oHpqQ== - dependencies: - ext "^1.1.0" - type "^2.0.0" - process-utils@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/process-utils/-/process-utils-4.0.0.tgz#3e5b204e1d38e62fe39ef3144664a1fe94097b9e" @@ -4669,16 +4837,16 @@ rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.2: dependencies: tslib "^1.9.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4803,63 +4971,63 @@ serverless-wsgi@^2.0.1: hasbin "^1.2.3" lodash "^4.17.21" -serverless@^2.57.0: - version "2.57.0" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.57.0.tgz#74b5347f50969d81869762f53c92221d92e9999a" - integrity sha512-/gLzaqdisNlymMiQJMkFoACy7NXkOd7E67sbcOrENXuVb48QAS4PTT8VPkMNu6eQonlphGagbKhpqVmKzHJDmQ== +serverless@^2.58.0: + version "2.72.3" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-2.72.3.tgz#de01203633ef7192c069b5a015aaf46e965cfa96" + integrity sha512-Fy/0IFXe2oJLvxQhiNVwwPQZPl2EzfVmxS+FqZbkw4z37MM/yKVRhIHzlJ5Wfnt9VCptgVAACiTJkjYB4jSrcg== dependencies: - "@serverless/cli" "^1.5.2" - "@serverless/components" "^3.16.0" - "@serverless/dashboard-plugin" "^5.4.4" - "@serverless/platform-client" "^4.3.0" - "@serverless/utils" "^5.7.0" + "@serverless/cli" "^1.6.0" + "@serverless/components" "^3.18.2" + "@serverless/dashboard-plugin" "^5.5.4" + "@serverless/platform-client" "^4.3.1" + "@serverless/utils" "^5.20.3" ajv "^6.12.6" ajv-keywords "^3.5.2" archiver "^5.3.0" - aws-sdk "^2.979.0" + aws-sdk "^2.1078.0" bluebird "^3.7.2" - boxen "^5.0.1" + boxen "^5.1.2" cachedir "^2.3.0" chalk "^4.1.2" child-process-ext "^2.1.1" - ci-info "^3.2.0" - cli-progress-footer "^1.1.1" + ci-info "^3.3.0" + cli-progress-footer "^2.3.0" d "^1.0.1" - dayjs "^1.10.6" + dayjs "^1.10.7" decompress "^4.2.1" dotenv "^10.0.0" dotenv-expand "^5.1.0" - essentials "^1.1.1" - ext "^1.5.0" + essentials "^1.2.0" + ext "^1.6.0" fastest-levenshtein "^1.0.12" - filesize "^8.0.0" + filesize "^8.0.7" fs-extra "^9.1.0" get-stdin "^8.0.0" - globby "^11.0.4" - got "^11.8.2" - graceful-fs "^4.2.8" + globby "^11.1.0" + got "^11.8.3" + graceful-fs "^4.2.9" https-proxy-agent "^5.0.0" is-docker "^2.2.1" - is-wsl "^2.2.0" js-yaml "^4.1.0" json-cycle "^1.3.0" json-refs "^3.0.15" lodash "^4.17.21" memoizee "^0.4.15" micromatch "^4.0.4" - ncjsm "^4.2.0" - node-fetch "^2.6.1" - object-hash "^2.2.0" + ncjsm "^4.3.0" + node-fetch "^2.6.7" + open "^7.4.2" path2 "^0.1.0" process-utils "^4.0.0" promise-queue "^2.2.5" replaceall "^0.1.6" semver "^7.3.5" - signal-exit "^3.0.3" + signal-exit "^3.0.7" + strip-ansi "^6.0.1" tabtab "^3.0.2" tar "^6.1.11" timers-ext "^0.1.7" - type "^2.5.0" + type "^2.6.0" untildify "^4.0.0" uuid "^8.3.2" yaml-ast-parser "0.0.43" @@ -4934,11 +5102,16 @@ shortid@^2.2.14: dependencies: nanoid "^2.1.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -4953,14 +5126,14 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.44.0: - version "2.45.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.45.0.tgz#4d53b146fc23496099ebfc7af5b0d605d0e3e504" - integrity sha512-wu/Ujs9IXn0HuyYm4HyRvne+EKsjJSWKEMkB3wQa3gNHSMHt7y3oeNX9zRQ3UBPk7bRRMLLHAdIZCZfHT9ehPg== +simple-git@^2.48.0: + version "2.48.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.48.0.tgz#87c262dba8f84d7b96bb3a713e9e34701c1f6e3b" + integrity sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" - debug "^4.3.1" + debug "^4.3.2" simple-swizzle@^0.2.2: version "0.2.2" @@ -5047,6 +5220,13 @@ sprintf-kit@^2.0.0: dependencies: es5-ext "^0.10.46" +sprintf-kit@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/sprintf-kit/-/sprintf-kit-2.0.1.tgz#bb837e8fa4b28f094531d8e33669120027236bb8" + integrity sha512-2PNlcs3j5JflQKcg4wpdqpZ+AjhQJ2OZEo34NXDtlB0tIPG84xaaXhpA8XFacFiwjKA4m49UOYG83y3hbMn/gQ== + dependencies: + es5-ext "^0.10.53" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -5129,6 +5309,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string-width@^4.2.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -5157,7 +5346,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -5171,6 +5360,13 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-bom-buffer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/strip-bom-buffer/-/strip-bom-buffer-0.1.1.tgz#ca3ddc4919c13f9fddf30b1dff100a9835248b4d" @@ -5244,6 +5440,13 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -5251,6 +5454,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + tabtab@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tabtab/-/tabtab-3.0.2.tgz#a2cea0f1035f88d145d7da77eaabbd3fe03e1ec9" @@ -5517,6 +5727,11 @@ type@^2.5.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +type@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f" + integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -5537,6 +5752,13 @@ unc-path-regex@^0.1.0: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= +uni-global@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/uni-global/-/uni-global-1.0.0.tgz#3583c449e87a2d9dc270ea221410a649bcdad040" + integrity sha512-WWM3HP+siTxzIWPNUg7hZ4XO8clKi6NoCAJJWnuRL+BAqyFXF8gC03WNyTefGoUXYc47uYgXxpKLIEvo65PEHw== + dependencies: + type "^2.5.0" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" From 630ebd659b4df478f64505a398602198c70ad73f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 20:05:54 -0700 Subject: [PATCH 0616/1276] Bump protobufjs from 6.10.2 to 6.11.3 in /cla-frontend-project-console (#3560) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.11.3. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/v6.11.3/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/v6.10.2...v6.11.3) --- updated-dependencies: - dependency-name: protobufjs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-project-console/yarn.lock | 43 ++++++++++++-------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/cla-frontend-project-console/yarn.lock b/cla-frontend-project-console/yarn.lock index 415cca5a9..58830645f 100644 --- a/cla-frontend-project-console/yarn.lock +++ b/cla-frontend-project-console/yarn.lock @@ -103,7 +103,7 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" @@ -118,12 +118,12 @@ "@protobufjs/eventemitter@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" @@ -131,27 +131,27 @@ "@protobufjs/float@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@serverless/cli@^1.5.2": version "1.5.2" @@ -442,19 +442,14 @@ integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg== "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/node@*": - version "14.14.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.11.tgz#fc25a4248a5e8d0837019b1d170146d07334abe0" - integrity sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== -"@types/node@^13.7.0": - version "13.13.35" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.35.tgz#d417b48313d691f5c8ff9c52cbc19cdecd306b5e" - integrity sha512-q9aeOGwv+RRou/ca4aJVUM/jD5u7LBexu+rq9PkA/NhHNn8JifcMo94soKm0b6JGSfw/PSNdqtc428OscMvEYA== +"@types/node@*", "@types/node@>=13.7.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== "@types/request-promise-native@^1.0.17": version "1.0.17" @@ -4909,9 +4904,9 @@ prompt-question@^5.0.1: prompt-choices "^4.0.5" protobufjs@^6.9.0: - version "6.10.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" - integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -4924,7 +4919,7 @@ protobufjs@^6.9.0: "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" "@types/long" "^4.0.1" - "@types/node" "^13.7.0" + "@types/node" ">=13.7.0" long "^4.0.0" proxy-addr@~2.0.5: From bf37281806cf76446454fc0ffd3570cc233ae8f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 20:21:15 -0700 Subject: [PATCH 0617/1276] Bump protobufjs from 6.10.1 to 6.11.3 in /cla-landing-page (#3559) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.1 to 6.11.3. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/v6.11.3/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/v6.10.1...v6.11.3) --- updated-dependencies: - dependency-name: protobufjs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-landing-page/yarn.lock | 43 +++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index f1d210cc4..aab6278a2 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1092,7 +1092,7 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" @@ -1107,12 +1107,12 @@ "@protobufjs/eventemitter@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" @@ -1120,27 +1120,27 @@ "@protobufjs/float@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@schematics/angular@9.1.12": version "9.1.12" @@ -1497,30 +1497,25 @@ integrity sha512-alvcho1kRUnnD1Gcl4J+hK0eencvzq9rmzvFPRmP5rPHx9VVsJj6bKLTATPVf9ktgv4ujzh7T+XWKp+jhuODig== "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/node@*": - version "14.14.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.0.tgz#f1091b6ad5de18e8e91bdbd43ec63f13de372538" - integrity sha512-BfbIHP9IapdupGhq/hc+jT5dyiBVZ2DdeC5WwJWQWDb0GijQlzUFAeIQn/2GtvZcd2HVUU7An8felIICFTC2qg== +"@types/node@*", "@types/node@>=13.7.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== "@types/node@^12.11.1": version "12.12.69" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.69.tgz#7cb6a3aa0d16664bf2dcd1450ccb8477464fbd79" integrity sha512-2F2VQRSFmzqgUEXw75L51MgnnZqc6bKWVSUPfrDPzp6mzGGibeVwyQcpvZvBr5RnsoMRHmC8EcBQiobSeqeJxg== -"@types/node@^13.7.0": - version "13.13.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.27.tgz#989cb01c7a6419abd2f2f956cae4828f825589f6" - integrity sha512-IeZlpkPnUqO45iBxJocIQzwV+K6phdSVaCxRwlvHHQ0YL+Gb1fvuv9GmIMYllZcjyzqoRKDNJeNo6p8dNWSPSQ== - "@types/q@^0.0.32": version "0.0.32" resolved "https://registry.yarnpkg.com/@types/q/-/q-0.0.32.tgz#bd284e57c84f1325da702babfc82a5328190c0c5" @@ -9499,9 +9494,9 @@ prompt-question@^5.0.1: prompt-choices "^4.0.5" protobufjs@^6.9.0: - version "6.10.1" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.1.tgz#e6a484dd8f04b29629e9053344e3970cccf13cd2" - integrity sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ== + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -9514,7 +9509,7 @@ protobufjs@^6.9.0: "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" "@types/long" "^4.0.1" - "@types/node" "^13.7.0" + "@types/node" ">=13.7.0" long "^4.0.0" protoduck@^5.0.1: From c66567e5f5def60c3c24716582d9954ec95cbec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 22:31:24 -0700 Subject: [PATCH 0618/1276] Bump protobufjs from 6.10.2 to 6.11.3 in /cla-frontend-corporate-console (#3558) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.11.3. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/v6.11.3/CHANGELOG.md) - [Commits](https://github.com/protobufjs/protobuf.js/compare/v6.10.2...v6.11.3) --- updated-dependencies: - dependency-name: protobufjs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cla-frontend-corporate-console/yarn.lock | 43 +++++++++++------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/cla-frontend-corporate-console/yarn.lock b/cla-frontend-corporate-console/yarn.lock index f9dfdb82f..2e7b1c4cd 100644 --- a/cla-frontend-corporate-console/yarn.lock +++ b/cla-frontend-corporate-console/yarn.lock @@ -103,7 +103,7 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" @@ -118,12 +118,12 @@ "@protobufjs/eventemitter@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" @@ -131,27 +131,27 @@ "@protobufjs/float@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@serverless/cli@^1.5.2": version "1.5.2" @@ -442,19 +442,14 @@ integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg== "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/node@*": - version "14.14.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.11.tgz#fc25a4248a5e8d0837019b1d170146d07334abe0" - integrity sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== -"@types/node@^13.7.0": - version "13.13.35" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.35.tgz#d417b48313d691f5c8ff9c52cbc19cdecd306b5e" - integrity sha512-q9aeOGwv+RRou/ca4aJVUM/jD5u7LBexu+rq9PkA/NhHNn8JifcMo94soKm0b6JGSfw/PSNdqtc428OscMvEYA== +"@types/node@*", "@types/node@>=13.7.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" + integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== "@types/request-promise-native@^1.0.17": version "1.0.17" @@ -4871,9 +4866,9 @@ prompt-question@^5.0.1: prompt-choices "^4.0.5" protobufjs@^6.9.0: - version "6.10.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" - integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -4886,7 +4881,7 @@ protobufjs@^6.9.0: "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" "@types/long" "^4.0.1" - "@types/node" "^13.7.0" + "@types/node" ">=13.7.0" long "^4.0.0" proxy-addr@~2.0.5: From d5528beb3c17739624aac4252f64d6abe94f5f0c Mon Sep 17 00:00:00 2001 From: Amol Sontakke <56335654+amolsontakke3576@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:35:00 +0530 Subject: [PATCH 0619/1276] added help link in cla landing page (#3561) Signed-off-by: Amol Sontakke --- cla-landing-page/package.json | 3 +- cla-landing-page/src/app/app.component.html | 6 +- cla-landing-page/src/polyfills.ts | 4 + cla-landing-page/yarn.lock | 183 ++++++++++++++++++++ 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index 5ee6c40ba..e5c0056ef 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -44,13 +44,14 @@ "@angular/compiler": "~9.1.3", "@angular/core": "~9.1.3", "@angular/forms": "~9.1.3", + "@angular/localize": "9.1.13", "@angular/platform-browser": "~9.1.3", "@angular/platform-browser-dynamic": "~9.1.3", "@angular/router": "~9.1.3", "@auth0/auth0-spa-js": "^1.12.1", + "@ng-bootstrap/ng-bootstrap": "^6.1.0", "@types/auth0-js": "^8.11.7", "@types/node": "^12.11.1", - "@ng-bootstrap/ng-bootstrap": "^6.1.0", "auth0-js": "^9.16.0", "aws-sdk": "^2.885.0", "bootstrap": "^4.4.0", diff --git a/cla-landing-page/src/app/app.component.html b/cla-landing-page/src/app/app.component.html index 39dbcb13a..1ca865e7e 100644 --- a/cla-landing-page/src/app/app.component.html +++ b/cla-landing-page/src/app/app.component.html @@ -1,8 +1,12 @@ - + +
      diff --git a/cla-landing-page/src/polyfills.ts b/cla-landing-page/src/polyfills.ts index c3ac037b9..866a55a2c 100644 --- a/cla-landing-page/src/polyfills.ts +++ b/cla-landing-page/src/polyfills.ts @@ -1,3 +1,7 @@ +/*************************************************************************************************** + * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. + */ +import '@angular/localize/init'; /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index aab6278a2..7dfe6a8f7 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -199,6 +199,15 @@ resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-9.1.12.tgz#c61392c0615654810cf99ee7ff46335bd0f2660b" integrity sha512-0qfIAn5fP5lD+JW6il5HBHGS89rv+idRv5aooDkHqBhuBo4V2VuB1wNy5eP49GZbHKMW1xPAzv1MqeMdk+zwQA== +"@angular/localize@9.1.13": + version "9.1.13" + resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-9.1.13.tgz#2b1dcc2135eafbf4dc540ec0cd8b655e7f784621" + integrity sha512-jmUQXVgkU2djlRtSE1SQg6ktlKnACdm4p+4YYm/D48gkl+HGwrdZtczlLTWIVeTP7o8tx6+6fQkRSRD64Xvbkg== + dependencies: + "@babel/core" "7.8.3" + glob "7.1.2" + yargs "^16.1.1" + "@angular/platform-browser-dynamic@~9.1.3": version "9.1.12" resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.12.tgz#332935d5ec4b44daaad1beae496dd994e2847a03" @@ -234,11 +243,39 @@ dependencies: "@babel/highlight" "^7.10.4" +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + "@babel/compat-data@^7.12.1", "@babel/compat-data@^7.9.0": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.1.tgz#d7386a689aa0ddf06255005b4b991988021101a0" integrity sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ== +"@babel/core@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.3.tgz#30b0ebb4dd1585de6923a0b4d179e0b9f5d82941" + integrity sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.3" + "@babel/helpers" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.0" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/core@7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" @@ -302,6 +339,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.18.6", "@babel/generator@^7.8.3": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd" + integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A== + dependencies: + "@babel/types" "^7.18.7" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" @@ -345,6 +391,11 @@ "@babel/types" "^7.10.5" lodash "^4.17.19" +"@babel/helper-environment-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" + integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== + "@babel/helper-explode-assignable-expression@^7.10.4": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz#8006a466695c4ad86a2a5f2fb15b5f2c31ad5633" @@ -361,6 +412,14 @@ "@babel/template" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/helper-function-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" + integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/helper-get-function-arity@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" @@ -375,6 +434,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-member-expression-to-functions@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" @@ -463,11 +529,23 @@ dependencies: "@babel/types" "^7.11.0" +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + "@babel/helper-validator-option@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz#175567380c3e77d60ff98a54bb015fe78f2178d9" @@ -492,6 +570,15 @@ "@babel/traverse" "^7.12.1" "@babel/types" "^7.12.1" +"@babel/helpers@^7.8.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" + integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/highlight@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" @@ -501,11 +588,25 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.10.4", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3", "@babel/parser@^7.4.3", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": version "7.12.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd" integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw== +"@babel/parser@^7.18.6", "@babel/parser@^7.8.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc" + integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw== + "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz#dc6c1170e27d8aca99ff65f4925bd06b1c90550e" @@ -989,6 +1090,15 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/template@^7.18.6", "@babel/template@^7.8.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + "@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.4.3", "@babel/traverse@^7.9.0": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e" @@ -1004,6 +1114,22 @@ globals "^11.1.0" lodash "^4.17.19" +"@babel/traverse@^7.18.6", "@babel/traverse@^7.8.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.6.tgz#a228562d2f46e89258efa4ddd0416942e2fd671d" + integrity sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.8.6", "@babel/types@^7.9.0": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae" @@ -1013,11 +1139,51 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.8.3": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.7.tgz#a4a2c910c15040ea52cdd1ddb1614a65c8041726" + integrity sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + "@istanbuljs/schema@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" + integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jsdevtools/coverage-istanbul-loader@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.3.tgz#102e414b02ae2f0b3c7fd45a705601e1fd4867c5" @@ -5750,6 +5916,18 @@ glob-parent@^3.1.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, dependencies: is-glob "^4.0.1" +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.1.6, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -7083,6 +7261,11 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + json5@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" From 63d38ca7dd2366d4f3ed1e9cbaa37b65e8c508b0 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 29 Jun 2022 09:46:25 -0700 Subject: [PATCH 0620/1276] Updated Help Links - Resolved Vulnerabilities (#3562) --- cla-landing-page/package.json | 11 +- cla-landing-page/src/app/app.component.html | 4 +- cla-landing-page/yarn.lock | 289 +++++--------------- 3 files changed, 76 insertions(+), 228 deletions(-) diff --git a/cla-landing-page/package.json b/cla-landing-page/package.json index e5c0056ef..3a90b430a 100644 --- a/cla-landing-page/package.json +++ b/cla-landing-page/package.json @@ -79,7 +79,7 @@ "codelyzer": "^5.1.2", "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~4.2.1", - "karma": "~5.0.0", + "karma": "^6.3.14", "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~2.1.0", "karma-jasmine": "~3.0.1", @@ -90,14 +90,18 @@ "typescript": "~3.8.3" }, "resolutions": { + "ansi-html": "^0.0.8", "ansi-regex": "^5.0.1", + "async": "^3.2.2", "aws-sdk": "^2.814.0", "axios": "^0.21.4", "browserslist": "^4.16.5", "dns-packet": "^5.2.2", "elliptic": "^6.5.4", "engine.io": "^4.0.0", + "eventsource": "^1.1.1", "glob-parent": "^5.1.2", + "got": "^11.8.5", "hosted-git-info": "^3.0.8", "ini": "^1.3.6", "is-svg": "^4.3.0", @@ -105,8 +109,10 @@ "jszip": "^3.7.0", "karma": "^6.3.14", "lodash": "^4.17.21", + "minimist": "^1.2.6", + "moment": "^2.29.2", "node-fetch": "^2.6.7", - "node-forge": "^1.0.0", + "node-forge": "^1.3.0", "normalize-url": "^6.0.1", "nth-check": "^2.0.1", "pac-resolver": "^5.0.0", @@ -115,6 +121,7 @@ "set-getter": "^0.1.1", "set-value": "^4.0.1", "simple-get": "^2.8.2", + "simple-git": "^3.5.0", "socket.io": "^2.4.0", "socket.io-parser": "^3.4.1", "ssri": "^8.0.1", diff --git a/cla-landing-page/src/app/app.component.html b/cla-landing-page/src/app/app.component.html index 1ca865e7e..5f3277271 100644 --- a/cla-landing-page/src/app/app.component.html +++ b/cla-landing-page/src/app/app.component.html @@ -3,8 +3,8 @@ +docslink="https://docs.linuxfoundation.org/lfx/easycla/v2-current" +faqlink="https://docs.linuxfoundation.org/lfx/easycla/v2-current/getting-started/easycla-faqs">
      diff --git a/cla-landing-page/yarn.lock b/cla-landing-page/yarn.lock index 7dfe6a8f7..402dd94ce 100644 --- a/cla-landing-page/yarn.lock +++ b/cla-landing-page/yarn.lock @@ -1561,23 +1561,11 @@ uuid "^8.3.2" write-file-atomic "^3.0.3" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - "@sindresorhus/is@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ== -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@szmarczak/http-timer@^4.0.5": version "4.0.5" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" @@ -2204,10 +2192,10 @@ ansi-hidden@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= +ansi-html@0.0.7, ansi-html@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.8.tgz#e969db193b12bcdfa6727b29ffd8882dc13cc501" + integrity sha512-QROYz1I1Kj+8bTYgx0IlMBpRSCIU+7GjbE0oH+KF7QKc+qSF8YAlIutN59Db17tXN70Ono9upT9Ht0iG93W7ug== ansi-inverse@^0.1.1: version "0.1.1" @@ -2550,17 +2538,10 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async@^2.5.0, async@^2.6.1, async@^2.6.2: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - -async@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== +async@^2.5.0, async@^2.6.1, async@^2.6.2, async@^3.2.0, async@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== asynckit@^0.4.0: version "0.4.0" @@ -3174,30 +3155,17 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3" integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w== -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" keyv "^4.0.0" lowercase-keys "^2.0.0" - normalize-url "^4.1.0" + normalize-url "^6.0.1" responselike "^2.0.0" cachedir@^2.3.0: @@ -3680,7 +3648,7 @@ colors@1.3.x: resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== -colors@1.4.0, colors@^1.2.1, colors@^1.4.0: +colors@1.4.0, colors@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -4278,7 +4246,7 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@~4.3.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@~4.3.1: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -4299,6 +4267,13 @@ debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: dependencies: ms "^2.1.1" +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -4425,11 +4400,6 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - defer-to-connect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" @@ -4694,11 +4664,6 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -5145,12 +5110,10 @@ events@^3.0.0: resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" +eventsource@^1.0.7, eventsource@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.2.tgz#bc75ae1c60209e7cb1541231980460343eaea7c2" + integrity sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -5581,11 +5544,6 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - flatted@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" @@ -5940,7 +5898,7 @@ glob@7.1.6, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glo once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.6, glob@^7.1.7: +glob@^7.1.7: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -6004,46 +5962,29 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -got@^11.8.2: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== +got@^11.8.2, got@^11.8.5, got@^9.6.0: + version "11.8.5" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" + integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-request "^7.0.2" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" p-cancelable "^2.0.0" responselike "^2.0.0" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.9" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== @@ -6961,7 +6902,7 @@ isarray@2.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= -isbinaryfile@^4.0.6, isbinaryfile@^4.0.8: +isbinaryfile@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf" integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w== @@ -7195,11 +7136,6 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -7409,43 +7345,6 @@ karma@^6.3.14: ua-parser-js "^0.7.30" yargs "^16.1.1" -karma@~5.0.0: - version "5.0.9" - resolved "https://registry.yarnpkg.com/karma/-/karma-5.0.9.tgz#11a119b0c763a806fdc471b40c594a2240b5ca13" - integrity sha512-dUA5z7Lo7G4FRSe1ZAXqOINEEWxmCjDBbfRBmU/wYlSMwxUQJP/tEEP90yJt3Uqo03s9rCgVnxtlfq+uDhxSPg== - dependencies: - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.0.0" - colors "^1.4.0" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - flatted "^2.0.2" - glob "^7.1.6" - graceful-fs "^4.2.4" - http-proxy "^1.18.1" - isbinaryfile "^4.0.6" - lodash "^4.17.15" - log4js "^6.2.1" - mime "^2.4.5" - minimatch "^3.0.4" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^2.3.0" - source-map "^0.6.1" - tmp "0.2.1" - ua-parser-js "0.7.21" - yargs "^15.3.1" - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - keyv@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" @@ -7654,7 +7553,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: +lodash@4.17.x, lodash@>=4.17.19, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7687,7 +7586,7 @@ log-utils@^0.2.1: time-stamp "^1.0.1" warning-symbol "^0.1.0" -log4js@^6.2.1, log4js@^6.4.1: +log4js@^6.4.1: version "6.4.1" resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.1.tgz#9d3a8bf2c31c1e213fe3fc398a6053f7a2bc53e8" integrity sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg== @@ -7743,11 +7642,6 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -8023,7 +7917,7 @@ mime@^2.4.4, mime@^2.4.6: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== -mime@^2.4.5, mime@^2.5.2: +mime@^2.5.2: version "2.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== @@ -8038,7 +7932,7 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0, mimic-response@^1.0.1: +mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== @@ -8075,10 +7969,10 @@ minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minipass-collect@^1.0.2: version "1.0.2" @@ -8168,10 +8062,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.24.0: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== +moment@^2.24.0, moment@^2.29.2: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== move-concurrently@^1.0.1: version "1.0.1" @@ -8361,10 +8255,10 @@ node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -node-forge@^0.10.0, node-forge@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" - integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== +node-forge@^0.10.0, node-forge@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== node-libs-browser@^2.2.1: version "2.2.1" @@ -8432,7 +8326,7 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= -normalize-url@1.9.1, normalize-url@^3.0.0, normalize-url@^4.1.0, normalize-url@^6.0.1: +normalize-url@1.9.1, normalize-url@^3.0.0, normalize-url@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== @@ -8739,13 +8633,6 @@ ora@4.0.3: strip-ansi "^6.0.0" wcwidth "^1.0.1" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -8783,11 +8670,6 @@ osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-cancelable@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" @@ -9534,11 +9416,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - prettyoutput@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/prettyoutput/-/prettyoutput-1.2.0.tgz#fef93f2a79c032880cddfb84308e2137e3674b22" @@ -10308,13 +10185,6 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2: is-core-module "^2.0.0" path-parse "^1.0.6" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - responselike@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" @@ -10872,14 +10742,14 @@ simple-get@^2.7.0, simple-get@^2.8.2: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^2.44.0: - version "2.45.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-2.45.0.tgz#4d53b146fc23496099ebfc7af5b0d605d0e3e504" - integrity sha512-wu/Ujs9IXn0HuyYm4HyRvne+EKsjJSWKEMkB3wQa3gNHSMHt7y3oeNX9zRQ3UBPk7bRRMLLHAdIZCZfHT9ehPg== +simple-git@^2.44.0, simple-git@^3.5.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.10.0.tgz#f20031dd121d3c49e215ef0186a102bece3f89b2" + integrity sha512-2w35xrS5rVtAW0g67LqtxCZN5cdddz/woQRfS0OJXaljXEoTychZ4jnE+CQgra/wX4ZvHeiChTUMenCwfIYEYw== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" - debug "^4.3.1" + debug "^4.3.4" simple-swizzle@^0.2.2: version "0.2.2" @@ -10985,7 +10855,7 @@ socket.io-parser@^3.4.1, socket.io-parser@~3.3.0, socket.io-parser@~3.4.0: debug "~4.1.0" isarray "2.0.1" -socket.io@^2.3.0, socket.io@^2.4.0, socket.io@^4.2.0: +socket.io@^2.4.0, socket.io@^4.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2" integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w== @@ -11780,13 +11650,6 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@0.2.1, tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -11794,6 +11657,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" @@ -11821,11 +11691,6 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -12042,7 +11907,7 @@ typescript@~3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@0.7.21, ua-parser-js@^0.7.24, ua-parser-js@^0.7.30: +ua-parser-js@^0.7.24, ua-parser-js@^0.7.30: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== @@ -12191,13 +12056,6 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - url-parse@^1.4.3, url-parse@^1.5.6: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -12835,23 +12693,6 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^15.3.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - yargs@^16.1.1: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" From e668d808a09cc15f09f0b405cf522e1879780161 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jun 2022 16:47:18 +0000 Subject: [PATCH 0621/1276] Bump ajv from 6.12.0 to 6.12.6 in /tests/rest Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.0 to 6.12.6. - [Release notes](https://github.com/ajv-validator/ajv/releases) - [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.0...v6.12.6) --- updated-dependencies: - dependency-name: ajv dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tests/rest/yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/rest/yarn.lock b/tests/rest/yarn.lock index ec436f484..881c4a6f0 100644 --- a/tests/rest/yarn.lock +++ b/tests/rest/yarn.lock @@ -24,9 +24,9 @@ safe-buffer "^5.0.1" ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -334,9 +334,9 @@ faker@4.1.0: integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -1067,9 +1067,9 @@ underscore@~1.7.0: integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" From 15ccc9ae6e97553467adb471ae37a44be5763592 Mon Sep 17 00:00:00 2001 From: David Deal Date: Wed, 29 Jun 2022 13:45:22 -0700 Subject: [PATCH 0622/1276] Removed Old V1 Consoles (#3566) --- .circleci/config.yml | 363 +- .../.gitignore | 1 - .../serverless-invalidate-cloudfront/index.js | 68 - .../jasmine.json | 5 - .../package.json | 17 - .../src/get-domain.js | 37 - .../src/get-domain.spec.js | 39 - .../src/invalidate-distributions.js | 77 - .../src/invalidate-distributions.spec.js | 83 - .../src/match-distributions.js | 61 - .../src/match-distributions.spec.js | 68 - .../src/random-string.js | 21 - .../src/random-string.spec.js | 16 - .../serverless-lambda-version/.editorconfig | 10 - .../serverless-lambda-version/.eslintrc | 50 - .../serverless-lambda-version/.gitignore | 13 - .../serverless-lambda-version/.npmignore | 4 - .../serverless-lambda-version/.travis.yml | 17 - .../serverless-lambda-version/LICENSE | 21 - .../serverless-lambda-version/index.js | 75 - .../serverless-lambda-version/package.json | 31 - cla-frontend-contributor-console/Makefile | 15 - .../edge/.gitignore | 3 - .../edge/jasmine.json | 5 - .../edge/package.json | 26 - .../edge/security-headers.js | 134 - .../edge/src/add-headers.js | 19 - .../edge/src/add-headers.spec.js | 64 - .../edge/src/index.js | 15 - .../edge/src/set-cache-control.js | 54 - .../edge/src/set-cache-control.spec.js | 68 - .../edge/webpack.config.js | 37 - .../edge/yarn.lock | 3487 --------- cla-frontend-contributor-console/package.json | 39 - .../serverless.yml | 280 - .../src/.editorconfig | 17 - .../src/.io-config.json | 1 - .../src/config.xml | 40 - .../src/config/scripts/prefetch-ssm.js | 24 - .../src/config/scripts/read-local.js | 21 - .../src/config/scripts/read-ssm.js | 79 - .../src/config/webpack.config.js | 44 - .../src/ionic.config.json | 6 - .../src/ionic/app/app.component.ts | 79 - .../src/ionic/app/app.html | 2 - .../src/ionic/app/app.module.ts | 41 - .../src/ionic/app/app.scss | 107 - .../src/ionic/app/main.ts | 10 - .../ionic/assets/fonts/Lato/Lato-Regular.ttf | Bin 75136 -> 0 bytes .../ionic/assets/fonts/Open_Sans/LICENSE.txt | 202 - .../fonts/Open_Sans/OpenSans-Regular.ttf | Bin 96932 -> 0 bytes .../src/ionic/assets/fonts/lato-font.scss | 5 - .../ionic/assets/fonts/open-sans-font.scss | 5 - .../src/ionic/assets/icon/favicon.png | Bin 796 -> 0 bytes .../src/ionic/assets/icon/logo.svg | 1 - .../src/ionic/assets/img/boat.jpeg | Bin 253267 -> 0 bytes .../src/ionic/assets/img/cla-icon-logo.png | Bin 32561 -> 0 bytes .../src/ionic/assets/img/gray.png | Bin 41033 -> 0 bytes .../src/ionic/assets/img/lf_logo.png | Bin 17484 -> 0 bytes .../src/ionic/assets/logo/lfx-easycla.png | Bin 3905 -> 0 bytes .../src/ionic/claenv.d.ts | 7 - .../ionic/components/get-help/get-help.html | 6 - .../components/get-help/get-help.module.ts | 13 - .../ionic/components/get-help/get-help.scss | 21 - .../src/ionic/components/get-help/get-help.ts | 12 - .../loading-spinner/loading-spinner.html | 3 - .../loading-spinner/loading-spinner.module.ts | 13 - .../loading-spinner/loading-spinner.scss | 8 - .../loading-spinner/loading-spinner.ts | 17 - .../src/ionic/constants/general.ts | 17 - .../src/ionic/declarations.d.ts | 17 - .../src/ionic/decorators/restricted.ts | 10 - .../loading-display/loading-display.module.ts | 10 - .../loading-display/loading-display.ts | 28 - .../src/ionic/index.html | 45 - .../ionic/layout/cla-footer/cla-footer.html | 0 .../ionic/layout/cla-footer/cla-footer.scss | 0 .../src/ionic/layout/cla-footer/cla-footer.ts | 33 - .../ionic/layout/cla-header/cla-header.html | 22 - .../ionic/layout/cla-header/cla-header.scss | 39 - .../src/ionic/layout/cla-header/cla-header.ts | 33 - .../src/ionic/layout/layout.module.ts | 16 - .../ionic/layout/lfx-header/lfx-header.html | 0 .../ionic/layout/lfx-header/lfx-header.scss | 0 .../src/ionic/layout/lfx-header/lfx-header.ts | 14 - .../src/ionic/manifest.json | 13 - .../cla-company-admin-send-email-modal.html | 99 - ...a-company-admin-send-email-modal.module.ts | 13 - .../cla-company-admin-send-email-modal.scss | 26 - .../cla-company-admin-send-email-modal.ts | 123 - .../cla-company-admin-yesno-modal.html | 42 - .../cla-company-admin-yesno-modal.module.ts | 16 - .../cla-company-admin-yesno-modal.scss | 25 - .../cla-company-admin-yesno-modal.ts | 57 - .../cla-employee-request-access-modal.html | 160 - ...la-employee-request-access-modal.module.ts | 14 - .../cla-employee-request-access-modal.scss | 32 - .../cla-employee-request-access-modal.ts | 282 - .../cla-new-company-modal.html | 58 - .../cla-new-company-modal.module.ts | 18 - .../cla-new-company-modal.scss | 60 - .../cla-new-company-modal.ts | 48 - .../cla-next-step-modal.html | 50 - .../cla-next-step-modal.module.ts | 13 - .../cla-next-step-modal.scss | 14 - .../cla-next-step-modal.ts | 95 - .../cla-select-company-modal.html | 53 - .../cla-select-company-modal.module.ts | 19 - .../cla-select-company-modal.scss | 14 - .../cla-select-company-modal.ts | 175 - .../cla-send-cla-manager-email-modal.html | 81 - ...cla-send-cla-manager-email-modal.module.ts | 13 - .../cla-send-cla-manager-email-modal.scss | 34 - .../cla-send-cla-manager-email-modal.ts | 127 - .../src/ionic/pages/auth/auth.html | 8 - .../src/ionic/pages/auth/auth.module.ts | 13 - .../src/ionic/pages/auth/auth.scss | 38 - .../src/ionic/pages/auth/auth.ts | 77 - .../cla-employee-company-confirm.html | 55 - .../cla-employee-company-confirm.module.ts | 14 - .../cla-employee-company-confirm.scss | 13 - .../cla-employee-company-confirm.ts | 150 - .../cla-employee-company-troubleshoot.html | 99 - ...la-employee-company-troubleshoot.module.ts | 14 - .../cla-employee-company-troubleshoot.scss | 6 - .../cla-employee-company-troubleshoot.ts | 122 - .../cla-gerrit-corporate.html | 43 - .../cla-gerrit-corporate.module.ts | 21 - .../cla-gerrit-corporate.scss | 28 - .../cla-gerrit-corporate.ts | 195 - .../cla-gerrit-individual.html | 56 - .../cla-gerrit-individual.module.ts | 16 - .../cla-gerrit-individual.scss | 15 - .../cla-gerrit-individual.ts | 129 - .../pages/cla-individual/cla-individual.html | 65 - .../cla-individual/cla-individual.module.ts | 15 - .../pages/cla-individual/cla-individual.scss | 30 - .../pages/cla-individual/cla-individual.ts | 102 - .../ionic/pages/cla-landing/cla-landing.html | 47 - .../pages/cla-landing/cla-landing.module.ts | 21 - .../ionic/pages/cla-landing/cla-landing.scss | 20 - .../ionic/pages/cla-landing/cla-landing.ts | 80 - .../src/ionic/pages/login/login.html | 16 - .../src/ionic/pages/login/login.module.ts | 21 - .../src/ionic/pages/login/login.scss | 46 - .../src/ionic/pages/login/login.ts | 32 - .../src/ionic/service-worker.js | 28 - .../src/ionic/services/auth.service.ts | 231 - .../src/ionic/services/auth.utils.ts | 4 - .../src/ionic/services/cla.env.utils.ts | 5 - .../src/ionic/services/cla.service.ts | 248 - .../src/ionic/services/constants.ts | 6 - .../src/ionic/services/http-client.ts | 145 - .../src/ionic/services/keycloak/keycloak.d.ts | 105 - .../ionic/services/keycloak/keycloak.http.ts | 78 - .../src/ionic/services/keycloak/keycloak.js | 1327 ---- .../services/keycloak/keycloak.service.ts | 112 - .../src/ionic/services/lfx-header.service.ts | 27 - .../src/ionic/services/roles.service.ts | 89 - .../src/ionic/theme/footer.scss | 36 - .../src/ionic/theme/modal.scss | 28 - .../src/ionic/theme/styles.scss | 117 - .../src/ionic/theme/table.scss | 238 - .../src/ionic/theme/variables.scss | 65 - .../src/ionic/validators/calendarlink.ts | 23 - .../src/ionic/validators/checkbox.ts | 16 - .../src/ionic/validators/email.ts | 19 - .../src/ionic/validators/phonenumber.ts | 41 - .../src/ionic/validators/url.ts | 27 - .../src/package.json | 96 - .../android/icon/drawable-hdpi-icon.png | Bin 2875 -> 0 bytes .../android/icon/drawable-ldpi-icon.png | Bin 1155 -> 0 bytes .../android/icon/drawable-mdpi-icon.png | Bin 1802 -> 0 bytes .../android/icon/drawable-xhdpi-icon.png | Bin 4317 -> 0 bytes .../android/icon/drawable-xxhdpi-icon.png | Bin 7793 -> 0 bytes .../android/icon/drawable-xxxhdpi-icon.png | Bin 11783 -> 0 bytes .../splash/drawable-land-hdpi-screen.png | Bin 13657 -> 0 bytes .../splash/drawable-land-ldpi-screen.png | Bin 3740 -> 0 bytes .../splash/drawable-land-mdpi-screen.png | Bin 7116 -> 0 bytes .../splash/drawable-land-xhdpi-screen.png | Bin 39781 -> 0 bytes .../splash/drawable-land-xxhdpi-screen.png | Bin 58987 -> 0 bytes .../splash/drawable-land-xxxhdpi-screen.png | Bin 89363 -> 0 bytes .../splash/drawable-port-hdpi-screen.png | Bin 13298 -> 0 bytes .../splash/drawable-port-ldpi-screen.png | Bin 3628 -> 0 bytes .../splash/drawable-port-mdpi-screen.png | Bin 7214 -> 0 bytes .../splash/drawable-port-xhdpi-screen.png | Bin 38625 -> 0 bytes .../splash/drawable-port-xxhdpi-screen.png | Bin 55504 -> 0 bytes .../splash/drawable-port-xxxhdpi-screen.png | Bin 81831 -> 0 bytes .../src/resources/icon.png | Bin 60857 -> 0 bytes .../src/resources/ios/icon/icon-40.png | Bin 1248 -> 0 bytes .../src/resources/ios/icon/icon-40@2x.png | Bin 3564 -> 0 bytes .../src/resources/ios/icon/icon-40@3x.png | Bin 6013 -> 0 bytes .../src/resources/ios/icon/icon-50.png | Bin 1902 -> 0 bytes .../src/resources/ios/icon/icon-50@2x.png | Bin 4691 -> 0 bytes .../src/resources/ios/icon/icon-60.png | Bin 2452 -> 0 bytes .../src/resources/ios/icon/icon-60@2x.png | Bin 6042 -> 0 bytes .../src/resources/ios/icon/icon-60@3x.png | Bin 10875 -> 0 bytes .../src/resources/ios/icon/icon-72.png | Bin 2866 -> 0 bytes .../src/resources/ios/icon/icon-72@2x.png | Bin 7793 -> 0 bytes .../src/resources/ios/icon/icon-76.png | Bin 3319 -> 0 bytes .../src/resources/ios/icon/icon-76@2x.png | Bin 8305 -> 0 bytes .../src/resources/ios/icon/icon-83.5@2x.png | Bin 9565 -> 0 bytes .../src/resources/ios/icon/icon-small.png | Bin 818 -> 0 bytes .../src/resources/ios/icon/icon-small@2x.png | Bin 2287 -> 0 bytes .../src/resources/ios/icon/icon-small@3x.png | Bin 3856 -> 0 bytes .../src/resources/ios/icon/icon.png | Bin 2112 -> 0 bytes .../src/resources/ios/icon/icon@2x.png | Bin 5718 -> 0 bytes .../ios/splash/Default-568h@2x~iphone.png | Bin 31992 -> 0 bytes .../src/resources/ios/splash/Default-667h.png | Bin 40444 -> 0 bytes .../src/resources/ios/splash/Default-736h.png | Bin 45098 -> 0 bytes .../ios/splash/Default-Landscape-736h.png | Bin 44906 -> 0 bytes .../ios/splash/Default-Landscape@2x~ipad.png | Bin 102289 -> 0 bytes .../ios/splash/Default-Landscape~ipad.png | Bin 22655 -> 0 bytes .../ios/splash/Default-Portrait@2x~ipad.png | Bin 99461 -> 0 bytes .../ios/splash/Default-Portrait~ipad.png | Bin 22692 -> 0 bytes .../ios/splash/Default@2x~iphone.png | Bin 18852 -> 0 bytes .../resources/ios/splash/Default~iphone.png | Bin 7214 -> 0 bytes .../src/resources/splash.png | Bin 62210 -> 0 bytes .../src/tsconfig.json | 26 - .../src/tslint.json | 11 - .../src/yarn.lock | 4320 ----------- cla-frontend-contributor-console/yarn.lock | 6673 ----------------- .../.gitignore | 1 - .../serverless-invalidate-cloudfront/index.js | 68 - .../jasmine.json | 5 - .../package.json | 17 - .../src/get-domain.js | 37 - .../src/get-domain.spec.js | 39 - .../src/invalidate-distributions.js | 77 - .../src/invalidate-distributions.spec.js | 83 - .../src/match-distributions.js | 61 - .../src/match-distributions.spec.js | 68 - .../src/random-string.js | 21 - .../src/random-string.spec.js | 16 - .../serverless-lambda-version/.editorconfig | 10 - .../serverless-lambda-version/.eslintrc | 50 - .../serverless-lambda-version/.gitignore | 13 - .../serverless-lambda-version/.npmignore | 4 - .../serverless-lambda-version/.travis.yml | 17 - .../serverless-lambda-version/LICENSE | 21 - .../serverless-lambda-version/index.js | 75 - .../serverless-lambda-version/package.json | 31 - cla-frontend-corporate-console/Makefile | 15 - .../edge/.gitignore | 3 - .../edge/jasmine.json | 5 - .../edge/package.json | 22 - .../edge/security-headers.js | 129 - .../edge/src/add-headers.js | 19 - .../edge/src/add-headers.spec.js | 64 - .../edge/src/index.js | 15 - .../edge/src/set-cache-control.js | 54 - .../edge/src/set-cache-control.spec.js | 68 - .../edge/webpack.config.js | 37 - cla-frontend-corporate-console/edge/yarn.lock | 2742 ------- cla-frontend-corporate-console/package.json | 42 - cla-frontend-corporate-console/serverless.yml | 277 - .../src/.editorconfig | 17 - .../src/.io-config.json | 1 - cla-frontend-corporate-console/src/config.xml | 40 - .../src/config/scripts/prefetch-ssm.js | 23 - .../src/config/scripts/read-local.js | 22 - .../src/config/scripts/read-ssm.js | 80 - .../src/config/webpack.config.js | 45 - .../src/ionic.config.json | 6 - .../src/ionic/app/app.component.ts | 116 - .../src/ionic/app/app.html | 25 - .../src/ionic/app/app.module.ts | 47 - .../src/ionic/app/app.scss | 144 - .../src/ionic/app/main.ts | 21 - .../src/ionic/assets/_bootstrap.scss | 82 - .../src/ionic/assets/_marterial.scss | 411 - .../ionic/assets/fonts/Lato/Lato-Regular.ttf | Bin 75136 -> 0 bytes .../ionic/assets/fonts/Open_Sans/LICENSE.txt | 202 - .../fonts/Open_Sans/OpenSans-Regular.ttf | Bin 96932 -> 0 bytes .../src/ionic/assets/icon/favicon.png | Bin 796 -> 0 bytes .../src/ionic/assets/icon/logo.svg | 1 - .../src/ionic/assets/logo/lfx-easycla.png | Bin 3905 -> 0 bytes .../src/ionic/claenv.d.ts | 7 - .../ionic/components/get-help/get-help.html | 6 - .../components/get-help/get-help.module.ts | 13 - .../ionic/components/get-help/get-help.scss | 21 - .../src/ionic/components/get-help/get-help.ts | 18 - .../loading-spinner/loading-spinner.html | 3 - .../loading-spinner/loading-spinner.module.ts | 13 - .../loading-spinner/loading-spinner.scss | 8 - .../loading-spinner/loading-spinner.ts | 17 - .../components/modal-header/modal-header.html | 13 - .../modal-header/modal-header.module.ts | 13 - .../components/modal-header/modal-header.scss | 35 - .../components/modal-header/modal-header.ts | 17 - .../sorting-display/sorting-display.html | 4 - .../sorting-display/sorting-display.module.ts | 13 - .../sorting-display/sorting-display.scss | 5 - .../sorting-display/sorting-display.ts | 20 - .../src/ionic/constant/general.ts | 13 - .../src/ionic/declarations.d.ts | 17 - .../src/ionic/decorators/restricted.ts | 12 - .../loading-display/loading-display.module.ts | 10 - .../loading-display/loading-display.ts | 28 - .../src/ionic/index.html | 35 - .../ionic/layout/cla-footer/cla-footer.html | 0 .../ionic/layout/cla-footer/cla-footer.scss | 0 .../src/ionic/layout/cla-footer/cla-footer.ts | 13 - .../ionic/layout/cla-header/cla-header.html | 24 - .../ionic/layout/cla-header/cla-header.scss | 39 - .../src/ionic/layout/cla-header/cla-header.ts | 32 - .../src/ionic/layout/layout.module.ts | 16 - .../ionic/layout/lfx-header/lfx-header.html | 0 .../ionic/layout/lfx-header/lfx-header.scss | 0 .../src/ionic/layout/lfx-header/lfx-header.ts | 14 - .../src/ionic/manifest.json | 13 - .../add-company-modal/add-company-modal.html | 79 - .../add-company-modal.module.ts | 14 - .../add-company-modal/add-company-modal.scss | 28 - .../add-company-modal/add-company-modal.ts | 228 - .../add-manager-modal/add-manager-modal.html | 94 - .../add-manager-modal.module.ts | 13 - .../add-manager-modal/add-manager-modal.scss | 11 - .../add-manager-modal/add-manager-modal.ts | 81 - .../collect-authority-email-modal.html | 64 - .../collect-authority-email-modal.module.ts | 13 - .../collect-authority-email-modal.scss | 26 - .../collect-authority-email-modal.ts | 88 - .../projects-ccla-select-modal.html | 50 - .../projects-ccla-select-modal.module.ts | 14 - .../projects-ccla-select-modal.scss | 11 - .../projects-ccla-select-modal.ts | 95 - .../view-cla-managers-modal.html | 37 - .../view-cla-managers-modal.module.ts | 13 - .../view-cla-managers-modal.scss | 13 - .../view-cla-managers-modal.ts | 55 - .../whitelist-modal/whitelist-modal.html | 60 - .../whitelist-modal/whitelist-modal.module.ts | 14 - .../whitelist-modal/whitelist-modal.scss | 0 .../modals/whitelist-modal/whitelist-modal.ts | 243 - .../ionic/models/cla-company-with-invites.ts | 20 - .../src/ionic/models/cla-company.ts | 15 - .../src/ionic/models/cla-manager.ts | 11 - .../src/ionic/models/cla-signature.ts | 29 - .../src/ionic/models/cla-user.ts | 21 - .../src/ionic/pages/auth/auth.html | 8 - .../src/ionic/pages/auth/auth.module.ts | 13 - .../src/ionic/pages/auth/auth.scss | 38 - .../src/ionic/pages/auth/auth.ts | 50 - .../authority-yesno-page.html | 39 - .../authority-yesno-page.module.ts | 14 - .../authority-yesno-page.scss | 18 - .../authority-yesno-page.ts | 69 - .../cla-corporate-page.html | 59 - .../cla-corporate-page.module.ts | 15 - .../cla-corporate-page.scss | 26 - .../cla-corporate-page/cla-corporate-page.ts | 95 - .../pages/companies-page/companies-page.html | 90 - .../companies-page/companies-page.module.ts | 27 - .../pages/companies-page/companies-page.scss | 97 - .../pages/companies-page/companies-page.ts | 210 - .../pages/company-page/company-page.html | 216 - .../pages/company-page/company-page.module.ts | 31 - .../pages/company-page/company-page.scss | 55 - .../ionic/pages/company-page/company-page.ts | 382 - .../ionic/pages/login-page/login-page.html | 1 - .../pages/login-page/login-page.module.ts | 21 - .../ionic/pages/login-page/login-page.scss | 0 .../src/ionic/pages/login-page/login-page.ts | 25 - .../ionic/pages/logout-page/logout-page.html | 5 - .../pages/logout-page/logout-page.module.ts | 21 - .../ionic/pages/logout-page/logout-page.scss | 0 .../ionic/pages/logout-page/logout-page.ts | 25 - .../pages/project-page/project-page.html | 312 - .../pages/project-page/project-page.module.ts | 25 - .../pages/project-page/project-page.scss | 121 - .../ionic/pages/project-page/project-page.ts | 534 -- .../src/ionic/pipes/local-timezone.pipe.ts | 13 - .../src/ionic/pipes/trim-characters.pipe.ts | 13 - .../src/ionic/service-worker.js | 28 - .../src/ionic/services/auth.service.ts | 231 - .../src/ionic/services/auth.utils.ts | 4 - .../src/ionic/services/cinco.service.ts | 350 - .../src/ionic/services/cla.env.utils.ts | 5 - .../src/ionic/services/cla.service.ts | 708 -- .../src/ionic/services/http-client.ts | 123 - .../src/ionic/services/keycloak/keycloak.d.ts | 105 - .../ionic/services/keycloak/keycloak.http.ts | 78 - .../src/ionic/services/keycloak/keycloak.js | 1327 ---- .../services/keycloak/keycloak.service.ts | 112 - .../src/ionic/services/lfx-header.service.ts | 27 - .../src/ionic/services/sort.service.ts | 119 - .../src/ionic/shared.module.ts | 19 - .../src/ionic/theme/breakpoints.scss | 17 - .../src/ionic/theme/footer.scss | 36 - .../src/ionic/theme/modal.scss | 28 - .../src/ionic/theme/styles.scss | 117 - .../src/ionic/theme/table.scss | 238 - .../src/ionic/theme/variables.scss | 74 - .../src/ionic/validators/email.ts | 19 - .../src/ionic/validators/lfid.ts | 19 - .../src/ionic/validators/user-name.ts | 19 - .../src/package.json | 93 - .../android/icon/drawable-hdpi-icon.png | Bin 2875 -> 0 bytes .../android/icon/drawable-ldpi-icon.png | Bin 1155 -> 0 bytes .../android/icon/drawable-mdpi-icon.png | Bin 1802 -> 0 bytes .../android/icon/drawable-xhdpi-icon.png | Bin 4317 -> 0 bytes .../android/icon/drawable-xxhdpi-icon.png | Bin 7793 -> 0 bytes .../android/icon/drawable-xxxhdpi-icon.png | Bin 11783 -> 0 bytes .../splash/drawable-land-hdpi-screen.png | Bin 13657 -> 0 bytes .../splash/drawable-land-ldpi-screen.png | Bin 3740 -> 0 bytes .../splash/drawable-land-mdpi-screen.png | Bin 7116 -> 0 bytes .../splash/drawable-land-xhdpi-screen.png | Bin 39781 -> 0 bytes .../splash/drawable-land-xxhdpi-screen.png | Bin 58987 -> 0 bytes .../splash/drawable-land-xxxhdpi-screen.png | Bin 89363 -> 0 bytes .../splash/drawable-port-hdpi-screen.png | Bin 13298 -> 0 bytes .../splash/drawable-port-ldpi-screen.png | Bin 3628 -> 0 bytes .../splash/drawable-port-mdpi-screen.png | Bin 7214 -> 0 bytes .../splash/drawable-port-xhdpi-screen.png | Bin 38625 -> 0 bytes .../splash/drawable-port-xxhdpi-screen.png | Bin 55504 -> 0 bytes .../splash/drawable-port-xxxhdpi-screen.png | Bin 81831 -> 0 bytes .../src/resources/icon.png | Bin 60857 -> 0 bytes .../src/resources/ios/icon/icon-40.png | Bin 1248 -> 0 bytes .../src/resources/ios/icon/icon-40@2x.png | Bin 3564 -> 0 bytes .../src/resources/ios/icon/icon-40@3x.png | Bin 6013 -> 0 bytes .../src/resources/ios/icon/icon-50.png | Bin 1902 -> 0 bytes .../src/resources/ios/icon/icon-50@2x.png | Bin 4691 -> 0 bytes .../src/resources/ios/icon/icon-60.png | Bin 2452 -> 0 bytes .../src/resources/ios/icon/icon-60@2x.png | Bin 6042 -> 0 bytes .../src/resources/ios/icon/icon-60@3x.png | Bin 10875 -> 0 bytes .../src/resources/ios/icon/icon-72.png | Bin 2866 -> 0 bytes .../src/resources/ios/icon/icon-72@2x.png | Bin 7793 -> 0 bytes .../src/resources/ios/icon/icon-76.png | Bin 3319 -> 0 bytes .../src/resources/ios/icon/icon-76@2x.png | Bin 8305 -> 0 bytes .../src/resources/ios/icon/icon-83.5@2x.png | Bin 9565 -> 0 bytes .../src/resources/ios/icon/icon-small.png | Bin 818 -> 0 bytes .../src/resources/ios/icon/icon-small@2x.png | Bin 2287 -> 0 bytes .../src/resources/ios/icon/icon-small@3x.png | Bin 3856 -> 0 bytes .../src/resources/ios/icon/icon.png | Bin 2112 -> 0 bytes .../src/resources/ios/icon/icon@2x.png | Bin 5718 -> 0 bytes .../ios/splash/Default-568h@2x~iphone.png | Bin 31992 -> 0 bytes .../src/resources/ios/splash/Default-667h.png | Bin 40444 -> 0 bytes .../src/resources/ios/splash/Default-736h.png | Bin 45098 -> 0 bytes .../ios/splash/Default-Landscape-736h.png | Bin 44906 -> 0 bytes .../ios/splash/Default-Landscape@2x~ipad.png | Bin 102289 -> 0 bytes .../ios/splash/Default-Landscape~ipad.png | Bin 22655 -> 0 bytes .../ios/splash/Default-Portrait@2x~ipad.png | Bin 99461 -> 0 bytes .../ios/splash/Default-Portrait~ipad.png | Bin 22692 -> 0 bytes .../ios/splash/Default@2x~iphone.png | Bin 18852 -> 0 bytes .../resources/ios/splash/Default~iphone.png | Bin 7214 -> 0 bytes .../src/resources/splash.png | Bin 62210 -> 0 bytes .../src/tsconfig.json | 27 - .../src/tslint.json | 11 - cla-frontend-corporate-console/src/yarn.lock | 4325 ----------- cla-frontend-corporate-console/yarn.lock | 6634 ---------------- .../.gitignore | 1 - .../serverless-invalidate-cloudfront/index.js | 68 - .../jasmine.json | 5 - .../package.json | 17 - .../src/get-domain.js | 37 - .../src/get-domain.spec.js | 39 - .../src/invalidate-distributions.js | 77 - .../src/invalidate-distributions.spec.js | 83 - .../src/match-distributions.js | 63 - .../src/match-distributions.spec.js | 68 - .../src/random-string.js | 21 - .../src/random-string.spec.js | 16 - .../serverless-lambda-version/.editorconfig | 10 - .../serverless-lambda-version/.eslintrc | 50 - .../serverless-lambda-version/.gitignore | 13 - .../serverless-lambda-version/.npmignore | 4 - .../serverless-lambda-version/.travis.yml | 17 - .../serverless-lambda-version/LICENSE | 21 - .../serverless-lambda-version/index.js | 75 - .../serverless-lambda-version/package.json | 31 - cla-frontend-project-console/Makefile | 16 - cla-frontend-project-console/edge/.gitignore | 3 - .../edge/jasmine.json | 5 - .../edge/package.json | 21 - .../edge/security-headers.js | 134 - .../edge/src/add-headers.js | 19 - .../edge/src/add-headers.spec.js | 64 - .../edge/src/index.js | 15 - .../edge/src/set-cache-control.js | 54 - .../edge/src/set-cache-control.spec.js | 68 - .../edge/webpack.config.js | 37 - cla-frontend-project-console/edge/yarn.lock | 2742 ------- cla-frontend-project-console/package.json | 41 - cla-frontend-project-console/serverless.yml | 280 - .../src/.editorconfig | 17 - .../src/.io-config.json | 1 - cla-frontend-project-console/src/config.xml | 40 - .../src/config/scripts/prefetch-ssm.js | 23 - .../src/config/scripts/read-local.js | 21 - .../src/config/scripts/read-ssm.js | 80 - .../src/config/webpack.config.js | 49 - .../src/ionic.config.json | 6 - .../src/ionic/app/app.component.ts | 139 - .../src/ionic/app/app.html | 25 - .../src/ionic/app/app.module.ts | 55 - .../src/ionic/app/app.scss | 225 - .../src/ionic/app/main.ts | 20 - .../src/ionic/app/styles/form.scss | 111 - .../src/ionic/app/styles/grid.scss | 14 - .../src/ionic/app/styles/loading.scss | 22 - .../src/ionic/app/styles/menu.scss | 45 - .../src/ionic/app/styles/modal.scss | 41 - .../src/ionic/app/styles/table.scss | 245 - .../src/ionic/assets/_bootstrap.scss | 80 - .../src/ionic/assets/_marterial.scss | 408 - .../ionic/assets/fonts/Lato/Lato-Regular.ttf | Bin 75136 -> 0 bytes .../ionic/assets/fonts/Open_Sans/LICENSE.txt | 202 - .../fonts/Open_Sans/OpenSans-Regular.ttf | Bin 96932 -> 0 bytes .../src/ionic/assets/icon/favicon.png | Bin 796 -> 0 bytes .../src/ionic/assets/icon/logo.svg | 1 - .../src/ionic/assets/img/boat.jpeg | Bin 253267 -> 0 bytes .../src/ionic/assets/img/cb_bg8.jpg | Bin 244511 -> 0 bytes .../src/ionic/assets/img/cla-icon-logo.png | Bin 32561 -> 0 bytes .../src/ionic/assets/img/gray.png | Bin 41033 -> 0 bytes .../src/ionic/assets/img/lf_logo.png | Bin 17484 -> 0 bytes .../src/ionic/assets/img/pdf.png | Bin 1213 -> 0 bytes .../src/ionic/assets/img/pdf_file_icon.svg | 149 - .../src/ionic/assets/logo/lfx-easycla.png | Bin 3905 -> 0 bytes .../src/ionic/assets/sample-cla-pdf.pdf | Bin 194619 -> 0 bytes .../src/ionic/claenv.d.ts | 7 - .../action-popover/action-popover.module.ts | 13 - .../action-popover/action-popover.scss | 3 - .../action-popover/action-popover.ts | 40 - .../ionic/components/get-help/get-help.html | 6 - .../components/get-help/get-help.module.ts | 13 - .../ionic/components/get-help/get-help.scss | 21 - .../src/ionic/components/get-help/get-help.ts | 18 - .../loading-spinner/loading-spinner.html | 3 - .../loading-spinner/loading-spinner.module.ts | 13 - .../loading-spinner/loading-spinner.scss | 8 - .../loading-spinner/loading-spinner.ts | 17 - .../components/modal-header/modal-header.html | 13 - .../modal-header/modal-header.module.ts | 13 - .../components/modal-header/modal-header.scss | 28 - .../components/modal-header/modal-header.ts | 17 - .../components/pdf-viewer/pdf-viewer.html | 20 - .../pdf-viewer/pdf-viewer.module.ts | 15 - .../components/pdf-viewer/pdf-viewer.scss | 0 .../ionic/components/pdf-viewer/pdf-viewer.ts | 39 - .../project-navigation.html | 14 - .../project-navigation.module.ts | 13 - .../project-navigation.scss | 95 - .../project-navigation/project-navigation.ts | 114 - .../section-header/section-header.html | 24 - .../section-header/section-header.module.ts | 13 - .../section-header/section-header.scss | 52 - .../section-header/section-header.ts | 33 - .../components/sort-table/sort-table.html | 47 - .../sort-table/sort-table.module.ts | 14 - .../components/sort-table/sort-table.scss | 17 - .../ionic/components/sort-table/sort-table.ts | 60 - .../sorting-display/sorting-display.html | 4 - .../sorting-display/sorting-display.module.ts | 13 - .../sorting-display/sorting-display.scss | 5 - .../sorting-display/sorting-display.ts | 17 - .../upload-button/upload-button.html | 23 - .../upload-button/upload-button.module.ts | 13 - .../upload-button/upload-button.scss | 80 - .../components/upload-button/upload-button.ts | 176 - .../src/ionic/constants/general.ts | 17 - .../src/ionic/declarations.d.ts | 17 - .../src/ionic/decorators/restricted.ts | 12 - .../loading-display/loading-display.module.ts | 10 - .../loading-display/loading-display.ts | 26 - .../src/ionic/index.html | 38 - .../ionic/layout/cla-footer/cla-footer.html | 0 .../ionic/layout/cla-footer/cla-footer.scss | 0 .../src/ionic/layout/cla-footer/cla-footer.ts | 15 - .../ionic/layout/cla-header/cla-header.html | 22 - .../ionic/layout/cla-header/cla-header.scss | 35 - .../src/ionic/layout/cla-header/cla-header.ts | 33 - .../src/ionic/layout/layout.module.ts | 16 - .../ionic/layout/lfx-header/lfx-header.html | 0 .../ionic/layout/lfx-header/lfx-header.scss | 0 .../src/ionic/layout/lfx-header/lfx-header.ts | 14 - .../src/ionic/manifest.json | 13 - ...a-configure-github-repositories-modal.html | 71 - ...figure-github-repositories-modal.module.ts | 21 - ...a-configure-github-repositories-modal.scss | 55 - ...cla-configure-github-repositories-modal.ts | 274 - .../cla-contract-config-modal.html | 94 - .../cla-contract-config-modal.module.ts | 21 - .../cla-contract-config-modal.scss | 38 - .../cla-contract-config-modal.ts | 165 - .../cla-contract-version-modal.html | 53 - .../cla-contract-version-modal.module.ts | 21 - .../cla-contract-version-modal.scss | 33 - .../cla-contract-version-modal.ts | 94 - ...tract-view-companies-signatures-modal.html | 66 - ...-view-companies-signatures-modal.module.ts | 31 - ...tract-view-companies-signatures-modal.scss | 76 - ...ontract-view-companies-signatures-modal.ts | 273 - .../cla-contract-view-signatures-modal.html | 73 - ...a-contract-view-signatures-modal.module.ts | 31 - .../cla-contract-view-signatures-modal.scss | 76 - .../cla-contract-view-signatures-modal.ts | 274 - .../cla-gerrit-modal/cla-gerrit-modal.html | 68 - .../cla-gerrit-modal.module.ts | 14 - .../cla-gerrit-modal/cla-gerrit-modal.scss | 28 - .../cla-gerrit-modal/cla-gerrit-modal.ts | 104 - .../cla-organization-app-modal.html | 34 - .../cla-organization-app-modal.module.ts | 21 - .../cla-organization-app-modal.scss | 1 - .../cla-organization-app-modal.ts | 71 - .../cla-organization-provider-modal.html | 38 - .../cla-organization-provider-modal.module.ts | 21 - .../cla-organization-provider-modal.scss | 28 - .../cla-organization-provider-modal.ts | 103 - .../pdf-viewer-modal/pdf-viewer-modal.html | 21 - .../pdf-viewer-modal.module.ts | 23 - .../pdf-viewer-modal/pdf-viewer-modal.scss | 0 .../pdf-viewer-modal/pdf-viewer-modal.ts | 34 - .../ionic/models/github-organisation-model.ts | 44 - .../src/ionic/models/project-model.ts | 35 - .../src/ionic/models/sfdc-project-model.ts | 12 - .../pages/all-projects/all-projects.html | 58 - .../pages/all-projects/all-projects.module.ts | 21 - .../pages/all-projects/all-projects.scss | 126 - .../ionic/pages/all-projects/all-projects.ts | 120 - .../src/ionic/pages/auth/auth.html | 31 - .../src/ionic/pages/auth/auth.module.ts | 15 - .../src/ionic/pages/auth/auth.scss | 38 - .../src/ionic/pages/auth/auth.ts | 49 - .../src/ionic/pages/login/login.html | 1 - .../src/ionic/pages/login/login.module.ts | 21 - .../src/ionic/pages/login/login.scss | 0 .../src/ionic/pages/login/login.ts | 26 - .../ionic/pages/logout-page/logout-page.html | 5 - .../pages/logout-page/logout-page.module.ts | 21 - .../ionic/pages/logout-page/logout-page.scss | 0 .../ionic/pages/logout-page/logout-page.ts | 23 - .../project-cla-template.html | 112 - .../project-cla-template.module.ts | 29 - .../project-cla-template.scss | 293 - .../project-cla-template.ts | 164 - .../project/project-cla/project-cla.html | 512 -- .../project/project-cla/project-cla.module.ts | 27 - .../project/project-cla/project-cla.scss | 254 - .../pages/project/project-cla/project-cla.ts | 528 -- .../ionic/pages/project/project/project.html | 73 - .../pages/project/project/project.module.ts | 25 - .../ionic/pages/project/project/project.scss | 30 - .../ionic/pages/project/project/project.ts | 95 - .../src/ionic/pipes/trim-characters.pipe.ts | 13 - .../src/ionic/service-worker.js | 37 - .../src/ionic/services/auth.service.ts | 231 - .../src/ionic/services/auth.utils.ts | 6 - .../src/ionic/services/cinco.service.ts | 479 -- .../src/ionic/services/cla.env.utils.ts | 6 - .../src/ionic/services/cla.service.ts | 353 - .../src/ionic/services/filter.service.ts | 23 - .../src/ionic/services/http-client.ts | 123 - .../src/ionic/services/keycloak/keycloak.d.ts | 105 - .../ionic/services/keycloak/keycloak.http.ts | 76 - .../src/ionic/services/keycloak/keycloak.js | 1284 ---- .../services/keycloak/keycloak.service.ts | 112 - .../src/ionic/services/lfx-header.service.ts | 27 - .../src/ionic/services/s3.service.ts | 20 - .../src/ionic/services/sort.service.ts | 119 - .../src/ionic/shared.module.ts | 16 - .../src/ionic/theme/breakpoints.scss | 17 - .../src/ionic/theme/footer.scss | 36 - .../src/ionic/theme/variables.scss | 78 - .../src/ionic/validators/calendarlink.ts | 23 - .../src/ionic/validators/checkbox.ts | 16 - .../src/ionic/validators/email.ts | 19 - .../src/ionic/validators/forbidden.ts | 12 - .../src/ionic/validators/phonenumber.ts | 39 - .../src/ionic/validators/requireSelfAnd.ts | 29 - .../src/ionic/validators/url.ts | 27 - cla-frontend-project-console/src/package.json | 94 - .../android/icon/drawable-hdpi-icon.png | Bin 2875 -> 0 bytes .../android/icon/drawable-ldpi-icon.png | Bin 1155 -> 0 bytes .../android/icon/drawable-mdpi-icon.png | Bin 1802 -> 0 bytes .../android/icon/drawable-xhdpi-icon.png | Bin 4317 -> 0 bytes .../android/icon/drawable-xxhdpi-icon.png | Bin 7793 -> 0 bytes .../android/icon/drawable-xxxhdpi-icon.png | Bin 11783 -> 0 bytes .../splash/drawable-land-hdpi-screen.png | Bin 13657 -> 0 bytes .../splash/drawable-land-ldpi-screen.png | Bin 3740 -> 0 bytes .../splash/drawable-land-mdpi-screen.png | Bin 7116 -> 0 bytes .../splash/drawable-land-xhdpi-screen.png | Bin 39781 -> 0 bytes .../splash/drawable-land-xxhdpi-screen.png | Bin 58987 -> 0 bytes .../splash/drawable-land-xxxhdpi-screen.png | Bin 89363 -> 0 bytes .../splash/drawable-port-hdpi-screen.png | Bin 13298 -> 0 bytes .../splash/drawable-port-ldpi-screen.png | Bin 3628 -> 0 bytes .../splash/drawable-port-mdpi-screen.png | Bin 7214 -> 0 bytes .../splash/drawable-port-xhdpi-screen.png | Bin 38625 -> 0 bytes .../splash/drawable-port-xxhdpi-screen.png | Bin 55504 -> 0 bytes .../splash/drawable-port-xxxhdpi-screen.png | Bin 81831 -> 0 bytes .../src/resources/icon.png | Bin 60857 -> 0 bytes .../src/resources/ios/icon/icon-40.png | Bin 1248 -> 0 bytes .../src/resources/ios/icon/icon-40@2x.png | Bin 3564 -> 0 bytes .../src/resources/ios/icon/icon-40@3x.png | Bin 6013 -> 0 bytes .../src/resources/ios/icon/icon-50.png | Bin 1902 -> 0 bytes .../src/resources/ios/icon/icon-50@2x.png | Bin 4691 -> 0 bytes .../src/resources/ios/icon/icon-60.png | Bin 2452 -> 0 bytes .../src/resources/ios/icon/icon-60@2x.png | Bin 6042 -> 0 bytes .../src/resources/ios/icon/icon-60@3x.png | Bin 10875 -> 0 bytes .../src/resources/ios/icon/icon-72.png | Bin 2866 -> 0 bytes .../src/resources/ios/icon/icon-72@2x.png | Bin 7793 -> 0 bytes .../src/resources/ios/icon/icon-76.png | Bin 3319 -> 0 bytes .../src/resources/ios/icon/icon-76@2x.png | Bin 8305 -> 0 bytes .../src/resources/ios/icon/icon-83.5@2x.png | Bin 9565 -> 0 bytes .../src/resources/ios/icon/icon-small.png | Bin 818 -> 0 bytes .../src/resources/ios/icon/icon-small@2x.png | Bin 2287 -> 0 bytes .../src/resources/ios/icon/icon-small@3x.png | Bin 3856 -> 0 bytes .../src/resources/ios/icon/icon.png | Bin 2112 -> 0 bytes .../src/resources/ios/icon/icon@2x.png | Bin 5718 -> 0 bytes .../ios/splash/Default-568h@2x~iphone.png | Bin 31992 -> 0 bytes .../src/resources/ios/splash/Default-667h.png | Bin 40444 -> 0 bytes .../src/resources/ios/splash/Default-736h.png | Bin 45098 -> 0 bytes .../ios/splash/Default-Landscape-736h.png | Bin 44906 -> 0 bytes .../ios/splash/Default-Landscape@2x~ipad.png | Bin 102289 -> 0 bytes .../ios/splash/Default-Landscape~ipad.png | Bin 22655 -> 0 bytes .../ios/splash/Default-Portrait@2x~ipad.png | Bin 99461 -> 0 bytes .../ios/splash/Default-Portrait~ipad.png | Bin 22692 -> 0 bytes .../ios/splash/Default@2x~iphone.png | Bin 18852 -> 0 bytes .../resources/ios/splash/Default~iphone.png | Bin 7214 -> 0 bytes .../src/resources/splash.png | Bin 62210 -> 0 bytes .../src/tsconfig.json | 27 - cla-frontend-project-console/src/tslint.json | 11 - cla-frontend-project-console/src/yarn.lock | 4480 ----------- cla-frontend-project-console/yarn.lock | 6626 ---------------- 723 files changed, 3 insertions(+), 76329 deletions(-) delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/.gitignore delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/index.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/jasmine.json delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/package.json delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.spec.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.spec.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.spec.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.spec.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.editorconfig delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.eslintrc delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.gitignore delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.npmignore delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.travis.yml delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/LICENSE delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/index.js delete mode 100644 cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/package.json delete mode 100644 cla-frontend-contributor-console/Makefile delete mode 100644 cla-frontend-contributor-console/edge/.gitignore delete mode 100644 cla-frontend-contributor-console/edge/jasmine.json delete mode 100644 cla-frontend-contributor-console/edge/package.json delete mode 100644 cla-frontend-contributor-console/edge/security-headers.js delete mode 100644 cla-frontend-contributor-console/edge/src/add-headers.js delete mode 100644 cla-frontend-contributor-console/edge/src/add-headers.spec.js delete mode 100644 cla-frontend-contributor-console/edge/src/index.js delete mode 100644 cla-frontend-contributor-console/edge/src/set-cache-control.js delete mode 100644 cla-frontend-contributor-console/edge/src/set-cache-control.spec.js delete mode 100644 cla-frontend-contributor-console/edge/webpack.config.js delete mode 100644 cla-frontend-contributor-console/edge/yarn.lock delete mode 100644 cla-frontend-contributor-console/package.json delete mode 100644 cla-frontend-contributor-console/serverless.yml delete mode 100755 cla-frontend-contributor-console/src/.editorconfig delete mode 100755 cla-frontend-contributor-console/src/.io-config.json delete mode 100755 cla-frontend-contributor-console/src/config.xml delete mode 100644 cla-frontend-contributor-console/src/config/scripts/prefetch-ssm.js delete mode 100644 cla-frontend-contributor-console/src/config/scripts/read-local.js delete mode 100644 cla-frontend-contributor-console/src/config/scripts/read-ssm.js delete mode 100644 cla-frontend-contributor-console/src/config/webpack.config.js delete mode 100755 cla-frontend-contributor-console/src/ionic.config.json delete mode 100755 cla-frontend-contributor-console/src/ionic/app/app.component.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/app/app.html delete mode 100755 cla-frontend-contributor-console/src/ionic/app/app.module.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/app/app.scss delete mode 100755 cla-frontend-contributor-console/src/ionic/app/main.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/fonts/Lato/Lato-Regular.ttf delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/fonts/Open_Sans/LICENSE.txt delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/fonts/Open_Sans/OpenSans-Regular.ttf delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/fonts/lato-font.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/fonts/open-sans-font.scss delete mode 100755 cla-frontend-contributor-console/src/ionic/assets/icon/favicon.png delete mode 100755 cla-frontend-contributor-console/src/ionic/assets/icon/logo.svg delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/img/boat.jpeg delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/img/cla-icon-logo.png delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/img/gray.png delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/img/lf_logo.png delete mode 100644 cla-frontend-contributor-console/src/ionic/assets/logo/lfx-easycla.png delete mode 100644 cla-frontend-contributor-console/src/ionic/claenv.d.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/components/get-help/get-help.html delete mode 100644 cla-frontend-contributor-console/src/ionic/components/get-help/get-help.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/components/get-help/get-help.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/components/get-help/get-help.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.html delete mode 100644 cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/constants/general.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/declarations.d.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/decorators/restricted.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/index.html delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.html delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/layout.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.html delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/manifest.json delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.html delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/auth/auth.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/auth/auth.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/auth/auth.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/auth/auth.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.module.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.scss delete mode 100755 cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/login/login.html delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/login/login.module.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/login/login.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/pages/login/login.ts delete mode 100755 cla-frontend-contributor-console/src/ionic/service-worker.js delete mode 100644 cla-frontend-contributor-console/src/ionic/services/auth.service.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/auth.utils.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/cla.env.utils.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/cla.service.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/constants.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/http-client.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.d.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.http.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.js delete mode 100644 cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.service.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/lfx-header.service.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/services/roles.service.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/theme/footer.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/theme/modal.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/theme/styles.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/theme/table.scss delete mode 100755 cla-frontend-contributor-console/src/ionic/theme/variables.scss delete mode 100644 cla-frontend-contributor-console/src/ionic/validators/calendarlink.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/validators/checkbox.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/validators/email.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/validators/phonenumber.ts delete mode 100644 cla-frontend-contributor-console/src/ionic/validators/url.ts delete mode 100644 cla-frontend-contributor-console/src/package.json delete mode 100755 cla-frontend-contributor-console/src/resources/android/icon/drawable-hdpi-icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/icon/drawable-ldpi-icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/icon/drawable-mdpi-icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/icon/drawable-xhdpi-icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/icon/drawable-xxhdpi-icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/icon/drawable-xxxhdpi-icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-land-hdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-land-ldpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-land-mdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-land-xhdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-land-xxhdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-land-xxxhdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-port-hdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-port-ldpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-port-mdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-port-xhdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-port-xxhdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/android/splash/drawable-port-xxxhdpi-screen.png delete mode 100755 cla-frontend-contributor-console/src/resources/icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-40.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-40@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-40@3x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-50.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-50@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-60.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-60@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-60@3x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-72.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-72@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-76.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-76@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-83.5@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-small.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-small@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon-small@3x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/icon/icon@2x.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-568h@2x~iphone.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-667h.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-736h.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-Landscape-736h.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-Landscape@2x~ipad.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-Landscape~ipad.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-Portrait@2x~ipad.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default-Portrait~ipad.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default@2x~iphone.png delete mode 100755 cla-frontend-contributor-console/src/resources/ios/splash/Default~iphone.png delete mode 100755 cla-frontend-contributor-console/src/resources/splash.png delete mode 100755 cla-frontend-contributor-console/src/tsconfig.json delete mode 100755 cla-frontend-contributor-console/src/tslint.json delete mode 100644 cla-frontend-contributor-console/src/yarn.lock delete mode 100644 cla-frontend-contributor-console/yarn.lock delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/.gitignore delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/index.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/jasmine.json delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/package.json delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.spec.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.spec.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.spec.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.spec.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/.editorconfig delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/.eslintrc delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/.gitignore delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/.npmignore delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/.travis.yml delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/LICENSE delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/index.js delete mode 100644 cla-frontend-corporate-console/.serverless_plugins/serverless-lambda-version/package.json delete mode 100644 cla-frontend-corporate-console/Makefile delete mode 100644 cla-frontend-corporate-console/edge/.gitignore delete mode 100644 cla-frontend-corporate-console/edge/jasmine.json delete mode 100644 cla-frontend-corporate-console/edge/package.json delete mode 100644 cla-frontend-corporate-console/edge/security-headers.js delete mode 100644 cla-frontend-corporate-console/edge/src/add-headers.js delete mode 100644 cla-frontend-corporate-console/edge/src/add-headers.spec.js delete mode 100644 cla-frontend-corporate-console/edge/src/index.js delete mode 100644 cla-frontend-corporate-console/edge/src/set-cache-control.js delete mode 100644 cla-frontend-corporate-console/edge/src/set-cache-control.spec.js delete mode 100644 cla-frontend-corporate-console/edge/webpack.config.js delete mode 100644 cla-frontend-corporate-console/edge/yarn.lock delete mode 100644 cla-frontend-corporate-console/package.json delete mode 100644 cla-frontend-corporate-console/serverless.yml delete mode 100755 cla-frontend-corporate-console/src/.editorconfig delete mode 100755 cla-frontend-corporate-console/src/.io-config.json delete mode 100755 cla-frontend-corporate-console/src/config.xml delete mode 100644 cla-frontend-corporate-console/src/config/scripts/prefetch-ssm.js delete mode 100644 cla-frontend-corporate-console/src/config/scripts/read-local.js delete mode 100644 cla-frontend-corporate-console/src/config/scripts/read-ssm.js delete mode 100644 cla-frontend-corporate-console/src/config/webpack.config.js delete mode 100755 cla-frontend-corporate-console/src/ionic.config.json delete mode 100644 cla-frontend-corporate-console/src/ionic/app/app.component.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/app/app.html delete mode 100644 cla-frontend-corporate-console/src/ionic/app/app.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/app/app.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/app/main.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/assets/_bootstrap.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/assets/_marterial.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/assets/fonts/Lato/Lato-Regular.ttf delete mode 100644 cla-frontend-corporate-console/src/ionic/assets/fonts/Open_Sans/LICENSE.txt delete mode 100644 cla-frontend-corporate-console/src/ionic/assets/fonts/Open_Sans/OpenSans-Regular.ttf delete mode 100755 cla-frontend-corporate-console/src/ionic/assets/icon/favicon.png delete mode 100755 cla-frontend-corporate-console/src/ionic/assets/icon/logo.svg delete mode 100644 cla-frontend-corporate-console/src/ionic/assets/logo/lfx-easycla.png delete mode 100644 cla-frontend-corporate-console/src/ionic/claenv.d.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/get-help/get-help.html delete mode 100644 cla-frontend-corporate-console/src/ionic/components/get-help/get-help.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/get-help/get-help.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/components/get-help/get-help.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/loading-spinner/loading-spinner.html delete mode 100644 cla-frontend-corporate-console/src/ionic/components/loading-spinner/loading-spinner.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/loading-spinner/loading-spinner.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/components/loading-spinner/loading-spinner.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/modal-header/modal-header.html delete mode 100644 cla-frontend-corporate-console/src/ionic/components/modal-header/modal-header.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/modal-header/modal-header.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/components/modal-header/modal-header.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/sorting-display/sorting-display.html delete mode 100644 cla-frontend-corporate-console/src/ionic/components/sorting-display/sorting-display.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/components/sorting-display/sorting-display.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/components/sorting-display/sorting-display.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/constant/general.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/declarations.d.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/decorators/restricted.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/directives/loading-display/loading-display.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/directives/loading-display/loading-display.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/index.html delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/cla-footer/cla-footer.html delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/cla-footer/cla-footer.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/cla-footer/cla-footer.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.html delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/cla-header/cla-header.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/layout.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/lfx-header/lfx-header.html delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/lfx-header/lfx-header.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/layout/lfx-header/lfx-header.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/manifest.json delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-company-modal/add-company-modal.html delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-company-modal/add-company-modal.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-company-modal/add-company-modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-company-modal/add-company-modal.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-manager-modal/add-manager-modal.html delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-manager-modal/add-manager-modal.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-manager-modal/add-manager-modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/add-manager-modal/add-manager-modal.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/collect-authority-email-modal/collect-authority-email-modal.html delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/collect-authority-email-modal/collect-authority-email-modal.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/collect-authority-email-modal/collect-authority-email-modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/collect-authority-email-modal/collect-authority-email-modal.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/projects-ccla-select-modal/projects-ccla-select-modal.html delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/projects-ccla-select-modal/projects-ccla-select-modal.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/projects-ccla-select-modal/projects-ccla-select-modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/projects-ccla-select-modal/projects-ccla-select-modal.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/view-cla-managers-modal/view-cla-managers-modal.html delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/view-cla-managers-modal/view-cla-managers-modal.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/view-cla-managers-modal/view-cla-managers-modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/view-cla-managers-modal/view-cla-managers-modal.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/whitelist-modal/whitelist-modal.html delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/whitelist-modal/whitelist-modal.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/whitelist-modal/whitelist-modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/modals/whitelist-modal/whitelist-modal.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/models/cla-company-with-invites.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/models/cla-company.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/models/cla-manager.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/models/cla-signature.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/models/cla-user.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/auth/auth.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/auth/auth.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/auth/auth.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/auth/auth.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/authority-yesno-page/authority-yesno-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/authority-yesno-page/authority-yesno-page.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/authority-yesno-page/authority-yesno-page.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/authority-yesno-page/authority-yesno-page.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/cla-corporate-page/cla-corporate-page.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/pages/companies-page/companies-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/companies-page/companies-page.module.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/pages/companies-page/companies-page.scss delete mode 100755 cla-frontend-corporate-console/src/ionic/pages/companies-page/companies-page.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/pages/company-page/company-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/company-page/company-page.module.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/pages/company-page/company-page.scss delete mode 100755 cla-frontend-corporate-console/src/ionic/pages/company-page/company-page.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/login-page/login-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/login-page/login-page.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/login-page/login-page.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/login-page/login-page.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/logout-page/logout-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/logout-page/logout-page.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/logout-page/logout-page.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/logout-page/logout-page.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/project-page/project-page.html delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/project-page/project-page.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/project-page/project-page.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/pages/project-page/project-page.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pipes/local-timezone.pipe.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/pipes/trim-characters.pipe.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/service-worker.js delete mode 100644 cla-frontend-corporate-console/src/ionic/services/auth.service.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/auth.utils.ts delete mode 100755 cla-frontend-corporate-console/src/ionic/services/cinco.service.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/cla.env.utils.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/cla.service.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/http-client.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/keycloak/keycloak.d.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/keycloak/keycloak.http.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/keycloak/keycloak.js delete mode 100644 cla-frontend-corporate-console/src/ionic/services/keycloak/keycloak.service.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/lfx-header.service.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/services/sort.service.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/shared.module.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/theme/breakpoints.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/theme/footer.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/theme/modal.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/theme/styles.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/theme/table.scss delete mode 100755 cla-frontend-corporate-console/src/ionic/theme/variables.scss delete mode 100644 cla-frontend-corporate-console/src/ionic/validators/email.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/validators/lfid.ts delete mode 100644 cla-frontend-corporate-console/src/ionic/validators/user-name.ts delete mode 100644 cla-frontend-corporate-console/src/package.json delete mode 100755 cla-frontend-corporate-console/src/resources/android/icon/drawable-hdpi-icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/icon/drawable-ldpi-icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/icon/drawable-mdpi-icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/icon/drawable-xhdpi-icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/icon/drawable-xxhdpi-icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/icon/drawable-xxxhdpi-icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-land-hdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-land-ldpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-land-mdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-land-xhdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-land-xxhdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-land-xxxhdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-port-hdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-port-ldpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-port-mdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-port-xhdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-port-xxhdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/android/splash/drawable-port-xxxhdpi-screen.png delete mode 100755 cla-frontend-corporate-console/src/resources/icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-40.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-40@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-40@3x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-50.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-50@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-60.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-60@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-60@3x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-72.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-72@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-76.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-76@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-83.5@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-small.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-small@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon-small@3x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/icon/icon@2x.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-568h@2x~iphone.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-667h.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-736h.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-Landscape-736h.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-Landscape@2x~ipad.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-Landscape~ipad.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-Portrait@2x~ipad.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default-Portrait~ipad.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default@2x~iphone.png delete mode 100755 cla-frontend-corporate-console/src/resources/ios/splash/Default~iphone.png delete mode 100755 cla-frontend-corporate-console/src/resources/splash.png delete mode 100755 cla-frontend-corporate-console/src/tsconfig.json delete mode 100755 cla-frontend-corporate-console/src/tslint.json delete mode 100644 cla-frontend-corporate-console/src/yarn.lock delete mode 100644 cla-frontend-corporate-console/yarn.lock delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/.gitignore delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/index.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/jasmine.json delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/package.json delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.spec.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.spec.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.spec.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.spec.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/.editorconfig delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/.eslintrc delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/.gitignore delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/.npmignore delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/.travis.yml delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/LICENSE delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/index.js delete mode 100644 cla-frontend-project-console/.serverless_plugins/serverless-lambda-version/package.json delete mode 100644 cla-frontend-project-console/Makefile delete mode 100644 cla-frontend-project-console/edge/.gitignore delete mode 100644 cla-frontend-project-console/edge/jasmine.json delete mode 100644 cla-frontend-project-console/edge/package.json delete mode 100644 cla-frontend-project-console/edge/security-headers.js delete mode 100644 cla-frontend-project-console/edge/src/add-headers.js delete mode 100644 cla-frontend-project-console/edge/src/add-headers.spec.js delete mode 100644 cla-frontend-project-console/edge/src/index.js delete mode 100644 cla-frontend-project-console/edge/src/set-cache-control.js delete mode 100644 cla-frontend-project-console/edge/src/set-cache-control.spec.js delete mode 100644 cla-frontend-project-console/edge/webpack.config.js delete mode 100644 cla-frontend-project-console/edge/yarn.lock delete mode 100644 cla-frontend-project-console/package.json delete mode 100644 cla-frontend-project-console/serverless.yml delete mode 100755 cla-frontend-project-console/src/.editorconfig delete mode 100755 cla-frontend-project-console/src/.io-config.json delete mode 100755 cla-frontend-project-console/src/config.xml delete mode 100644 cla-frontend-project-console/src/config/scripts/prefetch-ssm.js delete mode 100644 cla-frontend-project-console/src/config/scripts/read-local.js delete mode 100644 cla-frontend-project-console/src/config/scripts/read-ssm.js delete mode 100644 cla-frontend-project-console/src/config/webpack.config.js delete mode 100755 cla-frontend-project-console/src/ionic.config.json delete mode 100644 cla-frontend-project-console/src/ionic/app/app.component.ts delete mode 100644 cla-frontend-project-console/src/ionic/app/app.html delete mode 100644 cla-frontend-project-console/src/ionic/app/app.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/app/app.scss delete mode 100644 cla-frontend-project-console/src/ionic/app/main.ts delete mode 100644 cla-frontend-project-console/src/ionic/app/styles/form.scss delete mode 100644 cla-frontend-project-console/src/ionic/app/styles/grid.scss delete mode 100644 cla-frontend-project-console/src/ionic/app/styles/loading.scss delete mode 100644 cla-frontend-project-console/src/ionic/app/styles/menu.scss delete mode 100644 cla-frontend-project-console/src/ionic/app/styles/modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/app/styles/table.scss delete mode 100644 cla-frontend-project-console/src/ionic/assets/_bootstrap.scss delete mode 100644 cla-frontend-project-console/src/ionic/assets/_marterial.scss delete mode 100644 cla-frontend-project-console/src/ionic/assets/fonts/Lato/Lato-Regular.ttf delete mode 100644 cla-frontend-project-console/src/ionic/assets/fonts/Open_Sans/LICENSE.txt delete mode 100644 cla-frontend-project-console/src/ionic/assets/fonts/Open_Sans/OpenSans-Regular.ttf delete mode 100755 cla-frontend-project-console/src/ionic/assets/icon/favicon.png delete mode 100755 cla-frontend-project-console/src/ionic/assets/icon/logo.svg delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/boat.jpeg delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/cb_bg8.jpg delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/cla-icon-logo.png delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/gray.png delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/lf_logo.png delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/pdf.png delete mode 100644 cla-frontend-project-console/src/ionic/assets/img/pdf_file_icon.svg delete mode 100644 cla-frontend-project-console/src/ionic/assets/logo/lfx-easycla.png delete mode 100644 cla-frontend-project-console/src/ionic/assets/sample-cla-pdf.pdf delete mode 100644 cla-frontend-project-console/src/ionic/claenv.d.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/action-popover/action-popover.module.ts delete mode 100755 cla-frontend-project-console/src/ionic/components/action-popover/action-popover.scss delete mode 100755 cla-frontend-project-console/src/ionic/components/action-popover/action-popover.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/get-help/get-help.html delete mode 100644 cla-frontend-project-console/src/ionic/components/get-help/get-help.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/get-help/get-help.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/get-help/get-help.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/loading-spinner/loading-spinner.html delete mode 100644 cla-frontend-project-console/src/ionic/components/loading-spinner/loading-spinner.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/loading-spinner/loading-spinner.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/loading-spinner/loading-spinner.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/modal-header/modal-header.html delete mode 100644 cla-frontend-project-console/src/ionic/components/modal-header/modal-header.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/modal-header/modal-header.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/modal-header/modal-header.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/pdf-viewer/pdf-viewer.html delete mode 100644 cla-frontend-project-console/src/ionic/components/pdf-viewer/pdf-viewer.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/pdf-viewer/pdf-viewer.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/pdf-viewer/pdf-viewer.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/project-navigation/project-navigation.html delete mode 100644 cla-frontend-project-console/src/ionic/components/project-navigation/project-navigation.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/project-navigation/project-navigation.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/project-navigation/project-navigation.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/section-header/section-header.html delete mode 100644 cla-frontend-project-console/src/ionic/components/section-header/section-header.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/section-header/section-header.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/section-header/section-header.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/sort-table/sort-table.html delete mode 100644 cla-frontend-project-console/src/ionic/components/sort-table/sort-table.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/sort-table/sort-table.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/sort-table/sort-table.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/sorting-display/sorting-display.html delete mode 100644 cla-frontend-project-console/src/ionic/components/sorting-display/sorting-display.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/components/sorting-display/sorting-display.scss delete mode 100644 cla-frontend-project-console/src/ionic/components/sorting-display/sorting-display.ts delete mode 100755 cla-frontend-project-console/src/ionic/components/upload-button/upload-button.html delete mode 100644 cla-frontend-project-console/src/ionic/components/upload-button/upload-button.module.ts delete mode 100755 cla-frontend-project-console/src/ionic/components/upload-button/upload-button.scss delete mode 100755 cla-frontend-project-console/src/ionic/components/upload-button/upload-button.ts delete mode 100644 cla-frontend-project-console/src/ionic/constants/general.ts delete mode 100755 cla-frontend-project-console/src/ionic/declarations.d.ts delete mode 100644 cla-frontend-project-console/src/ionic/decorators/restricted.ts delete mode 100644 cla-frontend-project-console/src/ionic/directives/loading-display/loading-display.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/directives/loading-display/loading-display.ts delete mode 100755 cla-frontend-project-console/src/ionic/index.html delete mode 100644 cla-frontend-project-console/src/ionic/layout/cla-footer/cla-footer.html delete mode 100644 cla-frontend-project-console/src/ionic/layout/cla-footer/cla-footer.scss delete mode 100644 cla-frontend-project-console/src/ionic/layout/cla-footer/cla-footer.ts delete mode 100644 cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.html delete mode 100644 cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.scss delete mode 100644 cla-frontend-project-console/src/ionic/layout/cla-header/cla-header.ts delete mode 100644 cla-frontend-project-console/src/ionic/layout/layout.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/layout/lfx-header/lfx-header.html delete mode 100644 cla-frontend-project-console/src/ionic/layout/lfx-header/lfx-header.scss delete mode 100644 cla-frontend-project-console/src/ionic/layout/lfx-header/lfx-header.ts delete mode 100755 cla-frontend-project-console/src/ionic/manifest.json delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-configure-github-repositories-modal/cla-configure-github-repositories-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-configure-github-repositories-modal/cla-configure-github-repositories-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-configure-github-repositories-modal/cla-configure-github-repositories-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-configure-github-repositories-modal/cla-configure-github-repositories-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-config-modal/cla-contract-config-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-config-modal/cla-contract-config-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-config-modal/cla-contract-config-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-config-modal/cla-contract-config-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-version-modal/cla-contract-version-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-version-modal/cla-contract-version-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-version-modal/cla-contract-version-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-version-modal/cla-contract-version-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-companies-signatures-modal/cla-contract-view-companies-signatures-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-companies-signatures-modal/cla-contract-view-companies-signatures-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-companies-signatures-modal/cla-contract-view-companies-signatures-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-companies-signatures-modal/cla-contract-view-companies-signatures-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-signatures-modal/cla-contract-view-signatures-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-signatures-modal/cla-contract-view-signatures-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-signatures-modal/cla-contract-view-signatures-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-contract-view-signatures-modal/cla-contract-view-signatures-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-gerrit-modal/cla-gerrit-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-gerrit-modal/cla-gerrit-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-gerrit-modal/cla-gerrit-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-gerrit-modal/cla-gerrit-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-app-modal/cla-organization-app-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-app-modal/cla-organization-app-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-app-modal/cla-organization-app-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-app-modal/cla-organization-app-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-provider-modal/cla-organization-provider-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-provider-modal/cla-organization-provider-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-provider-modal/cla-organization-provider-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/cla-organization-provider-modal/cla-organization-provider-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/pdf-viewer-modal/pdf-viewer-modal.html delete mode 100644 cla-frontend-project-console/src/ionic/modals/pdf-viewer-modal/pdf-viewer-modal.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/modals/pdf-viewer-modal/pdf-viewer-modal.scss delete mode 100644 cla-frontend-project-console/src/ionic/modals/pdf-viewer-modal/pdf-viewer-modal.ts delete mode 100644 cla-frontend-project-console/src/ionic/models/github-organisation-model.ts delete mode 100644 cla-frontend-project-console/src/ionic/models/project-model.ts delete mode 100644 cla-frontend-project-console/src/ionic/models/sfdc-project-model.ts delete mode 100755 cla-frontend-project-console/src/ionic/pages/all-projects/all-projects.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/all-projects/all-projects.module.ts delete mode 100755 cla-frontend-project-console/src/ionic/pages/all-projects/all-projects.scss delete mode 100755 cla-frontend-project-console/src/ionic/pages/all-projects/all-projects.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/auth/auth.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/auth/auth.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/auth/auth.scss delete mode 100644 cla-frontend-project-console/src/ionic/pages/auth/auth.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/login/login.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/login/login.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/login/login.scss delete mode 100644 cla-frontend-project-console/src/ionic/pages/login/login.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/logout-page/logout-page.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/logout-page/logout-page.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/logout-page/logout-page.scss delete mode 100644 cla-frontend-project-console/src/ionic/pages/logout-page/logout-page.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla-template/project-cla-template.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla-template/project-cla-template.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla-template/project-cla-template.scss delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla-template/project-cla-template.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla/project-cla.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla/project-cla.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla/project-cla.scss delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project-cla/project-cla.ts delete mode 100755 cla-frontend-project-console/src/ionic/pages/project/project/project.html delete mode 100644 cla-frontend-project-console/src/ionic/pages/project/project/project.module.ts delete mode 100755 cla-frontend-project-console/src/ionic/pages/project/project/project.scss delete mode 100755 cla-frontend-project-console/src/ionic/pages/project/project/project.ts delete mode 100644 cla-frontend-project-console/src/ionic/pipes/trim-characters.pipe.ts delete mode 100755 cla-frontend-project-console/src/ionic/service-worker.js delete mode 100644 cla-frontend-project-console/src/ionic/services/auth.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/auth.utils.ts delete mode 100755 cla-frontend-project-console/src/ionic/services/cinco.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/cla.env.utils.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/cla.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/filter.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/http-client.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/keycloak/keycloak.d.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/keycloak/keycloak.http.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/keycloak/keycloak.js delete mode 100644 cla-frontend-project-console/src/ionic/services/keycloak/keycloak.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/lfx-header.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/s3.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/services/sort.service.ts delete mode 100644 cla-frontend-project-console/src/ionic/shared.module.ts delete mode 100644 cla-frontend-project-console/src/ionic/theme/breakpoints.scss delete mode 100644 cla-frontend-project-console/src/ionic/theme/footer.scss delete mode 100755 cla-frontend-project-console/src/ionic/theme/variables.scss delete mode 100644 cla-frontend-project-console/src/ionic/validators/calendarlink.ts delete mode 100644 cla-frontend-project-console/src/ionic/validators/checkbox.ts delete mode 100644 cla-frontend-project-console/src/ionic/validators/email.ts delete mode 100644 cla-frontend-project-console/src/ionic/validators/forbidden.ts delete mode 100644 cla-frontend-project-console/src/ionic/validators/phonenumber.ts delete mode 100644 cla-frontend-project-console/src/ionic/validators/requireSelfAnd.ts delete mode 100644 cla-frontend-project-console/src/ionic/validators/url.ts delete mode 100644 cla-frontend-project-console/src/package.json delete mode 100755 cla-frontend-project-console/src/resources/android/icon/drawable-hdpi-icon.png delete mode 100755 cla-frontend-project-console/src/resources/android/icon/drawable-ldpi-icon.png delete mode 100755 cla-frontend-project-console/src/resources/android/icon/drawable-mdpi-icon.png delete mode 100755 cla-frontend-project-console/src/resources/android/icon/drawable-xhdpi-icon.png delete mode 100755 cla-frontend-project-console/src/resources/android/icon/drawable-xxhdpi-icon.png delete mode 100755 cla-frontend-project-console/src/resources/android/icon/drawable-xxxhdpi-icon.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-land-hdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-land-ldpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-land-mdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-land-xhdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-land-xxhdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-land-xxxhdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-port-hdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-port-ldpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-port-mdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-port-xhdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-port-xxhdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/android/splash/drawable-port-xxxhdpi-screen.png delete mode 100755 cla-frontend-project-console/src/resources/icon.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-40.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-40@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-40@3x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-50.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-50@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-60.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-60@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-60@3x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-72.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-72@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-76.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-76@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-83.5@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-small.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-small@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon-small@3x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon.png delete mode 100755 cla-frontend-project-console/src/resources/ios/icon/icon@2x.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-568h@2x~iphone.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-667h.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-736h.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-Landscape-736h.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-Landscape@2x~ipad.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-Landscape~ipad.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-Portrait@2x~ipad.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default-Portrait~ipad.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default@2x~iphone.png delete mode 100755 cla-frontend-project-console/src/resources/ios/splash/Default~iphone.png delete mode 100755 cla-frontend-project-console/src/resources/splash.png delete mode 100755 cla-frontend-project-console/src/tsconfig.json delete mode 100755 cla-frontend-project-console/src/tslint.json delete mode 100644 cla-frontend-project-console/src/yarn.lock delete mode 100644 cla-frontend-project-console/yarn.lock diff --git a/.circleci/config.yml b/.circleci/config.yml index a076e9574..0fa773d17 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -528,48 +528,6 @@ jobs: ROOT_DOMAIN: lfcla.platform.linuxfoundation.org PRODUCT_DOMAIN: lfcla.com - buildFrontend: &buildFrontendAnchor - docker: - - image: circleci/node:8-browsers - steps: - - checkout - - *setup_aws - - run: echo 'export NVM_DIR=${HOME}/.nvm' >> $BASH_ENV - - *install-node-12 - - run: - name: Install Top Level Dependencies - command: | - echo "Node version is: $(node --version)" - echo "Running top level install..." - yarn install - - *install-node-8 - - run: - name: Install UI Dependencies - command: | - pushd $PROJECT_DIR - echo "Running yarn install in folder: `pwd`. This will run yarn install in several places - see output below." - yarn install-frontend - popd - - run: - name: Build UI Source - command: | - echo "Building src..." - pushd $PROJECT_DIR/src - echo "AWS_PROFILE=${AWS_PROFILE}" - echo "AWS_REGION=${AWS_REGION}" - ls ~/.aws - cat ${BASH_ENV} - yarn prebuild:${STAGE} - yarn build - popd - - run: - name: Build Edge Source - command: | - echo "Building edge..." - pushd $PROJECT_DIR/edge - yarn build - popd - buildLandingPage: &buildLandingPageAnchor docker: - image: circleci/node:12-browsers @@ -599,45 +557,6 @@ jobs: yarn build:${STAGE} popd - # Build Project Management Console - buildProjectConsoleDev: - <<: *buildFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: dev - PROJECT_DIR: cla-frontend-project-console - ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org - PRODUCT_DOMAIN: dev.lfcla.com - - # Build Corporate Console - buildCorporateConsoleDev: - <<: *buildFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: dev - PROJECT_DIR: cla-frontend-corporate-console - ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org - PRODUCT_DOMAIN: dev.lfcla.com - - # Build Contributor Console - buildContributorConsoleDev: - <<: *buildFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: dev - PROJECT_DIR: cla-frontend-contributor-console - ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org - PRODUCT_DOMAIN: dev.lfcla.com - # Build Landing Page Console buildLandingPageDev: <<: *buildLandingPageAnchor @@ -651,198 +570,6 @@ jobs: ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org PRODUCT_DOMAIN: dev.lfcla.com - deployFrontend: &deployFrontendAnchor - docker: - - image: circleci/node:8-browsers - steps: - - checkout - - *setup_aws - - run: echo 'export NVM_DIR=${HOME}/.nvm' >> $BASH_ENV - - *install-node-12 - - run: - name: Install Top Level Dependencies - command: | - echo "Node version is: $(node --version)" - echo "Running top level install..." - yarn install - - *install-node-8 - - run: - name: Install UI Dependencies - command: | - pushd $PROJECT_DIR - echo "Running yarn install in folder: `pwd`. This will run yarn install in several places - see output below." - yarn install-frontend - popd - - run: - name: Build UI Source - command: | - echo "Building src..." - pushd $PROJECT_DIR/src - echo "Current directory is: `pwd`" - echo "Running pre-fetch config: 'yarn prebuild:${STAGE}'..." - yarn prebuild:${STAGE} - echo "Running build: 'yarn build:${STAGE}'..." - yarn build:${STAGE} - popd - - run: - name: Build Edge Source - command: | - echo "Building edge..." - pushd $PROJECT_DIR/edge - echo "Current directory is: `pwd`" - echo "Running build: 'yarn build'..." - yarn build - popd - - *install-node-12 - - *install_aws_cli - - run: - name: Deploy Cloudfront and LambdaEdge - command: | - pushd $PROJECT_DIR - echo "Running install 'yarn install'..." - yarn install - echo "" - echo "Running: yarn sls deploy --stage=\"${STAGE}\" --cloudfront=true" - yarn sls deploy --stage="${STAGE}" --cloudfront="true" - popd - - run: - name: Deploy Frontend Bucket - command: | - pushd $PROJECT_DIR - echo "Running install 'yarn install'..." - yarn install - echo "" - echo "Running: yarn sls client deploy --stage=\"${STAGE}\" --cloudfront=true --no-confirm --no-policy-change --no-config-change" - yarn sls client deploy --stage="${STAGE}" --cloudfront="true" --no-confirm --no-policy-change --no-config-change - popd - - run: - name: Invalidate Cache - command: | - pushd $PROJECT_DIR - echo "Running: yarn sls cloudfrontInvalidate --stage=\"${STAGE}\" --region=\"${AWS_REGION}\" --cloudfront=\"true\"" - yarn sls cloudfrontInvalidate --stage="${STAGE}" --region="${AWS_REGION}" --cloudfront="true" - popd - - # Project Management Console - deployProjectManagementConsoleDev: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: dev - PROJECT_DIR: cla-frontend-project-console - ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org - PRODUCT_DOMAIN: dev.lfcla.com - BUCKET_NAME: lf-cla-dev-cla-frontend-pmc-4 - - deployProjectManagementConsoleStaging: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: staging - PROJECT_DIR: cla-frontend-project-console - ROOT_DOMAIN: lfcla.staging.platform.linuxfoundation.org - PRODUCT_DOMAIN: staging.lfcla.com - BUCKET_NAME: lf-cla-staging-cla-frontend-pmc-3 - - deployProjectManagementConsoleProd: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: prod - PROJECT_DIR: cla-frontend-project-console - ROOT_DOMAIN: lfcla.platform.linuxfoundation.org - PRODUCT_DOMAIN: lfcla.com - BUCKET_NAME: lf-cla-prod-cla-frontend-pmc-3 - - # Corporate Console - deployCorporateConsoleDev: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: dev - PROJECT_DIR: cla-frontend-corporate-console - ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org - PRODUCT_DOMAIN: dev.lfcla.com - BUCKET_NAME: lf-cla-dev-cla-frontend-cc-4 - - deployCorporateConsoleStaging: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: staging - PROJECT_DIR: cla-frontend-corporate-console - ROOT_DOMAIN: lfcla.staging.platform.linuxfoundation.org - PRODUCT_DOMAIN: staging.lfcla.com - BUCKET_NAME: lf-cla-staging-cla-frontend-cc-3 - - deployCorporateConsoleProd: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: prod - PROJECT_DIR: cla-frontend-corporate-console - ROOT_DOMAIN: lfcla.platform.linuxfoundation.org - PRODUCT_DOMAIN: lfcla.com - BUCKET_NAME: lf-cla-prod-cla-frontend-cc-3 - - # Contributor Console - deployContributorConsoleDev: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_DEV - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_DEV - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: dev - PROJECT_DIR: cla-frontend-contributor-console - ROOT_DOMAIN: lfcla.dev.platform.linuxfoundation.org - PRODUCT_DOMAIN: dev.lfcla.com - BUCKET_NAME: lf-cla-dev-cla-frontend-ic-4 - - deployContributorConsoleStaging: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_STAGING - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_STAGING - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: staging - PROJECT_DIR: cla-frontend-contributor-console - ROOT_DOMAIN: lfcla.staging.platform.linuxfoundation.org - PRODUCT_DOMAIN: staging.lfcla.com - BUCKET_NAME: lf-cla-staging-cla-frontend-ic-3 - - deployContributorConsoleProd: - <<: *deployFrontendAnchor - environment: - AWS_ACCESS_KEY_ID_ENV_VAR: AWS_ACCESS_KEY_ID_PROD - AWS_SECRET_ACCESS_KEY_ENV_VAR: AWS_SECRET_ACCESS_KEY_PROD - AWS_PROFILE: lf-cla - AWS_REGION: us-east-1 - STAGE: prod - PROJECT_DIR: cla-frontend-contributor-console - ROOT_DOMAIN: lfcla.platform.linuxfoundation.org - PRODUCT_DOMAIN: lfcla.com - BUCKET_NAME: lf-cla-prod-cla-frontend-ic-3 - deployLandingFrontend: &deployLandingFrontendAnchor docker: - image: circleci/node:8-browsers @@ -1047,18 +774,6 @@ workflows: filters: tags: only: /.*/ - - buildProjectConsoleDev: - filters: - tags: - only: /.*/ - - buildCorporateConsoleDev: - filters: - tags: - only: /.*/ - - buildContributorConsoleDev: - filters: - tags: - only: /.*/ - buildLandingPageDev: filters: tags: @@ -1085,28 +800,10 @@ workflows: branches: only: - main - - deployProjectManagementConsoleDev: - filters: - tags: - ignore: /.*/ - branches: - only: - - main - - deployCorporateConsoleDev: - filters: - tags: - ignore: /.*/ - branches: - only: - - main - - deployContributorConsoleDev: - filters: - tags: - ignore: /.*/ - branches: - only: - - main + # Deploy Landing Page Dev - deployLandingFrontendDev: + requires: + - buildLandingPageDev filters: tags: ignore: /.*/ @@ -1174,33 +871,6 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployProjectManagementConsoleStaging: - requires: - - approve_staging - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployCorporateConsoleStaging: - requires: - - approve_staging - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployContributorConsoleStaging: - requires: - - approve_staging - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - deployLandingFrontendStaging: requires: - approve_staging @@ -1251,33 +921,6 @@ workflows: tags: # see semver examples https://regex101.com/r/Ly7O1x/201/ only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployProjectManagementConsoleProd: - requires: - - approve_prod - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployCorporateConsoleProd: - requires: - - approve_prod - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - - deployContributorConsoleProd: - requires: - - approve_prod - filters: - branches: - ignore: /.*/ - tags: - # see semver examples https://regex101.com/r/Ly7O1x/201/ - only: /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - deployLandingFrontendProd: requires: - approve_prod diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/.gitignore b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/.gitignore deleted file mode 100644 index 1521c8b76..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/index.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/index.js deleted file mode 100644 index f47370da2..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/index.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; -const AWS = require('aws-sdk'); -const invalidateDistributions = require('./src/invalidate-distributions'); -const getDomain = require('./src/get-domain'); - -class InvalidateCloudfront { - constructor(serverless, options) { - this.serverless = serverless; - this.options = options || {}; - - this.commands = { - invalidate: { - usage: 'Invalidate the Cloudfront cache', - lifecycleEvents: ['invalidate'] - }, - 'get-domain': { - usage: 'Get a cloudfront domain', - lifecycleEvents: ['get-domain'], - options: { - distribution: { - usage: 'Specify the distribution you want to the domain of (e.g. "--distribution MyDistribution")', - shortcut: 'd', - required: true - } - } - } - }; - - this.hooks = { - 'invalidate:invalidate': () => this.invalidateCloudfrontDistributions(), - 'get-domain:get-domain': () => this.getDomain() - }; - } - - invalidateCloudfrontDistributions() { - const distributions = this.serverless.service.custom.invalidateCloudfront; - const resources = this.serverless.service.resources.Resources; - const cli = this.serverless.cli; - const aws = this.serverless.getProvider('aws'); - - if (distributions === undefined || resources === undefined) { - return; - } - - const credentials = aws.getCredentials().credentials; - const cloudfront = new AWS.CloudFront({ - credentials - }); - - return invalidateDistributions(aws, distributions, cloudfront, cli); - } - - getDomain() { - const aws = this.serverless.getProvider('aws'); - - const credentials = aws.getCredentials().credentials; - const cloudfront = new AWS.CloudFront({ - credentials - }); - const dist = this.options.distribution; - - return getDomain(dist, aws, cloudfront).then((domain) => { - console.log(domain); - }); - } -} - -module.exports = InvalidateCloudfront; diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/jasmine.json b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/jasmine.json deleted file mode 100644 index 922f84aa7..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/jasmine.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "spec_dir": "./src", - "spec_files": ["**/*[sS]pec.js"], - "stopSpecOnExpectationFailure": false -} diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/package.json b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/package.json deleted file mode 100644 index 7fbc163d0..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "serverless-invalidate-cloudfront", - "version": "1.0.0", - "description": "Serverless plugin that automatically performs cloudfront invalidation on deploy.", - "main": "index.js", - "license": "UNLICENSED", - "scripts": { - "pretest": "yarn install", - "test": "./node_modules/.bin/jasmine --config=jasmine.json" - }, - "dependencies": { - "aws-sdk": "^2.214.1" - }, - "devDependencies": { - "jasmine": "^3.1.0" - } -} diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.js deleted file mode 100644 index c99d01868..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.js +++ /dev/null @@ -1,37 +0,0 @@ -function getNameFromDistributionInfo(distributionId, cloudfront) { - const params = { - Id: distributionId - }; - - return cloudfront - .getDistribution(params, (data, err) => {}) - .promise() - .then((data) => { - if (!data.DomainName) { - throw Error('GetDomain: No domain name found'); - } - return data.DomainName; - }); -} - -/** - * Retrieves the domain name from a distribution. - * @param {string} distributionName The resource name of the distribution - * @param {AWS} aws The aws sdk - * @param {Cloudfront} cloudfront An AWS cloudfront service. - * @returns {Promise} A promise containing the domain name of the resource. - */ -function getDomain(distributionName, aws, cloudfront) { - const stackName = aws.naming.getStackName(); - - return aws.request('CloudFormation', 'describeStackResources', { StackName: stackName }).then((resp) => { - const stackResources = resp.StackResources; - const resource = stackResources.find((r) => r.LogicalResourceId === distributionName); - if (resource === undefined) { - throw Error(`Unable to find distribution matching '${name}'.`); - } - return getNameFromDistributionInfo(resource.PhysicalResourceId, cloudfront); - }); -} - -module.exports = getDomain; diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.spec.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.spec.js deleted file mode 100644 index 711d34564..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/get-domain.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -const getDomain = require('./get-domain'); - -describe('getDomain', () => { - let aws, cloudfront, promise; - beforeEach(() => { - const stacks = { - StackResources: [ - { - ResourceType: 'AWS::CloudFront::Distribution', - LogicalResourceId: 'dist1', - PhysicalResourceId: 'physical1' - } - ] - }; - const stackName = 'stack'; - aws = { - naming: jasmine.createSpyObj('naming', { - getStackName: jasmine.createSpy('getStackName').and.returnValue(stackName) - }), - request: jasmine.createSpy('request').and.returnValue(Promise.resolve(stacks)) - }; - - cloudfront = { - getDistribution: jasmine.createSpy('getDistribution').and.returnValue({ - promise: () => - Promise.resolve({ - DomainName: 'www.somedomain.com' - }) - }) - }; - }); - - it('gets a domain name from a distribution name', (done) => { - getDomain('dist1', aws, cloudfront).then((name) => { - expect(name).toBe('www.somedomain.com'); - done(); - }); - }); -}); diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.js deleted file mode 100644 index 2f8d503b3..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; -const randomString = require('./random-string'); -const matchDistributions = require('./match-distributions'); - -/** - * A distribution configuration object. - * @typedef {Object} Distribution - * @property {String[]} paths - An array of paths to invalidate. - */ - -/** - * A distribution/physical id pair. - * @typedef {Object} DistributionInfo - * @property {Distribution} distribution - * @property {String} distributionId - * @property {String} name - */ - -/** - * Invalidates a cloudfront distribution. - * @param {DistributionInfo} distributionInfo - Info about the distribution to invalidate. - * @param {Cloudfront} cloudfront - An AWS cloudfront service. - * @param {Cli} cli - An serverless cli service. - */ -function invalidateDistribution(distributionInfo, cloudfront, cli) { - const reference = randomString(16); - const paths = distributionInfo.distribution.paths; - const distributionId = distributionInfo.distributionId; - const name = distributionInfo.name; - - if (paths === undefined) { - return Promise.reject('No paths defined'); - } - - const params = { - DistributionId: distributionId, - InvalidationBatch: { - CallerReference: reference, - Paths: { - Quantity: paths.length, - Items: paths - } - } - }; - - return cloudfront - .createInvalidation(params, (error, data) => { - if (!error) { - cli.log(`InvalidateCloudfront: Invalidating Distribution '${name}(${distributionInfo.distributionId})'`); - } - }) - .promise(); -} - -/** - * Retrieves information about a stack from AWS, and invalidates all matching - * cloudfront distributions. - * @param {Aws} aws - The AWS service. - * @param {Array} distributions - A list of distribution objects, including invalidation paths. - * @param {Cloudfront} cloudfront - An AWS cloudfront service. - * @param {Cli} cli - An serverless cli service. - */ -function invalidateDistributions(aws, distributions, cloudfront, cli) { - const stackName = aws.naming.getStackName(); - - return aws - .request('CloudFormation', 'describeStackResources', { StackName: stackName }) - .then((resp) => { - return matchDistributions(distributions, resp.StackResources); - }) - .then((distributions) => { - const promises = distributions.map((pair) => invalidateDistribution(pair, cloudfront, cli)); - return Promise.all(promises); - }); -} - -module.exports = invalidateDistributions; diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.spec.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.spec.js deleted file mode 100644 index 1e78fd34b..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/invalidate-distributions.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -const invalidateDistributions = require('./invalidate-distributions'); - -describe('invalidateDistributions', () => { - let aws, cli, cloudfront, promise, distributions; - beforeEach(() => { - const stacks = { - StackResources: [ - { - ResourceType: 'AWS::CloudFront::Distribution', - LogicalResourceId: 'dist1', - PhysicalResourceId: 'physical1' - }, - { - ResourceType: 'AWS::CloudFront::Distribution', - LogicalResourceId: 'dist2', - PhysicalResourceId: 'physical2' - }, - { - ResourceType: 'AWS::Lambda', - LogicalResourceId: 'nondistribution', - PhysicalResourceId: 'physical3' - } - ] - }; - const stackName = 'stack'; - aws = { - naming: jasmine.createSpyObj('naming', { - getStackName: jasmine.createSpy('getStackName').and.returnValue(stackName) - }), - request: jasmine.createSpy('request').and.returnValue(Promise.resolve(stacks)) - }; - - cloudfront = { - createInvalidation: jasmine.createSpy('createInvalidation').and.returnValue({ promise: () => Promise.resolve() }) - }; - - cli = jasmine.createSpyObj('cli', { - log: jasmine.createSpy('log') - }); - distributions = { - dist1: { - paths: ['a', 'b', 'c'] - } - }; - promise = invalidateDistributions(aws, distributions, cloudfront, cli); - }); - it('calls createInvalidation with the correct distribution id', (done) => { - promise.then(() => { - expect(cloudfront.createInvalidation).toHaveBeenCalledWith( - jasmine.objectContaining({ - DistributionId: 'physical1' - }), - jasmine.any(Function) - ); - done(); - }); - }); - it('calls createInvalidation with the correct paths', (done) => { - promise.then(() => { - expect(cloudfront.createInvalidation).toHaveBeenCalledWith( - jasmine.objectContaining({ - InvalidationBatch: jasmine.objectContaining({ - Paths: { - Quantity: 3, - Items: distributions.dist1.paths - } - }) - }), - jasmine.any(Function) - ); - done(); - }); - }); - it('rejects distributions without paths', (done) => { - distributions = { - dist1: {} - }; - promise = invalidateDistributions(aws, distributions, cloudfront, cli); - promise.catch(() => { - done(); - }); - }); -}); diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.js deleted file mode 100644 index d54d0674b..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -/** - * A distribution configuration object. - * @typedef {Object} Distribution - * @property {String[]} paths - An array of paths to invalidate. - */ - -/** - * A stack resource object. - * @typedef {Object} StackResource - * @property {String} LogicalResourceId - * @property {String} PhysicalResourceId - * @property {String} ResourceType - */ - -/** - * A distribution/physical id pair. - * @typedef {Object} DistributionInfo - * @property {Distribution} distribution - * @property {String} distributionId - * @property {String} name - */ - -/** - * - * @param {Object.} distributions - Array of distribution configuration options - * @param {Array} stackResources - * @param {String} stackName - The name of the stack. - * @returns {DistributionInfo[]} - A distribution paired with it's physical id. - */ -function matchDistributions(distributions, stackResources, stackName) { - const CLOUDFRONT_TYPE = 'AWS::CloudFront::Distribution'; - - if (distributions === undefined) { - return []; - } - - return Object.keys(distributions) - .map((distributionName) => { - const distribution = distributions[distributionName]; - - const resource = stackResources.find((r) => r.LogicalResourceId === distributionName); - - if (!resource) { - throw new Error( - `InvalidateCloudfront: Stack '${stackName}'did not have a resource with logical name '${distributionName}'` - ); - } - if (resource.ResourceType !== CLOUDFRONT_TYPE) { - throw new Error( - `InvalidateCloudfront: Stack '${stackName}' had resource with logical name '${distributionName}', but was of incorrect type '${resource.ResourceType}'` - ); - } - const distributionId = resource.PhysicalResourceId; - return { distributionId, distribution, name: distributionName }; - }) - .filter((pair) => pair !== undefined); -} - -module.exports = matchDistributions; diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.spec.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.spec.js deleted file mode 100644 index fed03ea4c..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/match-distributions.spec.js +++ /dev/null @@ -1,68 +0,0 @@ -const matchDistributions = require('./match-distributions'); - -describe('matchDistributions', () => { - let stacks; - - beforeEach(() => { - stacks = [ - { - ResourceType: 'AWS::CloudFront::Distribution', - LogicalResourceId: 'dist1', - PhysicalResourceId: 'physical1' - }, - { - ResourceType: 'AWS::CloudFront::Distribution', - LogicalResourceId: 'dist2', - PhysicalResourceId: 'physical2' - }, - { - ResourceType: 'AWS::Lambda', - LogicalResourceId: 'nondistribution', - PhysicalResourceId: 'physical3' - } - ]; - }); - - it('can match a single distribution to a stack resource', () => { - const distributions = { - dist1: { - paths: ['a', 'b', 'c'] - } - }; - const result = matchDistributions(distributions, stacks, 'staging'); - expect(result).toEqual([{ distribution: distributions.dist1, distributionId: 'physical1', name: 'dist1' }]); - }); - it('can match multiple distributions to a stack resource', () => { - const distributions = { - dist1: { - paths: ['a', 'b', 'c'] - }, - dist2: { - paths: ['e', 'f', 'g'] - } - }; - const result = matchDistributions(distributions, stacks, 'staging'); - expect(result).toEqual([ - { distribution: distributions.dist1, distributionId: 'physical1', name: 'dist1' }, - { distribution: distributions.dist2, distributionId: 'physical2', name: 'dist2' } - ]); - }); - - it("throws an error when a distribution doesn't have a matching stack resource", () => { - const distributions = { - dist3: { - paths: ['a', 'b', 'c'] - } - }; - expect(() => matchDistributions(distributions, stacks, 'staging')).toThrow(); - }); - - it("throws an error when a distribution matches a stack resource which isn't a cloudfront distribution", () => { - const distributions = { - nondistribution: { - paths: ['a', 'b', 'c'] - } - }; - expect(() => matchDistributions(distributions, stacks, 'staging')).toThrow(); - }); -}); diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.js deleted file mode 100644 index ba9699153..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -const randomCharacter = () => { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - const index = Math.floor(Math.random() * chars.length) % chars.length; - return chars.charAt(index); -}; - -/** - * Generates a random alphanumeric ASCII string of the given length. - * @param {number} length - */ -function randomString(length) { - let text = ''; - for (let i = 0; i < length; ++i) { - text += randomCharacter(); - } - return text; -} - -module.exports = randomString; diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.spec.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.spec.js deleted file mode 100644 index 61f4f669d..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-invalidate-cloudfront/src/random-string.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -const randomString = require('./random-string'); - -describe('random-string', () => { - let stacks; - - it('will generate a string of the correct length', () => { - const result = randomString(16); - expect(result.length).toBe(16); - }); - - it("won't generate the same string twice", () => { - const result1 = randomString(16); - const result2 = randomString(16); - expect(result1).not.toBe(result2); - }); -}); diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.editorconfig b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.editorconfig deleted file mode 100644 index fa21f7792..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -root = true - -[*] -indent_style = space -end_of_line = lf - -[*.{js,jsx}] -indent_style = space -end_of_line = lf -charset = utf-8 diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.eslintrc b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.eslintrc deleted file mode 100644 index 2a8871517..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.eslintrc +++ /dev/null @@ -1,50 +0,0 @@ ---- -# Based on the AirBnb JavaScript styleguides with our own twist -env: - es6: true - node: true - mocha: true -extends: - - eslint:recommended - - plugin:lodash/recommended - - plugin:promise/recommended - - plugin:import/errors - - plugin:import/warnings -parser: babel-eslint -parserOptions: - sourceType: module - ecmaFeatures: - classes: true - experimentalObjectRestSpread: true -plugins: - - promise - - lodash - - import -rules: - indent: [ error, 2, { - MemberExpression: off - } ] - linebreak-style: [ error, unix ] - semi: [ error, always ] - quotes: [ error, single, { avoidEscape: true } ] - no-mixed-spaces-and-tabs: error - space-before-blocks: error - arrow-spacing: error - key-spacing: [ error, { afterColon: true, mode: minimum } ] - brace-style: [ error, '1tbs' ] - comma-spacing: [ error, { before: false, after: true } ] - comma-style: [ error, last, { exceptions: { VariableDeclaration: true } } ] - array-bracket-spacing: [ error, always, { singleValue: false } ] - computed-property-spacing: [ error, never ] - object-curly-spacing: [ error, always ] - prefer-const: error - no-var: error - promise/no-nesting: off - import/first: error - import/newline-after-import: error - import/no-named-as-default: off - import/no-extraneous-dependencies: [ error, { devDependencies: true } ] - lodash/import-scope: off - lodash/preferred-alias: off - lodash/prop-shorthand: off - lodash/prefer-lodash-method: [ error, { ignoreObjects: [ BbPromise, path ] } ] diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.gitignore b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.gitignore deleted file mode 100644 index e9cc9995d..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -node_modules -dist -.webpack -.serverless -coverage - -.idea -/.nyc_output -node_modules -dist -.webpack -coverage - diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.npmignore b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.npmignore deleted file mode 100644 index 1ca47212f..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -coverage -examples -tests diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.travis.yml b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.travis.yml deleted file mode 100644 index efc8420b9..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: node_js - -matrix: - include: - - node_js: '6.10.1' - -sudo: false - -install: - - travis_retry npm install - -script: - - npm run eslint - # - npm test - -# after_success: -# - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/LICENSE b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/LICENSE deleted file mode 100644 index 63b4b681c..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) [year] [fullname] - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/index.js b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/index.js deleted file mode 100644 index 039406bc2..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/index.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -class LambdaArn { - constructor(serverless, options) { - this.serverless = serverless; - this.options = options; - this.hooks = { - 'before:package:finalize': this.updateLambdaVersion.bind(this) - }; - } - - updateLambdaVersion() { - const resources = this.serverless.service.resources.Resources; - const compiledResources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources; - const lambdaArns = this.getResourcesWLambdaAssoc(resources); - - _.forEach(lambdaArns, (value) => { - const associations = value.Properties.DistributionConfig.DefaultCacheBehavior.LambdaFunctionAssociations; - - _.forEach(associations, (association) => { - const arn = association.LambdaFunctionARN; - const versionRef = this.getArnAndVersion(compiledResources, arn); - if (arn && versionRef) { - this.serverless.cli.log(`serverless-lambda-version: injecting arn+version for ${JSON.stringify(arn)}`); - association.LambdaFunctionARN = versionRef; - } - }); - }); - } - - getArnAndVersion(resources, funcNormName) { - const key = _.findKey(resources, { - Type: 'AWS::Lambda::Version', - Properties: { - FunctionName: { - Ref: funcNormName - } - } - }); - - return key - ? { - 'Fn::Join': ['', [{ 'Fn::GetAtt': [funcNormName, 'Arn'] }, ':', { 'Fn::GetAtt': [key, 'Version'] }]] - } - : undefined; - } - - getResourcesWLambdaAssoc(resources) { - const eventTypes = ['viewer-request', 'origin-request', 'origin-response', 'viewer-response']; - return eventTypes - .map((eventType) => this.getResourcesWLambdaAssocOfType(resources, eventType)) - .reduce((previous, current) => ({ ...previous, ...current }), {}); - } - - getResourcesWLambdaAssocOfType(resources, eventType) { - return _.pickBy(resources, { - Type: 'AWS::CloudFront::Distribution', - Properties: { - DistributionConfig: { - DefaultCacheBehavior: { - LambdaFunctionAssociations: [ - { - EventType: eventType - } - ] - } - } - } - }); - } -} - -module.exports = LambdaArn; diff --git a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/package.json b/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/package.json deleted file mode 100644 index 8b04f4146..000000000 --- a/cla-frontend-contributor-console/.serverless_plugins/serverless-lambda-version/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "serverless-lambda-version", - "version": "0.1.2", - "description": "Serverless plugin to auto update lambda function version for Lambda@Edge LambdaFunctionAssociations", - "main": "index.js", - "author": "Dan Van Brunt (http://danvanbrunt.com)", - "repository": { - "type": "git", - "url": "git+https://github.com/iDVB/serverless-lambda-version.git" - }, - "keywords": ["serverless", "plugin", "lambda", "edge", "arn", "version"], - "license": "MIT", - "bugs": { - "url": "https://github.com/iDVB/serverless-lambda-version/issues" - }, - "homepage": "https://github.com/iDVB/serverless-lambda-version/blob/master/README.md", - "scripts": { - "eslint": "node node_modules/eslint/bin/eslint.js --ext .js ." - }, - "dependencies": { - "lodash": "^4.17.4" - }, - "devDependencies": { - "babel-eslint": "^7.2.3", - "eslint": "^4.7.2", - "eslint-plugin-import": "^2.7.0", - "eslint-plugin-lodash": "^2.4.5", - "eslint-plugin-promise": "^3.5.0", - "serverless": "^1.52.2" - } -} diff --git a/cla-frontend-contributor-console/Makefile b/cla-frontend-contributor-console/Makefile deleted file mode 100644 index c36f4fef0..000000000 --- a/cla-frontend-contributor-console/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT -.PHONY: setup -setup: - yarn install-frontend - -.PHONY: run -run: - cd src; \ - npm run preserve:${STAGE}; \ - npm run serve:${STAGE}; \ - -.PHONY: deploy -deploy: - yarn deploy -s ${STAGE} -r us-east-1 -c diff --git a/cla-frontend-contributor-console/edge/.gitignore b/cla-frontend-contributor-console/edge/.gitignore deleted file mode 100644 index d185a4baf..000000000 --- a/cla-frontend-contributor-console/edge/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright The Linux Foundation and each contributor to CommunityBridge. -# SPDX-License-Identifier: MIT -dist diff --git a/cla-frontend-contributor-console/edge/jasmine.json b/cla-frontend-contributor-console/edge/jasmine.json deleted file mode 100644 index aec5a224e..000000000 --- a/cla-frontend-contributor-console/edge/jasmine.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "spec_dir": "src", - "spec_files": ["**/*[sS]pec.js"], - "stopSpecOnExpectationFailure": false -} diff --git a/cla-frontend-contributor-console/edge/package.json b/cla-frontend-contributor-console/edge/package.json deleted file mode 100644 index c9b992a58..000000000 --- a/cla-frontend-contributor-console/edge/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "edge", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "prebuild": "yarn install", - "build": "webpack", - "pretest": "yarn install", - "test": "./node_modules/.bin/jasmine --config=jasmine.json" - }, - "dependencies": { - "graceful-fs": "^4.2.6" - }, - "resolutions": { - "node-sass": "4.13.1", - "mem": "^4.0.0", - "yargs-parser": "13.1.2" - }, - "devDependencies": { - "babel-core": "^6.26.0", - "babel-loader": "^7.1.2", - "jasmine": "^3.1.0", - "webpack": "^3.11.0" - } -} diff --git a/cla-frontend-contributor-console/edge/security-headers.js b/cla-frontend-contributor-console/edge/security-headers.js deleted file mode 100644 index 6ee3be085..000000000 --- a/cla-frontend-contributor-console/edge/security-headers.js +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -function getHeaders(env, isDevServer) { - return { - 'X-Content-Type-Options': 'nosniff', - 'X-Frame-Options': 'DENY', - 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', - 'X-XSS-Protection': '1', - 'Referrer-Policy': 'no-referrer', - 'Content-Security-Policy': generateCSP(env, isDevServer), - 'Cache-Control': 's-maxage=31536000' - }; -} - -function getSources(environmentSources, sourceType) { - if (environmentSources[sourceType] === undefined) { - return []; - } - return environmentSources[sourceType].filter((source) => { - return typeof source === 'string'; - }); -} - -function generateCSP(env, isDevServer) { - const SELF = "'self'"; - const UNSAFE_INLINE = "'unsafe-inline'"; - const UNSAFE_EVAL = "'unsafe-eval'"; - const NONE = "'none'"; - - let connectSources = [ - SELF, - 'https://linuxfoundation-dev.auth0.com/', - 'https://linuxfoundation-staging.auth0.com/', - 'https://sso.linuxfoundation.org/', - 'https://api.dev.lfcla.com/', - 'https://api.lfcla.dev.platform.linuxfoundation.org/', - 'https://api.staging.lfcla.com/', - 'https://api.lfcla.staging.platform.linuxfoundation.org/', - 'https://api.lfcla.com/', - 'https://api.easycla.lfx.linuxfoundation.org/', - 'https://communitybridge.org/', - 'https://api-gw.dev.platform.linuxfoundation.org/', - 'https://api-gw.staging.platform.linuxfoundation.org/', - 'https://api-gw.platform.linuxfoundation.org/' - ]; - - let scriptSources = [SELF, UNSAFE_EVAL, UNSAFE_INLINE, - 'https://cdn.dev.platform.linuxfoundation.org/lfx-header-no-zone.js', - 'https://cdn.staging.platform.linuxfoundation.org/lfx-header-no-zone.js', - 'https://cdn.platform.linuxfoundation.org/lfx-header-no-zone.js', - 'https://cdn.dev.platform.linuxfoundation.org/lfx-footer-no-zone.js', - 'https://cdn.staging.platform.linuxfoundation.org/lfx-footer-no-zone.js', - 'https://cdn.platform.linuxfoundation.org/lfx-footer-no-zone.js' - ]; - - let styleSources = [SELF, UNSAFE_INLINE, 'https://communitybridge.org/', 'https://lfx.linuxfoundation.org/']; - - if (isDevServer) { - connectSources = [...connectSources, 'https://localhost:8100/sockjs-node/', 'wss://localhost:8100/sockjs-node/']; - // The webpack dev server uses system js which violates the unsafe-eval exception. This doesn't happen in the - // production AOT build. - // The development build needs unsafe inline assets. - scriptSources = [...scriptSources, UNSAFE_EVAL]; - } - - const CSP_SOURCES = env ? env.CSP_SOURCES : undefined; - const environmentSources = JSON.parse(CSP_SOURCES || '{}'); - - const sources = { - 'default-src': [NONE], - 'img-src': ['*'], // allow all sources - /* - 'img-src': [ - SELF, - 'data:', - '*', - // 'https://s3.amazonaws.com/cla-project-logo-dev/', // project logos - // 'https://s3.amazonaws.com/cla-project-logo-staging/', // project logos - // 'https://s3.amazonaws.com/cla-project-logo-prod/', // project logos - // 'https://s3.amazonaws.com/lf-master-project-logos-prod/', // project logos - // 'https://lf-master-project-logos-prod.s3.us-east-2.amazonaws.com/', // project logos - // 'https://s.gravatar.com/', // my profile user logos - // 'https://lh3.googleusercontent.com/', // my profile user logos - // 'https://platform-logos-myprofile-api-dev.s3.us-east-2.amazonaws.com/', // my profile user logos - // 'https://platform-logos-myprofile-api-staging.s3.us-east-2.amazonaws.com/', // my profile user logos - // 'https://platform-logos-myprofile-api-prod.s3.us-east-2.amazonaws.com/', // my profile user logos - // 'https://avatars3.githubusercontent.com/', // my profile user logos - // 'https://cdn.platform.linuxfoundation.org/', // cdn for the LF favicon: https://cdn.platform.linuxfoundation.org/assets/lf-favicon.png - ], - */ - 'script-src': scriptSources, - 'style-src': styleSources, // Unfortunately using Angular basically requires inline styles. - 'font-src': [SELF, 'data:', 'https://communitybridge.org/'], - 'connect-src': connectSources, - 'frame-ancestors': [NONE], - 'form-action': [NONE], - 'worker-src': [SELF, 'blob:'], - 'base-uri': [SELF], - // frame-src restricts what iframe's you can put on your website - 'frame-src': [ - SELF, - 'data:', - 'https://sso.linuxfoundation.org/', - 'https://cla-signature-files-dev.s3.amazonaws.com/', - 'https://s3.amazonaws.com/cla-project-logo-dev/', - 'https://cla-signature-files-staging.s3.amazonaws.com/', - 'https://s3.amazonaws.com/cla-project-logo-staging/', - 'https://cla-signature-files-prod.s3.amazonaws.com/', - 'https://s3.amazonaws.com/cla-project-logo-prod/', - 'https://linuxfoundation-dev.auth0.com', - 'https://linuxfoundation-staging.auth0.com', - 'https://linuxfoundation.auth0.com' - ], - 'child-src': [], - 'media-src': [], - 'manifest-src': [SELF], - 'object-src': ['data:', '*'] - }; - - return Object.entries(sources) - .map((keyValuePair) => { - const additionalSources = getSources(environmentSources, keyValuePair[0]); - return [keyValuePair[0], [...keyValuePair[1], ...additionalSources]]; - }) - .filter((keyValuePair) => keyValuePair[1].length !== 0) - .map((keyValuePair) => { - const entry = keyValuePair[1].join(' '); - return `${keyValuePair[0]} ${entry};`; - }) - .join(' '); -} - -module.exports = getHeaders; diff --git a/cla-frontend-contributor-console/edge/src/add-headers.js b/cla-frontend-contributor-console/edge/src/add-headers.js deleted file mode 100644 index ceffb1993..000000000 --- a/cla-frontend-contributor-console/edge/src/add-headers.js +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -exports.addHeaders = function(event, headerList) { - const { response } = event.Records[0].cf; - const { headers } = response; - - Object.keys(headerList).forEach((headerName) => { - const headerValue = headerList[headerName]; - headers[headerName.toLowerCase()] = [ - { - key: headerName, - value: headerValue - } - ]; - }); - - return response; -}; diff --git a/cla-frontend-contributor-console/edge/src/add-headers.spec.js b/cla-frontend-contributor-console/edge/src/add-headers.spec.js deleted file mode 100644 index f517449a1..000000000 --- a/cla-frontend-contributor-console/edge/src/add-headers.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const handler = require('./add-headers'); - -describe('addHeaders', () => { - const EVENT = { - Records: [ - { - cf: { - response: { - headers: { - host: [ - { - value: 'd123.cf.net', - key: 'Host' - } - ] - }, - clientIp: '2001:cdba::3257:9652', - uri: '/index.html', - method: 'GET' - }, - config: { - distributionId: 'EXAMPLE' - } - } - } - ] - }; - - it('returns a response object with added headers', () => { - const headers = { - 'Some-Header-One': '1', - 'Some-Header-Two': '2' - }; - const output = handler.addHeaders(EVENT, headers); - expect(output).toEqual({ - headers: { - host: [ - { - value: 'd123.cf.net', - key: 'Host' - } - ], - 'some-header-one': [ - { - value: '1', - key: 'Some-Header-One' - } - ], - 'some-header-two': [ - { - value: '2', - key: 'Some-Header-Two' - } - ] - }, - clientIp: '2001:cdba::3257:9652', - uri: '/index.html', - method: 'GET' - }); - }); -}); diff --git a/cla-frontend-contributor-console/edge/src/index.js b/cla-frontend-contributor-console/edge/src/index.js deleted file mode 100644 index 33eb978e8..000000000 --- a/cla-frontend-contributor-console/edge/src/index.js +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const addHeaders = require('./add-headers'); -const setCacheControl = require('./set-cache-control'); - -exports.handler = (event, context, callback) => { - const headers = HEADERS; - const resourcesNotToCache = ['/index.html', '/']; - const resource = event.Records[0].cf.request.uri; - const timeToLive = 60 * 60 * 24 * 365; - const modifiedHeaders = setCacheControl.setCacheControl(headers, resource, resourcesNotToCache, timeToLive); - const response = addHeaders.addHeaders(event, modifiedHeaders); - callback(null, response); -}; diff --git a/cla-frontend-contributor-console/edge/src/set-cache-control.js b/cla-frontend-contributor-console/edge/src/set-cache-control.js deleted file mode 100644 index abe5f3936..000000000 --- a/cla-frontend-contributor-console/edge/src/set-cache-control.js +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -/** - * Splits a header property in the format 'key=value', or just 'key', and returns an object in the format {key: value}. - * @param {string} assignment - */ -function splitAssignmentPair(assignment) { - const parts = assignment.split('=').map((value) => value.trim()); - - const obj = {}; - if (parts.length == 1) { - obj[parts[0]] = true; - } else { - obj[parts[0]] = parts[1]; - } - return obj; -} - -/** - * Splits a comma seperated header into a list of key-value pairs. - * @param {string} headerValue - */ -function splitHeaderValue(headerValue) { - return headerValue - .split(',') - .map((value) => splitAssignmentPair(value)) - .reduce((previous, current) => Object.assign(previous, current), {}); -} - -/** - * Modifies the Cache-Control header on a per resource basis. - * @param {Object.} headers - A list of preset headers. - * @param {String} currentResourceName - The name of the current resource. - * @param {Array} resourcesNotToCache - A list of resources not to cache. - * @param {Number} timeToLive - The time to cache objects for, if they are to be cached. - */ -exports.setCacheControl = function(headers, currentResourceName, resourcesNotToCache, timeToLive) { - const existingCacheControl = headers['Cache-Control'] !== undefined ? headers['Cache-Control'] : ''; - const cacheValues = splitHeaderValue(existingCacheControl); - const sMaxAge = cacheValues['s-maxage']; - - let newCacheControl = ''; - if (resourcesNotToCache.includes(currentResourceName)) { - newCacheControl = 'no-cache, no-store, must-revalidate'; - } else { - newCacheControl = `max-age=${timeToLive}`; - } - if (sMaxAge) { - newCacheControl = `s-maxage=${sMaxAge}, ${newCacheControl}`; - } - - return Object.assign({}, headers, { 'Cache-Control': newCacheControl }); -}; diff --git a/cla-frontend-contributor-console/edge/src/set-cache-control.spec.js b/cla-frontend-contributor-console/edge/src/set-cache-control.spec.js deleted file mode 100644 index 9cee1f266..000000000 --- a/cla-frontend-contributor-console/edge/src/set-cache-control.spec.js +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const handler = require('./set-cache-control'); - -describe('setCacheControl', () => { - it("turns on Cache-Control with max-age when resource isn't in filesNotToCache", () => { - const headers = { - 'Some-Header-One': '1', - 'Some-Header-Two': '2' - }; - const filesNotToCache = ['index.html']; - const timeToLive = 60 * 60 * 24 * 365; - - const result = handler.setCacheControl(headers, 'some-image.png', filesNotToCache, timeToLive); - expect(result).toEqual({ - ...headers, - 'Cache-Control': `max-age=${timeToLive}` - }); - }); - - it('turns off Cache-Control when resource is in filesNotToCache', () => { - const headers = { - 'Some-Header-One': '1', - 'Some-Header-Two': '2' - }; - const filesNotToCache = ['index.html']; - const timeToLive = 60 * 60 * 24 * 365; - - const result = handler.setCacheControl(headers, 'index.html', filesNotToCache, timeToLive); - expect(result).toEqual({ - ...headers, - 'Cache-Control': `no-cache, no-store, must-revalidate` - }); - }); - - it("doesn't change the s-maxage property in an existing Cache-Control header", () => { - const headers = { - 'Some-Header-One': '1', - 'Some-Header-Two': '2', - 'Cache-Control': 's-maxage=100' - }; - const filesNotToCache = ['index.html']; - const timeToLive = 60 * 60 * 24 * 365; - - const result = handler.setCacheControl(headers, 'index.html', filesNotToCache, timeToLive); - expect(result).toEqual({ - ...headers, - 'Cache-Control': `s-maxage=100, no-cache, no-store, must-revalidate` - }); - }); - - it('overrides max-age property in an existing Cache-Control header', () => { - const headers = { - 'Some-Header-One': '1', - 'Some-Header-Two': '2', - 'Cache-Control': 'max-age=100' - }; - const filesNotToCache = ['index.html']; - const timeToLive = 60 * 60 * 24 * 365; - - const result = handler.setCacheControl(headers, 'some-file.html', filesNotToCache, timeToLive); - expect(result).toEqual({ - ...headers, - 'Cache-Control': `max-age=${timeToLive}` - }); - }); -}); diff --git a/cla-frontend-contributor-console/edge/webpack.config.js b/cla-frontend-contributor-console/edge/webpack.config.js deleted file mode 100644 index 4d30f7d47..000000000 --- a/cla-frontend-contributor-console/edge/webpack.config.js +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const webpack = require('webpack'); -const path = require('path'); - -const GetSecurityHeaders = require('./security-headers.js'); - -module.exports = (env) => { - const securityHeaders = GetSecurityHeaders(env, false); - - return { - target: 'node', - context: path.join(__dirname, '/src'), - entry: { - index: ['./index.js'] - }, - output: { - path: path.join(__dirname, './dist'), - filename: '[name].js', - libraryTarget: 'umd' - }, - module: { - rules: [ - { - test: /\.js$/, - loaders: ['babel-loader'] - } - ] - }, - plugins: [ - new webpack.DefinePlugin({ - HEADERS: JSON.stringify(securityHeaders) - }) - ] - }; -}; diff --git a/cla-frontend-contributor-console/edge/yarn.lock b/cla-frontend-contributor-console/edge/yarn.lock deleted file mode 100644 index 907fac2cf..000000000 --- a/cla-frontend-contributor-console/edge/yarn.lock +++ /dev/null @@ -1,3487 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -acorn-dynamic-import@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" - integrity sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ= - dependencies: - acorn "^4.0.3" - -acorn@^4.0.3: - version "4.0.13" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" - integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= - -acorn@^5.0.0: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -ajv-keywords@^3.1.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.1.tgz#b83ca89c5d42d69031f424cad49aada0236c6957" - integrity sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA== - -ajv@^6.1.0: - version "6.12.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" - integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.12.3: - version "6.12.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" - integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - -async@^2.1.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" - integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== - -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.26.0: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-loader@^7.1.2: - version "7.1.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" - integrity sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw== - dependencies: - find-cache-dir "^1.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= - dependencies: - babel-runtime "^6.22.0" - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" - integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11" - integrity sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA== - dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.2" - inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - -camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chokidar@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" - integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - -convert-source-map@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== - dependencies: - safe-buffer "~5.1.1" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-js@^2.4.0, core-js@^2.5.0: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -elliptic@^6.0.0, elliptic@^6.5.2: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -enhanced-resolve@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" - integrity sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24= - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - object-assign "^4.0.1" - tapable "^0.2.7" - -errno@^0.1.3: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== - dependencies: - prr "~1.0.1" - -error-ex@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-map@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= - dependencies: - d "1" - es5-ext "~0.10.14" - -es6-symbol@^3.1.1, es6-symbol@~3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -es6-weak-map@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - -escape-string-regexp@^1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" - integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= - dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= - dependencies: - d "1" - es5-ext "~0.10.14" - -events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-cache-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" - integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8= - dependencies: - commondir "^1.0.1" - make-dir "^1.0.0" - pkg-dir "^2.0.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.0.0, find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -fstream@^1.0.0, fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -globule@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" - integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -graceful-fs@^4.1.11, graceful-fs@^4.1.2: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -graceful-fs@^4.2.6: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -in-publish@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" - integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -invariant@^2.2.2: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jasmine-core@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" - integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== - -jasmine@^3.1.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" - integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== - dependencies: - glob "^7.1.4" - jasmine-core "~3.5.0" - -js-base64@^2.1.8: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== - -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - -json-loader@^0.5.4: - version "0.5.7" - resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" - integrity sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" - -loader-runner@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@~4.17.10: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -mem@^1.1.0, mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - -memory-fs@^0.4.0, memory-fs@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -meow@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - -mimic-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@^3.0.4, minimatch@~3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -nan@^2.12.1, nan@^2.13.2: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -neo-async@^2.5.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -node-gyp@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" - integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "^2.87.0" - rimraf "2" - semver "~5.3.0" - tar "^2.0.0" - which "1" - -node-libs-browser@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-sass@4.13.1: - version "4.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" - integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw== - dependencies: - async-foreach "^0.1.3" - chalk "^1.1.1" - cross-spawn "^3.0.0" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - in-publish "^2.0.0" - lodash "^4.17.15" - meow "^3.7.0" - mkdirp "^0.5.1" - nan "^2.13.2" - node-gyp "^3.8.0" - npmlog "^4.0.0" - request "^2.88.0" - sass-graph "^2.2.4" - stdout-stream "^1.4.0" - "true-case-path" "^1.0.2" - -"nopt@2 || 3": - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= - dependencies: - abbrev "1" - -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -os-locale@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" - integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== - dependencies: - execa "^0.7.0" - lcid "^1.0.0" - mem "^1.1.0" - -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" - -pbkdf2@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" - integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.3, readable-stream@^2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== - dependencies: - picomatch "^2.2.1" - -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -request@^2.87.0, request@^2.88.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.10.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= - dependencies: - align-text "^0.1.1" - -rimraf@2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-graph@^2.2.4: - version "2.2.6" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.6.tgz#09fda0e4287480e3e4967b72a2d133ba09b8d827" - integrity sha512-MKuEYXFSGuRSi8FZ3A7imN1CeVn9Gpw0/SFJKdL1ejXJneI9a5rwlEZrKejhEFAA3O6yr3eIyl/WuvASvlT36g== - dependencies: - glob "^7.0.0" - lodash "^4.0.0" - scss-tokenizer "^0.2.3" - yargs "^7.0.0" - -scss-tokenizer@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" - integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= - dependencies: - js-base64 "^2.1.8" - source-map "^0.4.2" - -"semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= - -set-blocking@^2.0.0, set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== - dependencies: - readable-stream "^2.0.1" - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" - integrity sha1-vnoN5ITexcXN34s9WRJQRJEvY1s= - dependencies: - has-flag "^2.0.0" - -tapable@^0.2.7: - version "0.2.9" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.9.tgz#af2d8bbc9b04f74ee17af2b4d9048f807acd18a8" - integrity sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A== - -tar@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== - dependencies: - block-stream "*" - fstream "^1.0.12" - inherits "2" - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== - dependencies: - setimmediate "^1.0.4" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - -"true-case-path@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" - integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== - dependencies: - glob "^7.1.2" - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== - -uglify-js@^2.8.29: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - integrity sha1-KcVzMUgFe7Th913zW3qcty5qWd0= - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc= - -uglifyjs-webpack-plugin@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" - integrity sha1-uVH0q7a9YX5m9j64kUmOORdj4wk= - dependencies: - source-map "^0.5.6" - uglify-js "^2.8.29" - webpack-sources "^1.0.1" - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.4.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== - dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.0" - watchpack-chokidar2 "^2.0.0" - -webpack-sources@^1.0.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.12.0.tgz#3f9e34360370602fcf639e97939db486f4ec0d74" - integrity sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ== - dependencies: - acorn "^5.0.0" - acorn-dynamic-import "^2.0.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - async "^2.1.2" - enhanced-resolve "^3.4.0" - escope "^3.6.0" - interpret "^1.0.0" - json-loader "^0.5.4" - json5 "^0.5.1" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - mkdirp "~0.5.0" - node-libs-browser "^2.0.0" - source-map "^0.5.3" - supports-color "^4.2.1" - tapable "^0.2.7" - uglifyjs-webpack-plugin "^0.4.6" - watchpack "^1.4.0" - webpack-sources "^1.0.1" - yargs "^8.0.2" - -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@1, which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" - integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yargs-parser@13.1.2, yargs-parser@5.0.0-security.0, yargs-parser@^7.0.0: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6" - integrity sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g== - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "5.0.0-security.0" - -yargs@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" - integrity sha1-YpmpBVsc78lp/355wdkY3Osiw2A= - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" diff --git a/cla-frontend-contributor-console/package.json b/cla-frontend-contributor-console/package.json deleted file mode 100644 index b8f0ac3f8..000000000 --- a/cla-frontend-contributor-console/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "cla-frontend-contributor-console", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "deploy:dev": "yarn sls deploy --stage=dev --cloudfront=true && yarn sls client deploy --stage=dev --cloudfront=true --no-confirm --no-policy-change --no-config-change && yarn sls cloudfrontInvalidate --stage=dev --region=us-east-1 --cloudfront=true", - "deploy:staging": "yarn sls deploy --stage=staging --cloudfront=true && yarn sls client deploy --stage=staging --cloudfront=true --no-confirm --no-policy-change --no-config-change && yarn sls cloudfrontInvalidate --stage=staging --region=us-east-1 --cloudfront=true", - "deploy:prod": "SLS_DEBUG=* yarn sls client deploy --stage='prod' --region='us-east-1' --cloudfront=true --no-confirm --no-policy-change --no-config-change --verbose && SLS_DEBUG=* yarn sls deploy --stage='prod' --region='us-east-1' --cloudfront=true --verbose && SLS_DEBUG=* yarn sls cloudfrontInvalidate --stage='prod' --region='us-east-1' --cloudfront='true' --verbose", - "sls": "../node_modules/serverless/bin/serverless.js", - "info:dev": "../node_modules/serverless/bin/serverless.js info --stage=dev --region=us-east-1", - "info:stating": "../node_modules/serverless/bin/serverless.js info --stage=stating --region=us-east-1", - "info:prod": "../node_modules/serverless/bin/serverless.js info --stage=prod --region=us-east-1", - "install-frontend": "../scripts/install-frontend.sh" - }, - "dependencies": { - "graceful-fs": "^4.2.6", - "ionic": "^3.20.0", - "serverless": "^2.15.0", - "serverless-cloudfront-invalidate": "^1.2.1", - "serverless-finch": "^2.6.0", - "serverless-plugin-tracing": "^2.0.0", - "serverless-pseudo-parameters": "^2.5.0" - }, - "resolutions": { - "axios": "^0.21.4", - "bl": "^2.2.1", - "braces": "^2.3.1", - "glob-parent": "^5.1.2", - "ini": "^1.3.7", - "netmask": "^2.0.1", - "normalize-url": "^4.5.1", - "pac-resolver": "^5.0.0", - "set-value": "^4.0.1", - "tar": "^6.1.9", - "trim-newlines": "^3.0.1", - "ws": "^7.4.6", - "xmlhttprequest-ssl": "^1.6.2" - } -} diff --git a/cla-frontend-contributor-console/serverless.yml b/cla-frontend-contributor-console/serverless.yml deleted file mode 100644 index 935e03a37..000000000 --- a/cla-frontend-contributor-console/serverless.yml +++ /dev/null @@ -1,280 +0,0 @@ -service: cla-frontend-ic-4 - -# Only package lambda@edge function. -package: - exclude: - - "**" - include: - - edge/dist/* - -provider: - name: aws - #runtime: nodejs8.10 # https://aws.amazon.com/about-aws/whats-new/2018/05/lambda-at-edge-adds-support-for-node-js-v8-10/ - runtime: nodejs12.x - region: us-east-1 # Region can't be configurable, lambda@edge is us-east-1 only. - deploymentBucket: - serverSideEncryption: AES256 # Make sure items are uploaded encrypted. - role: EdgeRole - - tracing: - apiGateway: true - lambda: true - - iamRoleStatements: - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: "*" - -plugins: - # Serverless Finch does s3 uploading. Called with 'sls client deploy'. - # Also allows bucket removal with 'sls client remove'. - - serverless-finch - # Automatically versions and updates the lambda@edge function. - - serverless-lambda-version - # Automatically invalidates cloudfront after frontend bucket is deployed - - serverless-cloudfront-invalidate - - serverless-plugin-tracing - -custom: - project: ${file(../project-vars.yml):projectIdentifier} - client: # Configurations for serverless finch. - bucketName: ${self:custom.project}-${opt:stage}-${self:service} - distributionFolder: src/www - indexDocument: index.html - # Because our application is a Single Page Application, we always want our index - # documents to handle 404/403 urls. - errorDocument: index.html - manageResources: false - - # CloudFront invalidation plugin configuration - cloudfrontInvalidate: - # Grab the distribution ID key from the output section - distributionIdKey: 'CloudfrontDistributionId' - items: # one or more paths required - - '/*' - certificate: - arn: - # From env Certificate Manager - - # currently, PROD is managed externally, DEV and STAGING are still managed by serverless - dev: arn:aws:acm:us-east-1:395594542180:certificate/b6efe7d2-d8c3-4d27-a582-341a8df70ccd - staging: arn:aws:acm:us-east-1:844390194980:certificate/dc289a7d-8410-452e-860b-a4ccadd99fad - prod: arn:aws:acm:us-east-1:716487311010:certificate/0fad9da1-45c3-46ba-95bb-36e62a20a572 - other: arn:aws:acm:us-east-1:395594542180:certificate/b6efe7d2-d8c3-4d27-a582-341a8df70ccd - product: - domain: - name: - dev: 'contributor.lfcla.dev.platform.linuxfoundation.org' - staging: 'contributor.lfcla.staging.platform.linuxfoundation.org' - prod: 'contributor.v1.easycla.lfx.linuxfoundation.org' - other: 'contributor.dev.lfcla.com' - alt: - dev: 'contributor.dev.lfcla.com' - staging: 'contributor.staging.lfcla.com' - prod: 'contributor.v1.easycla.lfx.linuxfoundation.org' - other: 'contributor.dev.lfcla.com' - ses_from_email: - dev: admin@dev.lfcla.com - staging: admin@staging.lfcla.com - prod: admin@lfx.linuxfoundation.org - -functions: - # Configure a lambda@edge handler. Lambda@edge is a function that adds http headers to - # cloudfront requests. We use it to enforce HTTP security best practices. - clientEdge: - handler: edge/dist/index.handler - memorySize: 128 # This is the maximum memory size for lambda@edge functions - timeout: 1 # This is the maximum execution time for lambda@edge functions - -resources: - Conditions: - # https://gist.github.com/DavidWells/be078deef45f8cb2e280ccc7af947392 - isProd: { "Fn::Equals": [ "${env:STAGE}", "prod" ] } - isStaging: { "Fn::Equals": [ "${env:STAGE}", "staging" ] } - isDev: { "Fn::Equals": [ "${env:STAGE}", "dev" ] } - # true when a TSL certificate should be created by serverless (false created externally) - ShouldGenerateCertificate: - Fn::Not: [Fn::Equals: ["${env:STAGE}", "prod"]] - - Resources: - # The bucket the website is uploaded to. We make sure to turn on AES256 encryption, which - # is best practice. - WebsiteDeploymentBucket: - Type: AWS::S3::Bucket - Properties: - AccessControl: Private - BucketEncryption: - ServerSideEncryptionConfiguration: - - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 - BucketName: ${self:custom.project}-${opt:stage}-${self:service} - - # Policy that only exposes bucket to cloudfront with proper - # Origin Access Identity - WebsiteDeploymentBucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: - Ref: WebsiteDeploymentBucket - PolicyDocument: - Statement: - - Action: - - "s3:GetObject" - Effect: Allow - Resource: - "Fn::Join": - - "" - - - "arn:aws:s3:::" - - Ref: WebsiteDeploymentBucket - - "/*" - Principal: - AWS: - "Fn::Join": - - " " - - - "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity" - - Ref: WebsiteOriginAccessIdentity - - WebsiteOriginAccessIdentity: - Type: AWS::CloudFront::CloudFrontOriginAccessIdentity - Properties: - CloudFrontOriginAccessIdentityConfig: - Comment: "CloudFrontOriginAccessIdentity for ${self:service}-${self:provider.stage}" - - # The cloudfront distribution wraps around our static website S3 bucket. Using a CDN to host our SPA is good - # practice, and also lets us set custom headers using lambda@edge. - CloudfrontDistribution: - Type: AWS::CloudFront::Distribution - DependsOn: - - WebsiteDeploymentBucket - Properties: - DistributionConfig: - Enabled: true - Aliases: - Fn::If: - - isProd - - - ${self:custom.product.domain.name.${opt:stage}, self:custom.product.domain.name.other} - - - ${self:custom.product.domain.name.${opt:stage}, self:custom.product.domain.name.other} - - ${self:custom.product.domain.alt.${opt:stage}, self:custom.product.domain.alt.other} - ViewerCertificate: - Fn::If: - - ShouldGenerateCertificate - - AcmCertificateArn: - Ref: Cert - # The distribution accepts HTTPS connections from only viewers that support server name indication - # Recommended, most browsers and clients released after 2010 support SNI. - SslSupportMethod: sni-only - # Specify the security policy that you want CloudFront to use for HTTPS connections - # Recommend that you specify TLSv1.2_2018 unless your viewers are using browsers or devices that don’t support TLSv1.2 - # Allowed Values: SSLv3 | TLSv1 | TLSv1.1_2016 | TLSv1.2_2018 | TLSv1_2016 - MinimumProtocolVersion: TLSv1.2_2018 - - AcmCertificateArn: ${self:custom.certificate.arn.${opt:stage}, self:custom.certificate.arn.other} - # The distribution accepts HTTPS connections from only viewers that support server name indication - # Recommended, most browsers and clients released after 2010 support SNI. - SslSupportMethod: sni-only - # Specify the security policy that you want CloudFront to use for HTTPS connections - # Recommend that you specify TLSv1.2_2018 unless your viewers are using browsers or devices that don’t support TLSv1.2 - # Allowed Values: SSLv3 | TLSv1 | TLSv1.1_2016 | TLSv1.2_2018 | TLSv1_2016 - MinimumProtocolVersion: TLSv1.2_2018 - Origins: - - DomainName: { "Fn::GetAtt": [ WebsiteDeploymentBucket, DomainName ] } - Id: - Ref: WebsiteDeploymentBucket - S3OriginConfig: - OriginAccessIdentity: - "Fn::Join": - - "" - - - "origin-access-identity/cloudfront/" - - Ref: WebsiteOriginAccessIdentity - # Routes besides / will result in S3 serving a 403 - # Redirect all routes back to the SPA where routes should - # be handled - CustomErrorResponses: - - - ErrorCode: 403 - ErrorCachingMinTTL: 1 - ResponseCode: 200 - ResponsePagePath: '/index.html' - HttpVersion: http2 - DefaultRootObject: index.html - DefaultCacheBehavior: - AllowedMethods: - - GET - - HEAD - # Links our lambda@edge function, (which adds HTTPS our security headers), to the cloudfront distribution. - LambdaFunctionAssociations: - - EventType: 'viewer-response' - # Cloudfront requires a lambda@edge arn in the format - # 'arn:aws:lambda:${region}:${accountNumber}:function:${lambdaName}:${explicitVersion}' - # We use the serverless-lambda-version plugin to automatically update this every time there is a change. - LambdaFunctionARN: ClientEdgeLambdaFunction - Compress: true # Turns on gzipping - #DefaultTTL: 86400 # Defaults to a day if no Cache-Control header is set. - DefaultTTL: 600 # 10 minutes only due to users seeing a lot of stale cache issues after release (even after invalidating - MinTTL: 0 - #MaxTTL: 31536000 # Can keep the file in the cloudfront cache for a maximum of a year. - MaxTTL: 600 # 10 minutes only due to users seeing a lot of stale cache issues after release (even after invalidating - TargetOriginId: - Ref: WebsiteDeploymentBucket - ForwardedValues: - QueryString: true - Cookies: - Forward: none - ViewerProtocolPolicy: redirect-to-https - PriceClass: PriceClass_100 # Cheapest class, only hosts content at North American cloudfront locations. - - # Severless usually generates our roles out of the box, but lambda@edge support is lacking, so we have to create - # our own. This role can assume the edgelambda.amazonaws.com role, (the lambda won't run without it). - EdgeRole: - Type: AWS::IAM::Role - Properties: - RoleName: ${self:custom.project}-${opt:stage}-${self:service}-edge-role - Path: / - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - - edgelambda.amazonaws.com # This is the important part of this role. - Action: - - sts:AssumeRole - Policies: - - PolicyName: LogGroupPolicy # Permissions to access Lambda@edge log groups. - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:DescribeLogGroups - - logs:DescribeLogStreams - - logs:PutLogEvents - - logs:GetLogEvents - - logs:FilterLogEvents - Resource: - - "Fn::Join": - - ":" - - - arn:aws:logs - - "Ref": "AWS::Region" - - "Ref": "AWS::AccountId" - - log-group - - "*" - ManagedPolicyArns: - - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - - Cert: - Type: AWS::CertificateManager::Certificate - Condition: ShouldGenerateCertificate - Properties: - DomainName: ${self:custom.product.domain.name.${opt:stage}, self:custom.product.domain.name.other} - SubjectAlternativeNames: - - ${self:custom.product.domain.alt.${opt:stage}, self:custom.product.domain.alt.other} - ValidationMethod: DNS - - Outputs: - CloudfrontDistributionId: - Value: - Ref: CloudfrontDistribution diff --git a/cla-frontend-contributor-console/src/.editorconfig b/cla-frontend-contributor-console/src/.editorconfig deleted file mode 100755 index 51873bc7f..000000000 --- a/cla-frontend-contributor-console/src/.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs -# editorconfig.org - -root = true - -[*] -indent_style = space -indent_size = 2 - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false \ No newline at end of file diff --git a/cla-frontend-contributor-console/src/.io-config.json b/cla-frontend-contributor-console/src/.io-config.json deleted file mode 100755 index 141a2bbf3..000000000 --- a/cla-frontend-contributor-console/src/.io-config.json +++ /dev/null @@ -1 +0,0 @@ -{"app_id":"3a1139c5"} \ No newline at end of file diff --git a/cla-frontend-contributor-console/src/config.xml b/cla-frontend-contributor-console/src/config.xml deleted file mode 100755 index 973d6de92..000000000 --- a/cla-frontend-contributor-console/src/config.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - cla-app - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cla-frontend-contributor-console/src/config/scripts/prefetch-ssm.js b/cla-frontend-contributor-console/src/config/scripts/prefetch-ssm.js deleted file mode 100644 index 7115da515..000000000 --- a/cla-frontend-contributor-console/src/config/scripts/prefetch-ssm.js +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const fs = require('fs'); -const RetrieveSSMValues = require('./read-ssm'); -const configVarArray = ['auth0-clientId', 'auth0-domain', 'cla-api-url', 'corp-console-link', 'logo-url', 'lfx-header', 'lfx-footer', 'lfx-header-enabled', 'landing-page','auth0-platform-api-gw']; -const region = 'us-east-1'; -const profile = process.env.AWS_PROFILE; -const stageEnv = process.env.STAGE_ENV; - -async function prefetchSSM() { - let result = {}; - console.log(`Start to fetch SSM values at ${stageEnv}...`); - result = await RetrieveSSMValues(configVarArray, stageEnv, region, profile); - - //test for local - // result['cla-api-url'] = 'http://localhost:5000'; - fs.writeFile(`./config/cla-env-config.json`, JSON.stringify(result), function (err) { - if (err) throw new Error(`Couldn't save SSM paramters to disk with error ${err}`); - console.log('Fetching completed...'); - }); -} - -prefetchSSM(); diff --git a/cla-frontend-contributor-console/src/config/scripts/read-local.js b/cla-frontend-contributor-console/src/config/scripts/read-local.js deleted file mode 100644 index a611c366e..000000000 --- a/cla-frontend-contributor-console/src/config/scripts/read-local.js +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -/** - * @param {string[]} variables - * @returns {{ [key:string]: string }} - */ -async function retrieveLocalConfigValues(variables, fileName) { - const localConfig = require(`../${fileName}`); - const parameterMap = {}; - variables.forEach((variable) => { - value = localConfig[variable]; - if (value === undefined) { - throw new Error(`Couldn't retrieve value from local config for ${variable}`); - } - parameterMap[variable] = localConfig[variable]; - }); - return parameterMap; -} - -module.exports = retrieveLocalConfigValues; diff --git a/cla-frontend-contributor-console/src/config/scripts/read-ssm.js b/cla-frontend-contributor-console/src/config/scripts/read-ssm.js deleted file mode 100644 index d3d515e51..000000000 --- a/cla-frontend-contributor-console/src/config/scripts/read-ssm.js +++ /dev/null @@ -1,79 +0,0 @@ -// @ts-check - -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT -const AWS = require('aws-sdk'); - -/** - * @param {string[]} variables - * @param {string} stage - * @param {string} region - * @param {string} profile - * @returns {Promise<{ [key:string]: string}>} - */ -async function retrieveSSMValues(variables, stage, region, profile) { - const scopedVariables = variables.map((param) => { - return `cla-${param}-${stage}`; - }); - - const result = await requestSSMParameters(scopedVariables, stage, region, profile); - const parameters = result.Parameters; - const error = result.$response.error; - if (error !== null) { - throw new Error( - `Couldn't retrieve SSM parameters for stage ${stage} in region ${region} using profile ${profile} - error ${error}` - ); - } - const scopedParams = createParameterMap(parameters, stage); - let params = {}; - Object.keys(scopedParams).forEach((key) => { - const param = scopedParams[key]; - key = key.replace('cla-', ''); - key = key.replace(`-${stage}`, ''); - params[key] = param; - }); - - variables.forEach((variable) => { - if (params[variable] === undefined) { - throw new Error( - `Missing SSM parameter with name ${variable} for stage ${stage} in region ${region} using profile ${profile}`, - ); - } - }); - return params; -} - -/** - * @param {string[]} variables - * @param {string} stage - * @param {string} region - */ -function requestSSMParameters(variables, stage, region, profile) { - AWS.config.credentials = new AWS.SharedIniFileCredentials({profile}); - const ssm = new AWS.SSM({region: region}); - - const ps = { - Names: variables, - WithDecryption: true - }; - - return ssm.getParameters(ps).promise(); -} - -/** - * @param {AWS.SSM.Parameter[]} parameters - * @param {string} stage - */ -function createParameterMap(parameters, stage) { - return parameters.filter((param) => param.Name.endsWith(`-${stage}`)) - .map((param) => { - const output = {}; - output[param.Name] = param.Value; - return output; - }) - .reduce((prev, current) => { - return {...prev, ...current}; - }, {}); -} - -module.exports = retrieveSSMValues; diff --git a/cla-frontend-contributor-console/src/config/webpack.config.js b/cla-frontend-contributor-console/src/config/webpack.config.js deleted file mode 100644 index ff0b4a2e9..000000000 --- a/cla-frontend-contributor-console/src/config/webpack.config.js +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -const { dev, prod } = require('@ionic/app-scripts/config/webpack.config'); -const webpack = require('webpack'); -const RetrieveLocalConfigValues = require('./scripts/read-local'); -const configVarArray = ['auth0-clientId', 'auth0-domain', 'cla-api-url']; -const stageEnv = process.env.STAGE_ENV; -/** - * This custom webpack config is deprecated, - * since we don't inject environment variables through webpack plugin. - * If we're going to reactivate this someday, go to src/package.json, - * add `"ionic_webpack": "./config/webpack.config.js"` at config block. - */ -module.exports = async (env) => { - // Here we hard code stage name, it's not perfect since if a new stage created/modified, we also need to change it. - const shouldReadFromSSM = - stageEnv !== undefined && - (stageEnv === 'staging' || stageEnv === 'prod' || stageEnv === 'qa' || stageEnv === 'dev'); - let configMap = {}; - - // Here in the future, we maybe want to use Enum class to replace hard-code file name as indicator. - if (shouldReadFromSSM) { - configMap = await RetrieveLocalConfigValues(configVarArray, `config-${stageEnv}.json`); - } else { - configMap = await RetrieveLocalConfigValues(configVarArray, 'config-local.json'); - } - - const claConfigPlugin = new webpack.DefinePlugin({ - webpackGlobalVars: { - CLA_API_URL: JSON.stringify(configMap['cla-api-url']), - AUTH0_DOMAIN: JSON.stringify(configMap['auth0-domain']), - AUTH0_CLIENT_ID: JSON.stringify(configMap['auth0-clientId']) - } - }); - - dev.plugins.push(claConfigPlugin); - prod.plugins.push(claConfigPlugin); - - return { - dev: dev, - prod: prod - }; -}; diff --git a/cla-frontend-contributor-console/src/ionic.config.json b/cla-frontend-contributor-console/src/ionic.config.json deleted file mode 100755 index 4d12d43f0..000000000 --- a/cla-frontend-contributor-console/src/ionic.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "cla-console-app", - "app_id": "3a1139c5", - "type": "ionic-angular", - "integrations": {} -} diff --git a/cla-frontend-contributor-console/src/ionic/app/app.component.ts b/cla-frontend-contributor-console/src/ionic/app/app.component.ts deleted file mode 100755 index ded4a60fb..000000000 --- a/cla-frontend-contributor-console/src/ionic/app/app.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component, ViewChild } from '@angular/core'; -import { Nav, Platform } from 'ionic-angular'; -import { StatusBar } from '@ionic-native/status-bar'; -import { SplashScreen } from '@ionic-native/splash-screen'; -import { ClaService } from '../services/cla.service'; -import { EnvConfig } from '../services/cla.env.utils'; - -import { AuthService } from '../services/auth.service'; -import { AuthPage } from '../pages/auth/auth'; -import { HttpClient } from '../services/http-client'; -import { KeycloakService } from '../services/keycloak/keycloak.service'; -import { LfxHeaderService } from '../services/lfx-header.service'; - -@Component({ - templateUrl: 'app.html', - providers: [] -}) -export class MyApp { - @ViewChild(Nav) nav: Nav; - - rootPage: any = AuthPage; - - constructor( - public platform: Platform, - public statusBar: StatusBar, - public splashScreen: SplashScreen, - public claService: ClaService, - public authService: AuthService, - public httpClient: HttpClient, - public keycloak: KeycloakService, - private lfxHeaderService: LfxHeaderService - ) { - this.initializeApp(); - - // Determine if we're running in a local services (developer) mode - the USE_LOCAL_SERVICES environment variable - // will be set to 'true', otherwise we're using normal services deployed in each environment - const localServicesMode = (process.env.USE_LOCAL_SERVICES || 'false').toLowerCase() === 'true'; - // Set true for local debugging using localhost (local ports set in claService) - this.claService.isLocalTesting(localServicesMode); - this.claService.setApiUrl(EnvConfig['cla-api-url']); - this.claService.setV4ApiUrl(EnvConfig['auth0-platform-api-gw']); - this.claService.setHttp(httpClient); - } - - ngOnInit() { - this.mountHeader(); - this.mountFooter(); - } - - initializeApp() { - this.platform.ready().then(() => { }); - } - - mountHeader() { - const script = document.createElement('script'); - script.setAttribute( - 'src', - EnvConfig['lfx-header'] - ); - document.head.appendChild(script); - } - - mountFooter() { - const script = document.createElement('script'); - script.setAttribute( - 'src', - EnvConfig['lfx-footer'] - ); - document.head.appendChild(script); - } - - openPage(page) { - // Set the nav root so back button doesn't show - this.nav.setRoot(page.component); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/app/app.html b/cla-frontend-contributor-console/src/ionic/app/app.html deleted file mode 100755 index 3d7474c1c..000000000 --- a/cla-frontend-contributor-console/src/ionic/app/app.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/cla-frontend-contributor-console/src/ionic/app/app.module.ts b/cla-frontend-contributor-console/src/ionic/app/app.module.ts deleted file mode 100755 index df1fde965..000000000 --- a/cla-frontend-contributor-console/src/ionic/app/app.module.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule, ErrorHandler } from '@angular/core'; -import { HttpModule } from '@angular/http'; -import { CurrencyPipe } from '@angular/common'; -import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular'; -import { StatusBar } from '@ionic-native/status-bar'; -import { SplashScreen } from '@ionic-native/splash-screen'; -import { ClaService } from '../services/cla.service'; -import { AuthService } from '../services/auth.service'; -import { LfxHeaderService } from '../services/lfx-header.service'; -import { RolesService } from '../services/roles.service'; -import { HttpClient } from '../services/http-client'; -import { KeycloakService } from '../services/keycloak/keycloak.service'; -import { KeycloakHttp, KEYCLOAK_HTTP_PROVIDER } from '../services/keycloak/keycloak.http'; -import { AuthPage } from '../pages/auth/auth'; -import { MyApp } from './app.component'; -import { LayoutModule } from '../layout/layout.module'; - -@NgModule({ - declarations: [MyApp, AuthPage], - imports: [BrowserModule, HttpModule, LayoutModule, IonicModule.forRoot(MyApp)], - bootstrap: [IonicApp], - entryComponents: [MyApp, AuthPage], - providers: [ - StatusBar, - SplashScreen, - CurrencyPipe, - HttpClient, - ClaService, - AuthService, - LfxHeaderService, - KeycloakService, - KEYCLOAK_HTTP_PROVIDER, - RolesService, - { provide: ErrorHandler, useClass: IonicErrorHandler } - ] -}) -export class AppModule { } diff --git a/cla-frontend-contributor-console/src/ionic/app/app.scss b/cla-frontend-contributor-console/src/ionic/app/app.scss deleted file mode 100755 index 0c40e874e..000000000 --- a/cla-frontend-contributor-console/src/ionic/app/app.scss +++ /dev/null @@ -1,107 +0,0 @@ -// http://ionicframework.com/docs/v2/theming/ - -// App Global Sass -// -------------------------------------------------- -// Put style rules here that you want to apply globally. These -// styles are for the entire app and not just one component. -// Additionally, this file can be also used as an entry point -// to import other Sass files to be included in the output CSS. -// -// Shared Sass variables, which can be used to adjust Ionic's -// default Sass variables, belong in "theme/variables.scss". -// -// To declare rules for a specific mode, create a child rule -// for the .md, .ios, or .wp mode classes. The mode class is -// automatically applied to the element in the app. - -//@import url('https://fonts.googleapis.com/css?family=Lato'); -//@import url('https://fonts.googleapis.com/css?family=Open+Sans'); -//@import url('https://fonts.googleapis.com/css?family=Work+Sans'); -@import "../assets/fonts/lato-font.scss"; -@import "../assets/fonts/open-sans-font.scss"; -@import "../theme/styles.scss"; - -body ion-app.ios { - font-family: "Lato", sans-serif !important; // font-family: -apple-system, "Helvetica Neue", "Roboto", sans-serif; -} - -body ion-app.md { - font-family: "Lato", sans-serif !important; -} - -body ion-app.wp { - font-family: "Lato", sans-serif !important; -} - -.grid { - padding-left: 0; - padding-right: 0; -} - -ion-icon { - color: color($colors, gray); -} - -ion-icon[name="menu"] { - color: color($colors, white); -} - -.item.item-md { - padding-left: 0; - .checkbox-md { - margin: 6px 15px 9px 0px; - } -} - -.item-md.item-block .item-inner { - padding-right: 0; -} - -.card-md .item-md.item-block .item-inner { - border-bottom: 1px solid #dedede; - padding-right: 0 !important; -} - -.toolbar-title-md { - padding: 0; -} - -.md ion-footer .toolbar:last-child { - padding-bottom: 1rem; -} - -.bar-buttons-md[end] { - margin-right: 0; - .button:last-child { - margin-right: 0; - } -} - -.row .col .card { - min-height: calc(100% - 20px); -} - -.content-top-show { - .scroll-content { - overflow-y: auto; - margin-top: 162px; - } -} -.content-top-hide { - .scroll-content { - overflow-y: auto; - margin-top: 102px; - } -} -// set footer + header height to keep footer at bottom; -// $headerAndFooterCollapseHeight: 200px; -// $headerAndFooterExpandHeight: 280px; - -.page-content { - transition: margin 0.3s; - min-height: calc(100vh - 247px); - - &.expanded { - min-height: calc(100vh - 306px); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/app/main.ts b/cla-frontend-contributor-console/src/ionic/app/main.ts deleted file mode 100755 index b02329c17..000000000 --- a/cla-frontend-contributor-console/src/ionic/app/main.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app.module'; -import { enableProdMode } from '@angular/core'; - -enableProdMode(); -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/cla-frontend-contributor-console/src/ionic/assets/fonts/Lato/Lato-Regular.ttf b/cla-frontend-contributor-console/src/ionic/assets/fonts/Lato/Lato-Regular.ttf deleted file mode 100644 index 33eba8b192384487f04951539f13bf1f8dd9f9cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75136 zcmc${2Yg)Bl|TOOd-JAfil&cdB+Y0v(x}%N^)`|$S(ao=vRvfe5^r5YS z@Q?TLd+mmu>-PN9fn$#d!uw-_!1ixAus=`#kF19UMvV%UoAzwpxp(>AgdlwIZ9&lg zYV*2%djzMDC9up1yti!Lap|V}6F03uU5g;RXWO!I-T1`4e~AdfU&ryjehVICe!=h= z^!FBi7i`(N|6pSEs_)?ay%=!gj@=v9Jw5*49zpnXt00KQJJ%iDBU%g};CBwn=j~dz zb7NVs?FNjeS`f_J_Uzub{|U!t7rH}zY4x7H8}~eF_@qx@!V4%r02ox@`6>B?P$<+1 zO+uTHOnBQ`Tbi318|ve=rNssLpvCP#j#cG41$8FPPvPe<1T~`;6M)GZ9 zB)`ZNuUFsUSCoYv;RgJD=x>@cRN1k4UP-W`zB;0F9J1&F6%EyS^O{N;i*w9=%ZB!9 zZ*i`}kz4GoZcjca{rg01t+Zj{KHbIMXs#oYSkc%p+)@@T^liO3KUP*;*<2iJb$XrN ziBnYxN3g`_D-Jq3bdP+vxn3?8MBzuL|IEIR6|)Lq;fjP!5P~^wCw^HCdRbegY`_jE zS)gN#0s11A3ecA%r}K%hF$WB$*)x`S-K(~U$Jyah1mMx z5AfFQ#@p-)Dpi45-YuOFufi-d1ds6Pgwt#WK6}g_M<$+R=ym$kOx+d}(*fawG!>e& zEa@c7TEIjGp8YNGtSD84zv87LN-0z*Kt1{|tjkVR@RFEd5CD*JC!fxXMDjk(@~6IG350sk_6X+M zE_AV`1n|DAGtq%x?Tz(yvFfVQ;-Y9_Brlj9@Mk&gnbr)G#2niED{K}~XSYgN6dlGJ zH8F$19Lh9{vP{&MVHEXxTZmv|5%Oep?aNR~Z-~=NDyh@Uf*76A5LL+(IJyuJ8? zSaoMsWLN_sLQT*p3iyENTUei+SSuJzhPYtTo8p9TAlOXfVqtC+{1`%}&1L%azUgrfXR*7_hMIv{P50-z+o= z-NFL4IALo9t0~CO3HZE5J;!wu)}SwoF|ofmFBd?V*Ir@;JUT3rq!R#FSto-j1G*AH z7oZ1I7Dap(2s)iJB*-$57Z76(InMwzAYT5l+w+{+9x?Quk2pRi$PALAaB83)T^7g!b;RtF=LFa(*;ppWENibYaT z0_ztufmmgX#py}A#N1TFCb7R<`i;IiL%`;$?p-&hu=j@TolQF**peXzyj`}fcej?g z1GbXp!J2U2&3k%U_dT}VB4&HK*xiMRB{lh-8+#&?Kge$#t_pUpX$xA+>joB@ZPA)a zqv-O~Exvl)yqk9P=x%idziwUF+!geP!-iyzH@@)j%H<~y4(OkC<%-wU_Qj+2q)lJZ zyGs0S_1yYmJ9}MU+&Ul#ve0+>UHNVKF~KDi2{l5Gu#Bxtc$X|1?C(l6*4I>(mlos) zvg|fhlILKxs)2Wcfw){$FbGDI!ARdkk?@3Aog)MZPGHnCS+>Ynn1N^WQ7?Y}dMHno z^|S_hy;GmUxW-xQ>U3O2X$VTlHKV>!Wn4|bZ0H*z^bRN-_=*wFHVawKOdEiQ29W4U z)s?}DWc~TtKoJvRl{OEWPS^-sB0YftO9*2KiwL6(z!g#xJiwJ$ZM(=3OTnV^loVf& zFWuiUXWx?Q>LvT;bYHx<=BAvQ&cedZ>g?=l`mD(jud?Q5U3Kbr89A;aa$S7szNzB- zmsH<4v$(Ef(RC}Tt5;mNXvy^}tE*RDzoctrOCZp)va54Nli%O8f?eWpeR#xbXA2h{ zp@K&iExCaT-ms)=RdXQFysE2nITc(EiduO3J^7&gxX>XCutdVmgx)z-6~)o~JV&O< zAat+}BT1ql@S+<;C^j;%roOVmY*{Z!LGXG=PN^1Hg#{}xfLjBrz_IW{W;z{KS+7t1 z&_SV77*pp*YYH5Wx1`X#?38&!#F0AA-VhB2_@kf@9}$`X*68(WGah1;iQWrqL9dg6 z9uXj;4w?m_rPdBLr?DVLyoXp#w5Sr&Nj^TSQ z?XMc$JEyI=Zv2+yx2qS7$M-xpC%J6fcKPwxqTQ`tW^)D_D}#AW^D82W&Yoy;RrekJ zF7LjdtV@2^=Dlsjw))L?uIjyf+oIt7+ZW8cYE8qbcRDxBi7cGMo@~0GR3T_;EXFmp zkwgXrJJ-)7O+WFPNI{3epeSeHE{bZF$WvbszdL7Yxska4N5@c9P*)1Vhwc-I!a&SoAO*xDHkc5WRQGTSn??i4Tn zVRGje&32>BX{g@!i(mfc(51h9=NJ2H3{G37>kG*p=%qjTHhV+s#pI%1;$ji$8y!p$ z8ipcTtonv}CoA2#HDh`wI{lVi=!M<+1y?3IsoDRFcYb^6q2K)S7yGKwiP3KUBD({< ztd-V?ugJd;s)a2ameoRamXE{I4{Xk_C@C(;3uU|PP@D`pC_kk268s2if|Zf(UN%c1 zfRqZs&O{%i@e-f~DDlL}Xw1-miW=&HZB?wn=k-;xHWq`9)Xq8}8iAKV7GgBNB5y8Z z>t%yG{8af<1%>X2^Xc-ZBVLnX%x2DZ;F%-rbcP*x=*%(Or8RH3^6h^2hJkU9B$_Q7 z2gcob&O-Os-zW)Xdd+xjwuqwHjOS*rJzVs4jA~qXMwhQUE|`Uj6HbiRESNJ)Mwm7_ za1sMy6=|b|kV(*aFeT6$w0&YPLX-rRNU#v;gDdo@a;ox3Y9xR#Yk670xUeVeh&aM^ zVTX7VyDPc%_T+ZZYk zCNpNCHBrpn8E-QAYGo_M`GWA};nirlR1>Qq&8{*KzBRN<4a6Q+J3{4wU{%N-sGeI9 zwAwO4dD(hvpfuz->?;X5y+yHL#kw`iqq!El)n+cNZVo%cF!ljq(IPGs9|o%`Oz6q5 z!PpbN87P8sgdq}{!~n=R`%}^>K71F|AA|ilg1Nhd(S)8fiqzbpuhYnB-cFV$s`E~J z>Y=A?rD=}MG#+=&v=!Ou|2h4r_;>kL_zfx&dXqsX@(~>ap1{k( z;Miacw1J*8Xj*Y}TPD(4`?!`zD&T{*hTH*hnYB7R;ECkR|CyH$;UKi0{ripO+E7$;BdZ16 zA{Pe?s)mzmugp19+0YG+-29cDyZ-s+;o+PAdDqUb+&uDP!>U7FU58dSG_0i0RSn{| zUrGM=3u9w1FxM-uF!$%j#y+3?<13H+^k_@V(Vsr@$WQ3=Cj_i-!FUMC_hJ(Nny@$> z_Dq}A1W}ptEJQ4D_=w==pdsK6iFR`cg9m+yWNKJ+DJwHiD~bwnG|l2^0tlbw7R>XxOa7Z{mQvD8OFSTY&NX24IjQ=ejNJ2>fxi8U)+|w!fegY z-qGeNcCdeI-o9gZN5`7>P+rr5s$3Y!_Vssdsv~&rIQ^ck67cLNUn3K`J6oD+s;era z1=)U=L+EGyCXK$jd*P~yBoW#qq=4Qa1Lbs*PJ~t|3p&`nin2wph5@7Kms3ng zGaNe4-4xZ-8w99u(X>|KR3VCG415c!VE@NzgFz!E#ZYaFKFEq7$*Zjk=_-mj0U->3 z1hf@j5RQp<&^#8XZq|j`vErWH?UBxvO*bDKxaR2%dmq}<=O6TV+##c=6K%zFHVzD2 zG!PAq+_GoI)e8%Y=B%QntF{>P{8LL8Uwd?P&i0W;N8vNqZaBHSrD^;V!+9o$Q)iNM z=OxN(7ObsmUfWxIHH&twY;0fI3N3M{a%wR}p%}EW7P_BZ@Cx5d*t{NSCM2z~C@IIUX#wQeV=;-yB`n?WpDy=v_f1~s&eb`^7azS(k|+NG zl9xBxy~!Kcr9Q_B>AvL4f!4gq76s3xBw$l1{48OwtSBi8m?rWhzDN7@Sc@nBBCM%ID%axUT&xk*i2r0Hx;pc>PoA#jpZW2pu_ z#D)@GAE_>PCq+_&ps|o)LzwK=N5WE?cN~rr!eqr3R=AAbP~-|rZ&o)Y+q?Fx+4JQ7 zmf@RT+1aswd`Y<88Fa<^)^!gYTwGZ+bYQ4{fBT;O;$!kav?h5pt84$sReQg5ZLhzy zJ~$W%MMKrgE}P%IW3bfe4vQyAP7_C|1*8Lj_5H-7%Syr_pT}$x0xXci!-6cPgThQK zRtJ$mfFaqHqD<0RGzY0#7Hy01^a>gk6Gs4oRVYoKS!_00_`%{!UQj9r%eVe$OkbNlv>R+JC#>F(PCA+TPiZx75C+i%+$#hg?l?~Auy}|j$bcZsgnt-X6>K{wS>%nOdXU+Q4+$hj zyiSc2fT9eFKLpk;$f}L$S~HE4pR-mLT`4ZNW;-U`j%@kFaDI8_snI~8?xeremF>6` z!hJ1PHwt+Xf&`u`d@T{Ei-+@=(9ss3TQ|3?Brg_@Au14Jp_JUH%rc4)!9gPyTmo6q zV5j__Ap-`85yP*=bv5#95h0bUweSi;g=jCWetI3~Z&>^K^ArbSg72r*qdrxMEOYao z>@CfIMBq`xK#|7wRri2v#&5BE`*TNUZA&=d^FIH*LFX^^MO*OCQ@mbmOMx=1n&a?SHH^ z`uvTn@7mUMylC!**8Z(sp-|V>{?-k1i^ONk=B})%9qWolyT)p3R?aOWO1l%dQ?IVY zeMD*1klTJAC=KwRqO@Gj@!<*qs(|JI{uJRrQRYgJMrrwKS@3ZkNlM6usam24-G|~b zY9Wv#l=-QW7!Ao*=!#fQRHIK#=}|o>#Dqw?P9dL-J9ahi3rShAaOAdKoxN8)vt{#V z4|e*>+lrD8TdPBluot{hN5hTGUpBb4qig5<(xQ1=JNtGI7K?-RTTg6Q{m7x-IftIu z+`fC;*2bjK&=||j_xO;{zQ2^`< z07NQtw-M2SbPDhfcoIoI^44de6~v-6`IUOF(dMl0!Kb*bkyaHg6VS9Ss3#&);^Q-o zFGrg3Net_%na0@6-!qLf$u!ei10-NO6C{E#9zL7MsH!N8kOj^84?!ZxP2yRh0c)D@ zhMlMb!1e}K70zs8Hpn2#Hxn|`%mg{E%>l}8vI587$ z47B%q=^ouu@N4+Fm;)Xt7x}mhFl#7+AqN?JGj~dQyF1z8uJHRS-0W4ied0s$>+H5G z9sZ18nltsXJ>#8>fc+|VJK^M~*cYWgOUHm!1|gQnP@OCSRw5Brk&XLH02lCe!Why- z2z`JtKo{~1kSX;kX~o26=ud3Ff&CEK2g08)*1u|FwF?+)1DPC!;QeqS6_S66dH!Ul z5tjM=Wi(>DB>(;@djR9J%ZN(;GQ;nkb(@b!ePsiCT(s4zc| z@)A-!Cq^2b8*&#G7zhbulFCg`xIKms-lHS#g1`_ti75<5jKAjWRna?0j&migBHY5z zoSHAFj!f1G@h7EI`cO5d8F>Z}14uMbj4_^RphOikvmie-uxxYd&<*PwD|!~rjp>|+ zEEPRNJr()QLltdHTMI3I+b3sYm+a4t7yBv~9UK_kHQH8RA3pJ@xwNyXsb{RUthXi* zEC~8eJunl?L}AHkhdxIh5=Mk|h(+g(E@Z-rWeeAhuAAG_(u8O<_Pl9Con`aSTRUMgxQAKS*(0K@U^S2ouey=vqRCBxV4V784f7EDCzEk0F>1h?oLM zvJ5u^GDPSjOlI3R(C$*TUDG++H7V^flO*tn&3T(QWhcfyTFV%!31k34W`=2Vj*%;A zB!%$sPkHglq?7boXh>UUkJ}kUG!hQuSZ#>ub&>pnD8-X>5Lr4}`9g@M1{cph@wmOZ zTNbRhHXmKkI@XfgdDT}g@7`Fk%H<4|_-e+win-lu5J5mNio#XQsWpbsvNj{d93TDfdwPICG*Cc z7e9T|n8%&GYoMl}Dm$Zb(=AJH{O(OXn_l?)ZBHamKQq=9Gi7!4E!_1TR{rIk-}>Bb zo12Ok?EB;wx9|Ak^@9i9wM#l#k*Q8}j|165HNL z-Y)un?nR19M!<~>fJdD<@`;JV;@tX?lAZv(I>Iy1RvrLM+|Xo8g%=ZEk4F$ni}Uk5 zAx|*Ni{~zh@n{;!w{i=4kpLe9SemB7VsZr-Zz@`I+v=qme@GOZXY~%_Cnq+!F5IIOo0x zy03ouVDcNRe)N+01^3;b{8uI{JUA4+`+?*K@`?D`o0itCU)1FZJ#zhuPwi+Ck0q_q zo;6LoufDM$v7-Kp10;5qBMNDP1gI4LE#YJW+0t2F&V?#jWy+e4y8tto%>u~)9a(v` z(D4Zt)j$PIh#YVb0x4Sn+La~*YR^#@c?DPl&a4m(L@V4Q1bk|_uu41|fjFE}nLaeb z8I(We-mNbnPQjgVm0$-`(d34D-2-;(;Ml2u`t-ze>$iTHnI1U#t7RPqr^A|CT;IPw zvF|H4&YyqX7q%ugEggtDSh00<)86f`GTTEBG23g~*DqR@VRhx?<+z4!fAgB_-@I)w zGp{BenKy)!@=@TVMevh{+v|2YOz_^MY{4`SH@B2k1Vk#4MxwKNxM3H5Bo8;39~6mN zomX*f)Ax2!KDz0pfg?0i0!sieOgJVcB5n=z40iB_x# zs*_YVtqeE-4PJ9b6ToH+KB5eLaPo!MY24Z3ypkaqc9dR-^uDL4T>D zdEJu1=ucce_NDrZue*EMy~&AJwr>6k%lz!(+b`c%+2F{wyZr6TE*-t}Yd7}Kzxk`j z%-)cjwR-KlCVy1aT;$@^{Rp$Yy3JqJT{@O+wiOo!hVJ;@HAjDN=K=+1f&n0YW zA=D|qY?dZbg(#97r-ZL&PX?yH2g(!>E^yjbe6c_=ATvj9Rg5c5sYUTZpdf4+XTe+^ zL*jyuk{X5Ol{oRqL{$Zx1UY7rwH1cv72a1!8b?fSe0^f#_17ic>*CFm`;j_&wRk1w z_a@NzPRvgdMp98s#WJ)2S%LT5Z#5Nz1S0WnRG$^4K-2#Tvy~V05msW5=sw2oeC;)b z`dUxFCw>!OeZtd;3?HP9&7yM10Qg7%xIpO*3INd zRZw$<1l59wA0b>OB2bwsrMR4m&MW`}MUm4=bDEDyamrFmp@=7!K(6p`f+Hq=vt{3B zcdpwiBJ}I=I9qGGHqR}5mA!lL%h&ei+uak1_Qz8xbx+;bOlclY0O&E{CN5!Z%qMPZ)Xnf{@CJabamWj$)>yPQYoQksV&a>5QqXuarE29Q7&3 zRsxLjmOfMuYat1d7~3Q6n0WKGhaX1A${6%l<7-6tYQmBi%+2w-NE1(K;W;D$v3?4B ztR>o{Xf7X+wtB%c%b-{RmEwLBW$lX3CZbq{m;8 zQ{hi$NYWFx1ahqGT~VJ^mz@&}NEi7sKHRL!^;G*OuBxbzt_{?BPQ4?a$nZ@ZS0Tsw zr&qW=VL^tl%Yrl%D3coIX99N>U~yE}7>$EJCal9G3AgD5#jSGa1?U+pC%p)fZ#M{}QP2N&on8v)!d@@K?JhB*G3+Sb6%-^3Nfm|9{SJNSVG|R%%LMn))mM9Gc*% zlP8lCFKycN64RYL$z(+z+`sRY>-zexdu88#RVV!GYumTK#;gxM$gHpL*ztPuuMZx1 z^R@*GZhP~{k?-9$fBtRXqj`g`>8=C_Y|whSM&!YY zJFOr|TD!nYleD|=w#oHYFl{6NHsbozz^%sF&Q^>O;JKa8SBDmf^9xP>@r24EnC|J? z*sK^?=aQFF9ZQ~d2yQ(U=&+TVUr~5o8r(oQ#fN@zE>`%!miG;KL z_~mms2>oCv|St#wJ@2uO00OQ8Wm03y-}$Y#Z!r6 zW2yqr#U%iuFIYfiJ_CZtHc{J#`xBDA7HbR#>r_9pR#W{YJZ`Re2*9VHgMe4fz~@gM z{rc7I)hn(YEy!UHJyqvV-s~tYzT;mPU%j;a@yFg0mrOn(pJ>=}$BMxtTl-y};>mB6 zh(eaQq-T5u+4qttET}$Hd^Lual)kve!XY>KUbF;LvH z7O&Q}wCwrJ_Jd#V*S(mTnO~A0X&tF*9cnBoZ&-9_+3L?6>fN|xr8%>rz9QPQwxx5l zv8bee$<=EHkMHUl?8AJqw@`Nlu-72mNBhn2sfG$t($GWr2GI?Tj;puRH5xp`dtkJp zlR<0Hq+$rIvpT%zwILZ{U!fJ0rcW{+usH-@#Zs*xc!VIP3i4{4eR7J21_TaTr@JCq z^IEb>KJoCW4Bg)!M%4$fJHmq*_-O`AaJy_4gI@46|I`dfpCQc^ou%+W)5$z|iS)I! zmsoRpNs^uPCFLbo*D*G&2`=0*2N)R=w0V#cH8E=`Rj`u{Nm5-+2jX9YqWJc*o7cr& z9lGPKBS+r4bLiFBx?}gW?)cPlY4gO1%ij9**x0Avx(wg%S^lXVZA2SJ=05nZ+aT&SBhaoF&M5yW7ET>+Hw4;c$7j2G?R4wII8B`Vzp7F$PhSK;-Rm#*r zBJHvAK`lvXY%F5@H2kIPlQsm4JvMd+yr%a~e(7`aiGX{;nm=4tHk>d0)sg$*W{e*= zX7~-pUo6~_uoM@O-QmC-QfuXlc#UL;Bl(%@V1zbsa%l6I_F57Eb4lX<4Q0JN>9y6@ zG@md%2TgOasLe-pQqE|g`BRc@M z=Q@)o^TlK#`@>hgeq-_~gE@Jl$>(`N{F(To%{uv-$1hrJj>+LkpU);P^SCCzVpW31Y`d3KA9&9R|hIEh&9~t?<0avUk3Ut?UkTwA zOHIRil6=Zs(=cEB;j5E>eIgJvvp#pW*^J=8TvN#Zl-NA^b+K7m6L3v_Khhoywdac! z_FSUgC72I+Hmu6-UVOr2BZiy8|7WXnpw- zDveNEQ#m^Z2CEV|=g1h<-RYV5fmD0yyDq7G0@T@;A3SyJ7 zFkEdFmks1LTIE;s^FA!f2Zt7xV+P;iGe9gHAkQ$!I#G*-gKhKCgAbr6$9RG{VV5#^ zU%b8r>#unK!poJRvpvaM-*)HdW7+QB_kV$kCncBkW2~)1SdlO~GD(9-;i@YGJ#b7_ zCXu#-*`d6mbwr2@6C(DT!5KM-lbAKnux5mal#UnU)07Zu*Bf};O4RLRo33_b8NX(A z8En4Sj6OHq{*#B?Hn!GR<|!{rK45dBbs_nVH~i_B=E*6Xg^jSgJ$h)RdZ~6(v|5<_#FoBAM|ueU)6b z-jFi-?}YPgpWNgL*nTfR?(zM_nu+<6&HA2nNM{gAgeF#-@RpUbecq$(LIAy3OjP8fG9V;>M4X%ui&wLs*|GQA$dD4?l9!`3#)wq0-o z0ELCOf)>b3g#DzY9>c$V<|vSgibPLN4^XC*@jzowZvv(dl8*aktBpiu43`q1&u9fR z5g1(F5t@=R82aEF7rAGZGFr(yt!^+&9dCfg%H~)PqquXO4Lf0jqI75|kSDu~2N$hr zZrMCo(RIh4p4zyfb6IDV&zWt^TlCt2#YdJ`CTpt}^mGIdKeeMKP~K_}tS)FOa)-M% z_O$L^(x4kUaA~+C?6NhD3^$G+Up)CMSEM%Y`?^r=oYLavLI+{+-KYPe^Ma6s4G2k$C3e?8PM!=nT$;@~SY_`l4nOvzWL;4Em+I(yEPt9f>SoZ*cH*1WHf1 z`}vNCw~iejuW!BR;Z5s55i=R1Y-H)R4b>O-9ltx$wN`$0@}YUlx~_foiaoC$8yTFl zGAdm%JTK|(s{hu@8;-9aWxfCOU!*H_vQWyzgsnIl%*pbS?^w0ZXdPpvFv9}m07_A-$`Fy(wGPg5pckI|l)8L6M&*uQsvQR(2$Ih~6d3!FZ4ZP(X#t$FxhPsgRt?c4T9 zhtz4d_=Ens@#AAFZ&?>ZLbRiEWT5xD*Dky0t2coRiJtlZ{Wny4Y9V@JDm{7JJZkP` zUQSQ6pS#ip7kYL8CcR8?bIF>9q;5`LoHOd7YJ)Wr&XS)8@$z%jqSRVTI%#rdrlea{ zPpb*F9-BB6c1%PGzWnQrTLsSKr0gZV~-v=A8%#f25o zikyI(=Vn7N6>zCWbQ8x`0m__#;tpb|MXD9UJTM0%wm;HRLAVKLA)Jp~H;r;?r}v9q z&>`}?XzfyCNTI(@A)S^Bt8^~f;jSd?FHm;GaJor}FCW-e7z|5{thi@S2a?`bi`9$v z_jRx9t#WuRwc(Xp_cUz%r>pxq4?Vm0>N}lcQGVANo!qwX@g1c(Ymcv~3Kf8Oid&+t zxu5vfwcEaQ!~APMd8B>wKnW=~NZJwal%ElTtdQps1O>!TvI8C``3|_pt`@6N2DJnl z5s(-G4N{Uw`g6Au_%)0xL9hX6A=Wq4!vL#N)M-7kF*8f)*GonyP?tL5~JM zkET_n;+ZlJw~<34HdP#NbTYZ0rz)jytRp@}os-9}X{r_`P9g-!Gt+BB+n6Plfq0xE zSFk}C`-T;MMrkYv&)7ulHiKK{r9GbP2WnPaH9V(T$MSMRl>_y;tT6d=X_Zy>xaX~3 zdc&$}_cFIJtA4C=)zuRZKm)B!-XZ-&rx%)p`NAr;Hjy*5fC)<%FIY9Ss=qJMPF`)h z4dZEIO=hwHXoqxW3u$r)^hIcP-#_6de-bw5&!_8o4gX znIij5*S;#|D&3dgwDG3VxgB}o=Cy~eJhZkMgt&Y6W4jvGjF!dh;p)7B#Z8-zv0F>~ zR&Uu+(=<2Ix3j-*?@(#-YxSdzIT!8Neo=1YLY+Q0rz>o-cMkVox}?(KuJjfZ+I1#x z?O1Q;p*2nA{p;!mwuao@jozxtQhVu5tNJfpTzTsCXry6n9>hoL5{DJB&aY3+t zpkmdoU8^bv>M395cF2n#LrQvtzwx{pkKl3ItrpTYQ(ok-8@eR1ThiemSyT#lhd}Yu zHZp{?f-vd8m7q=4$t7YeCX zqA*jkF9YddJ zhN|~e&uGKO<4YzFi`VVDa;s6l?V!+5k;gGu%1zX;zQiip|qeBz`O`fYG{K1nneN|z!5+xsqO$rn8*rr$*?5Z+zey8UWzZysql52R8QHk310^SymBoyCW4ix-8<6{x)Hj z{>Iu<&#LHM6(Dl@Uy^rnh`5Df#t5HiUjw{SZU-=m;}gLI2XukOlb)rhiO6X}5Ksg^ z!nkOr0!$vR^AHO&Wo8Wlk-f=2Sz${h^D^4Qr98xSWDu?tY1lG`x%o`^GU89u8Q*0hpNSic= zw2By#+7Eq_c&cp=gDu6rl6{Uqh9%@qS{zPxzsH))0m!lF zkK&&(e3gXV)4WCsky}YW0+c!BYk3k=4?)WGkl>h!GC(SXe^7xQpTq5>8dfxcvFGGh zKkPvNoTJO{a;q~t0XBs^>r(6#L1d8fwoC?zrF=u-5;ODzI1%*lAD|{W1uwOQ;Iou? zG(qE0g<3&0N2;9SZJ;5-ZKM@Wp^a7;?@?6E3sB4wr7!{!)H0z#rB)a^AdpRnoUIZ7 z%ej1lDj3M|AsZxgO}S(-82kpRj6aBTh<9j`$Qmc}lOzxwy9s@PK2wKR!2T^ChJe=FS5 zGq!hK!*Dz+w*HotBq1(29BN(BO5YA0oZB{Or4{9J7OUbD{>tU7Pw>I6vC(>PzDT~h zT(7LiS?*vh0675KnvB)fBCHjnmzX?Ck-B}RFisVmO=wO_IZL$&jaPvp*dfssV0VU; zMd36li`+-G#`&bI3T^B}oDN-E*TdVVL@mI%Yu@r1qL$n8Ivb$p!WuuBaC(qSo9%Z; zJQ3`=bTL;-vg80D7!a5fX|yCJRPewTsR8h;WngDh8IrjHYCv@%rL}?*5UITNsh6Bh zMW#wom)d4R?RW#tdUinsC}*jX&M21ee*W_A?#rLwz5Dqq=FGX``Q4wo^UhBV?ina7 z9N43i`;WeQ$-%E4oj333R}WtD>e2p*gWr7N^WTcEzh(LI zhR2Y5n$*1%By%gX12Q(&d%$f)Bv~r-u3*@dKhDWLbau)K9(a~AJ1?k~C%qO)?Ar9c zILnxFEdWuc`b*W+oXzp{n&+%Ut4vKxDX$j1l~Xem(mjvx1`_)yOzW7WT%u`O4V3|< z*M$2~BF7;S0ve#?5YC4v3d-$0SBi@$J8UVk;+XD7Bo30<`PfeG8`<;lrq$QQn1j1U z`i_wwG5NSWSa@(~ZTGe09yvDB-jqz}{(YJ!5%`~cTzW%Y=>s!XdZur9x-Tou8LT+6 zG=fOWS>h`_%};c;S~&DFmFdJ+8nqmuyN% zf~b?cV5wd~2Vjw8($ebC@(6`#m;;hkye1-9}m*=X11LVGAU+M-NL!q|{Vm+nTrlY2*AMjoHx_ zDy<-pnfCD1Vp=mKhf;@#p1?b6opd3h19)IZf%gz>>t^j@N-(5EiCQgXekp!&wL*k7A+y*f+$#OBgDxjsXdtvSBx@>1z<-t3dxE^#@o^SgFzo;*xawH=YLOvnYk zC$_nfcEo1;jd~SQWDmqz8IyC7Y@6<3hpbA^fC5WOB{HRFK%Kqnl$$(NQO$t5pgQNs zfZ}JcUSI;0_(O|7Gukj1ujq?qSM;ymu)4pZcKz{H+n#DIHF}-S;--(aK~OP1F5)X%Zna`JPsD_iqR8;WxCO4?U+ zHg6xP?r0!LmtzOOOLDsqW<#`-B{v(f3_2Riir8kDg}Eo4e1S1+5tTy743nKJTK;Yd~5Z-d=-NL~b*kx2skW|k(jV^n3F*sKK28~EwW zd?%Ef?^RF&BZ*8TvYW=kO8G;%xHWh zJfjc{MG^(XoUx)fQeD&jBk2VdOH4$_FI5y9j;JU>zf>&>C6lZEGyr_%p50Uh>Vj`| zpu$l5c{CB+8eB}Ng;~y8hs$V61aJ^65v`y+@l+(k!OFL7UvOjdbCAxqu1B^Zp}0)? z+vK5rSM+y@f0!^A?wq%*Vf^?K@p`oH#OVJUP&`jA8Sr{qmeK#Wxn$1UJvj(@*z5+Q z(+1SbcHflT@F19E=H&i@zDT66K)l)RnJ{Dg@Jz|?O8>yl(a$F=mF4+)q1@@YWJSKT zTr%%ZHPh|RoZJ(c6At%<6bR;vpYv7u zeAT|mh4~mNGC`#`feqQh6SL%<`P?=*twB&c^UQ|C$Z0s?pY~cKw6rY4NP2;@W}lVv zQXe`0%!PyD$p@n3pMeGN#29vE^2sN?ZsYqV$B#1HnU7$9>4qnfeRkSl`(uXBeLpfY z{$#TzkL2XAgw2u6nS2+AMjp%cB_HFtXEx}j?*SfO;c&u;W{`ZwGhIkOblZ@dq^6#M zJh<*is6mohdq>g*$uK}=mddqsPSxY6AyBrDmmZ!q?~Fn%t}`;v*vqEuoFgwLSKs3c z=vk)8DpMY!L7(e5p8SD3i`_20j>z_f3lGS5<( zg|r`dN|I4|@W5%ig|dL^V*LM64dGdd}Xr=1~VFkBaw z7Te5||0R}8%2tb5H2G8UJ?U{*#5Nhdsoxs0i$A>?b<2}Kg+4neB!s#A?ELC1c=Bn_ z6*zsOrT{cXzH#tV@<&IhGZ?w_O*h*S9eScWn5uPTu_~e876dwIyAh9WP=Le`uaN(u|kunFrFx^UB6*V(|#NhPRK;f~8@*@*Eb zDg>x-NC6ggb#SezJ|b992rgpccm$%vjEOeY8AII0)Ib91^AR*Vj)4I;M=Y>`~8w!Xr z09c&r%9FNJreRh46X~OXf1B0)sHeY^X{oqGx zL`3QaC|6QqHN9;GsR~R>+?f z+F4b?-QHFo=chx#=kIo!O*%1kDj+2|NDXBo(k;n6BVwivuTx1ZDStB3H*o0+L4n^_ zrKOHF%+dh*SN+A)no3p2EmfMqmG}tN4F)U%#p#t%&DB@i4ZeZtiv{5%xcRvc zUFWX~Rfu#3z!NQibHZsxO+mKO2a@{~7&a6&VD|?FQyP5y5J$biXMjRZ4pBo9(j^-B z^$nCCQHecWHu0z7+!HJAD&NxFw5jaGvb#e0;oQ4d++Dt^1E_`N{>`lEo_fO+81J~<)DjEGsMNpkf;q633H|6mWiDrKrADL7?Eq>6Q)ZWUpX~s z#;#h0Ai)pzp0y&XfC4n5w?XMese-g;0}c(H)*bd51Zli z(~dzU;%OkNN}VB$y^mT7644sJm8l&EsPZ%Zo$bf_U=5=USA!I_YN&8?yA@QncRf_ z(#BAwBi0g^?V8MSHZOD^^+_>s^&taCs!Ub)b3saCLjb{$I({2;U10L7`6Tq0;( zy@=2Y;$bLA|IxVtVJsGZM~WPYA^=+{gep`<*%2}`wMAAnR3%q0D8B2=EAe@YLr!O? z*y}6FbF$5Jw4ARb=ycKzgt$;bHsKyZ{3`bON~v5a-AkDM8{ATubNVmx2av^;INg0c zTb}T)Sv5M+)7?-uEMX zBBO#t+C@xy$Y)#wNw^5IRPK``CiZ{PoJVPnl(C+(j}F&Mih3l*@KF01<4lbWjp2&Q z1s}T|TvJIzqqRp;g%Fvw*QKVnR$ zH=6Z0lBuof(f#Wm+}F`_=&|)X?ry4-?G{_8cCZBpN0;F!+xF_N>cWYI@io^!^wP$S zFF$nsnmB!bdE>^H==*s2!o4>?GY}mp_KD4U07y;6T-Cd<%h3Z8;`HO?%|g=j=%hn@*SNld*l`zaXtR@ z@2+3==q(3ED>r`b4@5;Pr4DhC`~>#@)CvDX`+t!0S65bd#{QprtRVLPNOA=UG2}+z zWGSH3^esS;Fd!DvfJr^jxSJ9JCX7m)r2(kOs6>=d2l?bMU-d7^JMVw4q=Tfd*f}f51D=D#b)`vHK&}_41+;6jGTJM+T zoaU+-zeT-|U6*6=N78gr~U?7FkOAjWqmYmwd)-vRSjIPL5m;ySsI zwjx7{H$03aP-zxmB}y(OT(daQL!@1Vv%co?ma7PNN-OW0Y!Tl{jxjJ{;eU6ljxNPH zvvJVEJYfkto(RO_bbnubUc7HkS5rfMU3FzaKKU5A)&tNi$*jYpS!4Tcx ze94FeU>z|G+UADgk=_fosp+rU{xm}J|3xbZFl)QqT4#<0;sCgYcE9Ctuz2Z>QS9A*f=070i35SV>LdIe5UOCdgI==j8L6*WI~F6*ppR3@#F z-y}+BG2`k{a>{FGUR1*WHRq$T&R^fl@}7Eh`!GQo8K&L#vPYGlK5qbADE7we9s3H%AMxy9lV^)_q4@D9?fPzVQ2?++zFW zYx10niFSIWBJoXdWI9D?bcS9-_~k{-k#IKQq)`3KvmMxE5(`CRB!Fr zB|SZtjMZwNR|f0nm6rC^2ZQx}rKR)ggSsu<2bR{>EgjKZDJ{~ z+li&XMiB%G;MK#TN;})?0xRmdpywe`Zjw02E1q^IS#rDd<%urcbIGGelee-xN7=!N z!W)SDEtHOkBl?ZF+xsdyqn~bTtO_8UKXA8y8 zU0TUCi$L>KSSfuVdxWHwdJs$|6c)u%PInK%kA%C2c)mF|lWFTITOMEB8m3D=9`aRr zUibKtEW`3+e|es(wW6}gW^_4lE6v=@8}%E5_4BKWd)k|WpV1@cGQZ54V|m*judBC-nmG>F*i1+_^Jxmg7$_*O}YM>zN##}&x+Gx-JxPnuq?;6l(oid<5k6W9GGvZ z8>-6&biby%NnEV$@&;>#xsAkG%`d~#K6zlv!6*oUUEZ)p=u|spFFxbJH?r@<#lN#< zd$!$a3+Ks?CzF}hzxX_a!-pvA3vsAiwv10&cH_`l%Rjzd&h0DNi&~u9k;sMf*S0^+xcYX8=sEqvc~% zrq|KY_TT)es+l3)1$B%z5c$-6gSF|k(39eJqQWq~Ni0QqMYM(;Thu6Vl}4&WI7^cU z7lPvkxRk05AZm5tP*JMVM|xV<8R-GhB7U46rDd@49b2_5f$n2V`}Y(X^vj|p$Q0PM z^PrX~&^ISmS-Lcu{9SPYdPkR~ryFE1yiP9RA---d!8zmpMuKcE7$W7I)dJ&z0{}iL z-;^c~!f08pHdwh_lr7%BLmF{-67 zC;Cv2Y-OGZ$!+{;J3M9Y+xI2^aNm|kFYf5L_|Yx$i4z|-$lv_1L0Z+e_4vq=V;dVW z?#tLev&Y2WVvdlQ>J5l$oQ@r_TeK6EmoRwAF7z);x_{5R) zUwBK9Jd%8ny$kPFuJAgYS%fp(aMvQ}Avd+ZCTzC?@v}JsK!gcmG3;r=Q<3-*Tu1~- zI>Ie)dOQrVcv*7)VgLyNJBLZ8$s{u6ks3n#^|O=WzCJfXCVZ=xbU;y%+=E3B8` z;JVd?C2FT64ej_-0@zxbHX_|#c17<+!{z0}7xm8FJ5pXgvUl#H(a}XqM@Qwy%ZB&i z4yuu|vXT9LbN3CGO-^ndAK$WNe0(d-;8U;)t*{E6Fsxw}7JBRgtilF0%aGpMH zGVNe49IqTRm_Xs$k5b9Yq`+Op&y0WY!TBvx#Dc(DEV;#TEg5a76xLq(@3vlNH=m4q! z6oOjP^GxtA!^aF~H24-k558={_9D2(l~hRRQ?!Xl3mCPfw}XOu15yHIef%soOn%G+{Y1H_-tK64-@rNO=yfQZrgiiRh3iwn3t#U5=bt1@w zxjkKpw$_&BrpCHhRb{%aox-PqhMH>1IaD=nx*kQrDab2^1WyIGB#v7aP_U|mn-$ao zra_$Gv>!q(&D`#r*(3^U=ymQ^0cWi}U>UVXST^#%Xph?pT=`eVN$rPC8p18+E#tau z#oQqE+6<(F(@{gZv%>(Wk!D(?wVsikSfAc*20OD|#6k)GR1hQ1B;t-5?jxB2Y3e0| z6x108ObY!j*oK*jm#!2>>NZ`Nbu)4AG)(=&6pD&JzYx18mWI5O2UNU;cL{iVx%>e5 z-~wThuv%P~h&mmsS1wz+XmoB*X9r<#b!ACWIOJI1TrkBE9VAWZexEh{%wmSKHlq;d z@Zd@yJyI`lLYCQ*Q7afsMiVyOgPcu<48{2bm#Ps_=cqNsC;?-$nRL8Ny9gyuW0<(l zp0GLtSV)X_!~a1ynVD!^>B~;~|ranZDh=kEO9_NBva2h#10vjR5NCuhs%?8Idk-k*S>GMiXC`y9{c7s*HxvuZrs zHVey$k8BT5tehq%MC2ph zi?EOkpLm<_M#9z%>Z1J_sT<2k*^a?-BlQ~FSde6f=mUjZkyi#!rDm?_kkmzau+TvH zws6u3h)$}{UX{!MR47bqL%Fc*>{XB~qUP~YWnzO`pVX3SD#@uc!*m@i=7Td1cT_2{ zSssm@Vd6+)Xkcv1*{>{%;v`2y@}ilSmc2!HmX$9&Ffh1nu)ew`e9vQ+^7h)Q*5Q`& z9)4$;#jLZ>ytM2zZt(_xo2UOfxC}wcz>I9WAk6aIJni{^rOI2_fwb~(rk*>dJ^xGU zxheg*p(6F%&i0{y@$_$zMfD?C!A>EI1ru({x1bXRQ`fdAXVlSB)_EX!l;l)gT1rkt z5V;Olq~IIgkTS$iYE|x#L$Fk~P?nd%#A0e?=ucQGsshKs8O=;p#UU}8$D#GYssa;< z@uZrdq#{bl=vJ#6a1a|^$$g#?;3!448JZ9jLuhwu5)wHz=^{b;lhj+b=))lXSel%4 zwTe}fziboNO-$5GzV*DhFmNaP5&Pb1ORg<-Ne(-j{EXX=xYM6YdXNN3CpkXIZ;Kib zFH2;RhSt{FSYK0BRG1g^`(Q&4uz^$zdI09Lt{DdzwcD(?2XP+5jdc`_*6XCH?JNq1 z1z8}3qTN$x@Prr+8>G`#3J{{T8}p#@JZr_0e*7k#vo%Qu{Ks!{)=?7sS$M|8*%=du zG&eBpE{FC`vW1*h(;)y9otnQU#fLsp%annKHVV(u#Z)|g{zqzdDuE!?!PLs}xct(O z*`AEO^mIuf=+kIawMGR9G!Gu2S7JtTy2}G+RMo}#1qWxj;Y-=?lu36v@Z=2gjEL8! zxkd_Z`o;ZA&U^Ql9ruKd{_Y|B>|T@umW#i_gcIkw70f!iZjZhp-Pr;@`V9OaVa{KR zLF&X`u`zCz=jLh8iN7l4Evzf8Jn>QGxk-6``r9DeS9tr#T0?Z*_&&8x*BRWH_S8&I zPrr+&hxOF9Qr05T)4$;9K|QssJSEaZf+ys{_rQnkLRk2D!WmQ&;BZU5%|ZyxckU4v z#=SM@RWiWD!@9z=OUSO~ys3x}HC0P}Bi|nUmvFXBuVQGg3W%9csl(u0i8TFFLg4ta*3M9E%F=HB88N2h}+bV)J8X| z)xIJ6_|+NwI*rNSy`lRL+^M!7dtNS2UdtBK9U78FJjSn7PbS@a7=KpAMustcZu+|B4I!n^3JT!&C{OKDk|kI>`!UsseR?SW7>0CU*)-p zo&&a52yf$j?lDB_3<*RuaMP?wG?;J~C2KG->4W5F+4?oflgUr7VdKfi*05!4O!|BB zv&s9{vh~S(*RpZ8Y;E#UHim^?dHN3AhVmm|N`=sfoJm_OHe`PDO(s zzSG*8XkiGV(Po4L9+oF0INhUxLr$jiNxUHOowm<&*6Q$7!pBN&oU1xnEp@Z?K2;q| z4<}R*YoZ;Syl=kaGtqv*^*}I$mRfykhBQDPlj3MaNv(Xp5nWcQGBgBoTA0D>l#%_7 zb7d(1NRH!(8NQKIR&HfvgSN0s{cew2Pm(hjpyu9(JHt#vrInX2C(R$}n zy3Iv;zq57nqW$-#-RM$NeKa?~bn98(^ZZ_y=K9^)zCWx_{{G1uzCTa8wzQ1yce&!5 zeG^A|>4q0zDL?O@V`HO=jrVcvn)%$!(w`GHD&;NgvuWiC>y+mv^|=xJ@Rz*(R^Gmu zHQ{sWW*Iwnl-!$eVq=NlkBnZ06NppK;j?vnSt^PbA-;dEE>XUON4r?tuIZ za!mhBJ7~JQ+FA-Dq-P0ztdGx^sHQZV!PtQ>9<(aL&QXrwfQx{VQk3W?a{MSOh4fQ` zP-Ql3qO;Y260B7Nmqsm3AugX@i#mWOQKblM#nl3>3|S}e<~L3+4q8US7^6!=Q`N{W zBZAOs@(`^?5DNIn5aJj1o|&_XOP!}KcIJn_;I{b(H{Z3ssw^>(D61P@HC)#;e#6kZ zlWK};!$4g*Zf9;>w0==zMfZa43V-9Wjyb!B%AW7J>IM0k)>XS=txLLUt7CaZ@rtV6 zO$!zrT3j);eLYdw*c&Tt9_^|d>Zos>TbtHazTOGMiIBbSZb)x(jZbS#T?2YU)rr(tn-3BMT-$Txi+3 zI?7}gg8;X6gag)KgzzWN-xTD}Gm~4;!K(jd2WHadB7zw=p7Ku(TJ#wvyM#7tkI&vo zhKP&FYiFGW{KAAg5zGd+mt#hdtl~NQBKIQs(>I-~FGO>-?xKi>7~nspmS^eAtRuH0 zv2{(bE1pdOo=VINzP`;fID|`Xx_@PhuJ*tc zkb=>}4;$c5Xd!=s+za#h46_=FJv<<3cT&4U44Nbe2aZ02!A|C>vNvS# zjMmOH($&^rdoma$(^`daoNANY6C_#ZeT+7_AAr1yDFxL{EG{wnG1^MA%nb&juGA=L zuv&uqbmJM>VjR)b%dw|rxGVUF{fFt6GrKj-axNS_wy9Y?J-uqyL)Og>eQnDV>m0LN z&6k?7FYzMfboIo{!`Ds5&c1$QbDF^n8B3?%e}l`?K2^s450|kspC1s?pOcJL%D+AB zImuGx`8MVG=?`G~T+iG87qqVzy1!47{Vu4W#c59u(9_dz;%U81Z7XHpev_X598XJR zYFT-@?fay^4xauicEn$gl#f2zb6j7SXs_$5@2e~?$mf|ABn(QMt08PzFdp-}oSDd* zqbm@h+(J!?(t(#GS}-W(%G&t33!_gUz*H@&jEoUtGPvFn83H)jLivKM+6LOWhGpg( z@3nLVaz3Mw!39o)P379w2lk*&_HC}%*yQxf?zJzBJQgu}*R^iwEowjf#e?ggxV*1@ z|7W(0|5K;?;?-YTGk4!erDTr}Y-k_2Xt1bqc&o0&VAXpw``r09IU9L5?e^B{Lrqq* z$KG9Y*{5IJb;~awSkiU)xjm~NyP|)|(xYEa+%R!-{m#$qE*t46tX+D^oRKS+RA4=B zhkEm4nKUG9HixG53wUX1op3t8&c@eip^6=+W_^A@NPkW%sFZ(u+H-1OdH#!O&uMLy z=iBHxG_7&rpONEw6~6g|GNDtsmw}&=idCRJ{$)S`N!t+4*$H=t@Nk6Y1San-7D=YG z7C08z49CB=om9)#9j6>sw%!#^-y0rw&HOFCa|3&HhGTznbjAakL*n!cx(eMqA&eQ+ zv9g4-qP)DWqK@{p1+wfoQ!=$@yfp`$I36pc9c!c(z)(lx2+IYoo?&jg`5Emh=E7Vj z{!6^_W48h9r#6UQn$JIulz8X4Z7KSJpMB0ptE^b>|F60;fseAt7JYrarPJB>J?W&g z09goONq_($ge^oi0mC9B5M+^USVB~WaX~=Fab?s|#|2Tr1zSW#T*g5}#zDuCxjrxB zb#l2QD*DC|9q-eD^!uOsx;vc^6yLqS_j^w~sjt7U>QvP^r%vtXbYjud_rOAXr+(BE zc)uNQX+V1ZZX~7{oF|RO8n9dsV)kymWmv6PXl%G{P-XJatWmetjuAtRleSdVF>{}F z=66GledAX)tew=$sGP87Qm+9Wlg3!r*8QecEH~!4>$B1hjXt@>{_Vn=e` zrtWqm!&1*aSReFAGkQcv|E_imqP`$aOoD=*lvL6DBLqe3$iO%y0-* zMG7kY>2O8Gu8M_|yI~bQBVYvBnOxW@ifF+1Cucqa_CRG@T%OLu{Cphg5he_5R5y}m2JrA*)h9{U}$jxA;AA#*d+@A$!8b+>FyJNJsz?jVJOZzu2 zoK&6Wc5XiV)6Mvxyu&i=b<41V#6Ee+{raS2Cq|taVM+P-DipVMIn_gJVsTa$~ma&7T^d2paLe!j&$E=^|Rg8;`k809K*G;Ie zyLj})ovRl~_#6%A=o9M}B1aR4a=U$ej>Q>{Yj>+Nj-dhb1Z;Na&;~1e$lR>|QR_!?_Uu6sO)eUklj`c(?OJ+x&u-T`1=BCMSylZ@ z6T;j-${8Daa+dG!*iewO6cB(pX@v1KgK|Gun-;=?xtxJ?n7eIs7b4Ug)nYe{XB^9-NW)!S6lq{eTmyn| z1II4PBEJ6QK;I8xQsLOTBnz#O1JmGP=f@MS$Uy{GySq8JXr0F}hjvH8S#YsKAq_WJ ztRTA1+M!`Zv&#nI=nFx?HBUMW=)roCR3DiP)JaMzN_K*Yhb2&p#o4EmJUx{NpE%!# zO<#JHNmy46>)%L}&BUZm1#5EzrFK&F*;&>#XD3@q&mOaQXLkyAh$;=9+UOKl3R92v z6c1R{YP9iEP4@6%2Gf&aqlb?kT*+zed^d(&v?Ns<)sX=WI=Ylq7Ah@0F~)ANJ6geF zn#~c;nqOf=r>^WeS_KN=GwIPBUA_8vRsz&+Wn5@TA8 zGIyCm)(gdKL0C|GS_3}3J`D;|?SMs3Zp68dcSVnGy((^>npl*ZXm;2y2mE>kglyoY z1(tE!j$9+|yt;Y%sva91k%M0#-F@4pcl!Ez1Vz@!Z8NKyS;JW}LRrudNt3~sc2=QH zIN|^=;K3q>6T|eus1>~n_XExgYn1&6N5ihijh?;%T{dDo{$&9EF5G+b7) zOkfrjtM1XXi&fMmSd&zW5QaY`O0JzS2ONO}oG|zJ-S3B4W+Uk#>2;}9z7QVwAXq=i z5BVquxG9bHqT%O@1Y!V1uP4P-a)}^Dm|)h<_|@LXm^=4Aopu*4jt*XQS!`0+sIzv< z3k^%MBI+zZlX)qpUO4L@J)wElA;Ne^_k_10ApfR&LUUcGmFmerWRCB&v&g<1E2-s{ ziW-K6rNy3He&Lx`tl7a}y$h`SLfIAV&#oxph` z+@`{|xs1!jY2%3ROUnJ@I6TQpCoLBTg3|j;Sk$ zs7!H}h8m`5Y{LXN(TR$k<4{y6(E1C<5^fWd4D28WhXw&$QaaPbo++ZbXVtyzKyApy zkP{Z>=E*>--*#Bw(5~>+vdxEYeYS$4^eEn;_qksZ#uSp(xmOjHf=g~`^1U2AES({O*+=;-H94C!O~Kb zUpum*A7?1XjGRzAVMtZKnu?l|K6&n-XUcM;JUXx!JG{(rIkS_^tyupSEJt$C(>kI_umWmDEwT2PgiYq0+lAo=~JhKt_N5%U#ocJX*#$ zdhe9$X_)z?B66Y4uW%&fB2Ufq9?-I&8mPN$BRXBW9d*_`1TQPbay65!Q_XIUR%pC> z8n0EeK1R`Xe857!Oxr_O`Pn}-?Z=vH{K=hLh+lWvLL4@;rZhe&vcKLzT>qQOLBGAB zW#_t@n)Q!fcHJujD_%VN=+C$2)h!-9cJb)E8>|c2GVF8imSNB_=$iGbt_(P#xnjkV zjpB%A*3I(=h%1_Xzq8tdn}qZiL$EmiXU8|xls-|wapOKi`S^{TTWw?&yQ( zQh&btI^%~W@ZO9c!}&MCZ^l0mjQgyp6?KUDlT#0V1*6! zKx#Sq%lf)5YmbQoXM;Gx?in~hHpnXzOvk8V?tn3fh;Iz1-YpN#O#9-!>e4AAN}MzA zNlN;cq}0!jXXcbmsVNOlHfpWvY~fok%pIIQWKNysofu16!uVZ>e=|QTC4I=Hbvv$+ zDl6NqmKH{kQEGOLllukChlAs;1X>(}N9tw4QWir~@O z1}{tV-n1P@BkW6o^_jo^mX`HN30KF(UgJoPyy`EH*lvy97#FvZ4o(zkpgeI>9`TP6 zS|0Xx&3g`(fs$#24B*TeaGd!`7Zoe`7F{0RiOp`Xax~I{=?LDN<>AyC2M+LDW|-z^ zt)8nQlO0z_IP78es~ss3>;LkI{Z}sgPs3eym+hyH@D6>J*l*(jfg zJR_sLu6IdQRY_@;*kst;@tE-?Eg@5Ft>G|IS_sdx6u=LM)NB_cW z=gGY*2a1ILk^L z`4;iq*IwGEr0=AvoUFX8#_~ynC8P>!E_P`?W)6mLJat`7yo=eqE5U^qb>0L#zv$LY z-dYA&uA@1}sm|7|-{_AX^CPrk(#c&`?--yggs^1osn`?N#iq1%!u`f0|SIpnUMq>U(Tfs?ArUa_o_qFXQ)+KU{ zJl&J#DtAUYZZ;zIWYX+Od%>oBtR9Jj7&MWdJkm!wSgio9G5tf zz~B3_h%P;@40{XB_@ET@IONuoB!f9oXzYNEUv830Nfg(;ZQN+=nu<4QNl!4Lh>4!NzWP2(* zYwO;QHCJ4dSDO=NamJ=6)((C-JT5)X`j2rb`Dbpju0DJ1`WqtSCWLdiZg{O_Lrp|# z0%zsay_P8JOO~4%1YEdeLGjx;*`5bM`Qk^Nhhs={iZ zKX)7w~l;PP;2MJ2g5XB`m}Gb$N67tcOSE*&`j%uEdgl zgRnKRebKu>iFCkqrMEmTX3OxzdSEmWB@u?F41)8Cssvr(pVomFD*C~bU`F~ z?ZRnRa$aIugqD_8O-o(2G)%}-w)4>Qg2`e0`oalca$eF=x{l8+t(qgDr8`eC&hz?Q zAvm{m=Vjmd3x~Y4cHC`?w;xnF#)_Kgoa~IWWW2XTx@_XTWrBAYI;fb%17&dC#;P(? zxkMm$Zty9LBDRuVl(V??v)Pi(aliEDz0XNPEE3pg#%K~rZYGefZbOfGbV(4r6c%OE zBL%_eN<%jwi;!LNLhG?4C1)YH^M4-F=^1nXobMgdB-7+6YqdDi6(!GX%G z4y3l9o0_9e^wK0^9M)rUJ@saUk}zP9EY7D`2~w=XjY{!klN*%WdsloYydU za#3opft7_R6CWI1(yOB_rLb~fuhfq9);n^_!(EY9;}fj!q=uy%+2;LxrG1cDaCm)IOJ%!9%`F7L?A}@TnY-U z&yk-Om&}8~g;`y9x0{ku|x)fG-zS8@iN0#s$;qY)k)08RpeZ<=jYn(mGma(;0$>;|s znj$uF$LQkT#tl77+Sy&zIps!LSVvk)it%w+nn<6ek*d6-+O5x9k1+bH4$g}vcvIkw zwpJljMpqb&_L#)T08rYQet?zoJJ)K~M$Q}iZ$~jpduM3NmWGSAE*v^^;ns^9wzLej zTpajy5x@9~T50rsX`i7!C+{LPRQ*~!QOU_!IhW|aL8wQb9HB8Y#MXnpy%g9|itqX* zNlIx>k+bBBS=cUd%mQa{a8t(8QWe;QL21KeqL{(ssY~^kc}fKZOaD=88?^+8VjfU(zQ)TfG7;^HS#AEWC-5UC(;?gw42>w6`*-|l*Y-N+~tVUS7!nS{59_2 zF|O`Q6k*+VIUg za&b6OJ7;Km`p`MGBWDjzOB+1f_-Rz!cblSOjj7W%NyJUlW^9p&TV~W=IwYOdzS@zq zB;u@*KkyySY@@CGtG33@Ou>>UGliZkv3EDReqJN%Xb!REXMCh>`GGSYV{TYtJI83B z@lh8ueHJ_<_GFRI@UdcT^|0<1u4-!>;p{zF0=w+oP!lIJ`ub}?Bg(Bc7SaMc?Cd3( zKd~Y!N{k%>zs0u@TRsE*R<8_|=ljyO8eKBh_(e_j*fBM@v>RMmQC{2|TRgck%EXvt zq-;%S{UL3*Asm%C5VgTE5IR&=JUCMl=H#%SxQdFBbzjk~k%j~Hf?GV@KAB(ZaR5!* ziQRRcU9NanaNDT<{5YwOfBogW*n=38T>3=0H1Lc6YZzTFbRx#2{AHCuc6k74@t>|}>UM#wt{*1$}vID5(^!t8qz`fIOYotJqWVS1R58*Lt645n+Wk zoE+!DM4o13h;4G&J2%mBq=zQP43?F4zjtO&T%|af#khS6%Pewpph#QL^$fJW=HLdQ z)44l5Hg@TX@aLm);&USRJ69~ledO2sHtw@ck4}r`f1_`d^Q_lcKB2>lOVx|Ub=+>5 z?2JlU(vfC-jO9>6cD(Ex?Yr^!bwcjX6EZjHRhWs-?MRnifk`=7{&e)J$&#m-LC26-hu0AbejdM-K^P z=Ro4KTE>kN|8PtfP4+TK=U?>9jgvK&PKPZiaTvH{cswxl;1m}naZAN0+FCbd>w?PB zSM6?T*?raM%K0};>2PM}8xJ+zvux;sr%vB|^XaD+3|)Rtld&^D+juT!`7_bx@Y0l@7+CP#@+8-y>QRgrj$J`nM^e~z4Dc@zWSP0ggY=E4&i;;hQki9w1Hu=*_8M~3#6?iG=AcY{Z>h-FTZQvnm4!4m^q_hRQloHC#EGv#{ct% z%2ki8DY>|&aQO0HUNZ8sF$Kt|Vk^f!A6;Wrk*c+e4-=99G<3Bo{N~v5oTFjE2>x9R zGlF?4EH98)e={XpM#wsj1c9aN{zh&=u3W(rYg|oFSuXzW31;9IbzB{u7#^MwE~nw| zwf-h$NM7DhnMUC#-9^s@uXNL|rJM>p0qq8D8dUiG{iR1vb1wkQ$$+z zVwz}$^TvshI5kC~C>WZFp(G)hNB(GiBY|jE>9tt?M~~7$`F*9ElL(KI{di_?V-h~n z?}&`#Zhg4nwPz--Is4u(EqCVj9-JK0vCv(Ymr<2v9~+zW?POZt5Y=IKTOZIf`bp~H zn(SfKy?cpGx#~&7CiU;vYiRGGIa%V@Ol}G3lYP4To=}>G+ibdiWDg~T&{1EPBODzk zJ+MbV+N9ia^IIOIFR2YCBj+%?RgW)`YkzWC9pZmFZ%M#NM-Hx;PH7p@u`wVTj@lQn zU=eZ`UoY_mbY8TjpzBRpF$@*xgK%nXJykXPqLGk1Y2!uHUzGr?1z?Xrd^y3>YSGOWO98?|SDbnJ}qXqSC?>p#D{?6!HOQLzn? zQ4S+x=%uyv1YTpK^?mE>SQM%>5^B=3GC2}aA*bRqE3+!KpJb?s_0Sl)(CBW9Bd0%N%&sdQ= zIX9`v6pF%3T|l?uAD!Vxh7SC{xol=z7kN%GyK*^O6ILWV$+qYoyodMJgseejdEsfE zRl^1>S~#yXyNX{~o}bhXT--9R^n1QpXGJHaMy&2zmfI^gDk&{;dH;U7y>mPEMDg|~ zI@m!n)Zt~Zh+hs|W= zSI-Z0*1}P8X1x~;laPxK2OJqNZ_5qyRN^hQGWQPS)796k{<7npdB)8h73;5A-|@9E zVP3}!>o30ia?6n|XU=Rnf;4YeldX5#ZsF8>k}9pi$v{FpD*!b)aj}S!o#PuKOOihi z(rJoPSGhLH(~zx6u7EKkv1c1utmTpWcB>;ky<==y|MZF^0N)QeS?b@1wi!a@wxyGYf62`^-_jECPc9J`iF?j*en0}sxHCR zz8j$OX{wv3%4=wVG*xJj7Z>evSVhCjwC4O|=pSx)u)m<^W#NXt-FUUJd4sXJ46#1zqnVi+d3NdW;n>v=$`wxBu%C zVIL>pt-rVbM&%mI{ihfFUWik<3mJynHFozZz*-s?D-$<_U6x(^^Ca1yW3~(j&>-yY z;c1aW=jI7`8B!F7B$60NEwKYjy_AU?+WsWc1IP&DLX*UpyNHLoB@uLXoA@m5Av4oD zr1bp*Lki|k0xcN8LEJ(T>q@~uJ$^;aO0ne-#7KWSCO@!WMy}@Z3IB~y8)s_*Sa4*= z79S$Ym2?qNMx@{fZpHMEGY=egF`80H2PK<(;_v3bs-%nuhOB*ZP2r?0hb1u~A~oje z2Q4joJEmDLjj|^tj<34(o>jwLu1llBjN7;OKa*o^I`cq)qo?ir*s0wiKGN_{qPit&6DioJ)1%7f*&1z-%C#p;ffj@yZG26M> zDz4biO(eaN76LPoV3d|)@}G6Mko2MXh!B*XnKHuI z1NYAKrr_uiV4ylO9}s0CgFJd#u^1Xs)<9;0NQ9u4E7U?ZW-!Pzjv=@d#JB=iquuc! zHi3JOf%%0-VoHH1>26|xt9MUa86#s61FjlqAsDdu-o+RHYFx|SLCbqxd!ym(=~QKGM-4xD@{u+5tBJ3scEHoiH^JYm6#~MN@Vx61k$zI-!i@oD3wU9 z3TtG;x2CXo@Ze(eJZ`$fX_?`8msyKfPOFNiCI4wv<|=wEXg0j~fA1`+u%OFPR7dAo z)W6wIHZpUYb zqobZpOg$VG&0hpX#47?zE=hbhsWj>H%FK?MV~(w&ncNw zI=1xR%5LksygasiRlk&e_g6S87F7JXzoY-p`oGoxtpPOyn+HuA^gk<~uKZiovxAF< zG!AtSy?p4-p@(n_nmcUMuv5dAkH{R+P}8gC?wYqo&Kdb;ZE@|&QFqpTHRkcL3&yS- zd)3%2V{adO|JcXJEv-+eo2udE+cKe2vheM|ky38@qECzMa9o-l60v7M)i32B&oP7I~$EQ3$<+Uk)nDXZ-C#QTl<;>Ku zse7g#n0k2XyHh`$dTQ!77dK9;oi=gWtZA1`TRm;#^mWs(nX$7myfLXUcjl;B*Ufr) zcJAz(X5T&gkxQL(ADO#%?kjWOocrF~6LbGI_q%zHdGYhI=k=NQVN+q#+NKRnA2dfa zw>1CD{KfPCu;8`@dl$UBprfTv%Z!$tEnh9Hy{vT6;zf@v9=GJ^iTgdFQ)n*DlLqgvr4^r``lGJ{7c#OzPgUvoiCuX#CIs0 zCGwxG2zm4WXIx7I+M|wJ)74$Jt!fak)RC{o**B`G#!+<_r+qd9^;WlP;#&)rnde%b z(_y3K6z8V5s%qd(U@@=^=;aUPc?BTpwj2LTT`eHbz#YKfteGm$u}sadPo_7HR(tHT z)GYf6wa2y%SjO*{`2C66W4TPdYi(22_NnS1CXM%C4QG$McT}r6w!`Y6gmx$2^3@RA zz4T4{)C1sjhJB-=*-DPKol;I)yP82gZnFH%_c`G#^1si@ttoI9TE79$Ty>ZA5miq( z&X%g`Ejv`YwL?{4^ZYi*cCB&7cZBzM@%)Hmsk%+x1LJIy2}xruRJU1M)NsowRl@r# zncTvxx_M0hra_$`*Z)Kp*^@efJf_93c|B~NYO_a@%AS-zuu_o+MVx2VfB+@|WF zeTk;K(7qdl)%rdWUT8iDFZ^J+%y$%=cN2aAoObk46#+g74?=-*ZIs`d7s3xeXkNfG z+d*|e=-v}Jns_g~fDfTyQNC}X|DA;I0{?ECsv4-T$p_&@C_oo}*8o>`0eGQ#Mkw`` z{H3lszn#=ocn^d`D^5$?l?=E+6LSK zFY5S~XMaG|*xyq#c%EZlMg1Dpa9|q0x7)5%WjqhDx_#32ynuw#7XJoZ%?-p3+gg=w z+o{sQ_b1=qv0J^v_qp$Lm8te)g(e-JX6T!+=Q`Ls4!8K~e50H%XblulC4@#e+l%qW zEi&1*x7lf5MyeTF52!ZdYp2x`!=91MvaDqJRE=t?4)>u!<3g~u zXsiZU4c|X}|KU66d&RfMcc*Ww?<(Iq-%otAed9WkIubh^XYch`IJoVv!eAxcspFZ6G;e#LE_vazU@8xEl(Br@TKd&lr zw|L!6mwCKua^u38rCx&@8rpix!#(Y8E$WtJXOt{ z>GpYMlbLh zqvto(m3nP~cdFR)w8K`=+cr0Od+mL4 zJzk?Z@JwxWdT{7(Gs$l(xr}Y{UOm6FE>A0!;HmQ(qr7VPUY32RcBwb4M7f*5^-Ki! z#%s4VJ?*d*mwKHgyo_jak0P5U!Oj0u8a?i6etNv|3o|ovb18XviPu@|b@nOsx=P&c zu~albH&1hw$GvTOWA~RCk}RTxt&?Q0tB==6Dlhd$mbeFkx6~WOd#g!!_!qz>k1Bbe z7SX!lo~oxKjU;l6E^$|bJrJu28Z=jxdSgn;QmRY6u|0gH<_m}sSMsz)B^S8M++(#c zSPI5(+cwrS*3&%SYj|q+a^oo}kqqKtOEN^~fBn^K6ffLX=5f2Lw}B<0^Q*f|e|LNB zh?Y_8_BIKpYo;_l!}h2<;~8tAEq!KfEu3_Yf@M0gXH1jVJ_`0Uxv8Hi(biE-Egr9J zRC5dbwvKAf;JIlg>?c|?`ON8jgkr?lEYvK&6UGyoo^sz2t{C6}-LmQyNcwG+}t1&X3{} zZ&EOJiTu#9OZAh)F4IpEwQq?xIT*EEe(0$E^pixb&`%Pze~C9G7p zKS|We68=<`cq>iKg-UwCshnzo8^uwFqV}xvCXS{N^=hJ%n%>wc?Ouzg?wN4B85BPg z5i5V=lKDGxudSD3^wc`u*(_WX==^WL`Z*&g`Fo>`V(bUFX{tEivKtG42W)oV>5lC> za*R44BN`ibLktuwu z=e2yvq-?+Q#_73al04wO@(KL}<*V8WM69(k5D_%+a{k4&H7Aoa#ou^qFk^hU~UZ5O# zU^7b@%In^`WsHZ)CfB6J2^Xc+xT(8vPv{^LY}Q@My9`+2r_35+n(b{BPa;v$4wvXy zW{L&8UjdF~yj`Hbt$=cFWK8J2Qva0{lCwy*v~oAki}@z`N=p?Ai8Q&fE3!_6&(iTh^lDf&0lqEpTH9Gx5 ztwqjltINo5rT!+m=5o>s2NqI`C0YZSy+WWiW{VEwExkjimR%19N!_&_c1lj5B}?ff z2eW?=+#;#c_XVW4g*RqNR`4Xb2<@Z|twcIZ+6aw=XRG;Vwi;88!{UQT`!4gN^XD~QVNOHj8qCP;c=*}NKH+xAgxxktF(hMauyj} zg?{MEzxDc`**8d=F!hnpOmxk1V*J?nT{$j2dfzU(ciuVUk-q=e#y!1$bZunh_5aUk zNqVua{8|GaMG98IbK$;dv1Np!{T9KQNsRXz7+-dmiwjG~Vyrnh_pL?y_wXNkb8MCq z%gXK?=Rf4OCA%jsxqrzjU^MO^V>wwD&u(KP=j)O=jh(8}SRu_|tu%{!OF7U#mtM%j zp`ij*sEVjzZ%)V;(~e3hbzeOyu2B6MjSWmLk~vb(vBmeG%)hlNhf}VH|cbea3XgpqJo@W2Ty=X5(|=QZ-l2Q%$Pbu&RgD zCUw1fS>2&NQJd9G%s_svcB`F+jT?-|)pe|`p2oWPFR**JRlTM@#B#-b>M^`|e67Aw zJJgfvcj^tSv(ASbx1e!;Pis7)-c)~3N7Y;EZS^U7+HH7R9n>J>aco6{YJfH#H$bFKBhZ2*G6xM#zjC3Qz$mC#Twr%3{hK8_p zE0&a(k1Y3xbzvh{E?T)_;pJh?{&2KETxf=liOmbvtzGC?VFnkQ!5TA|Wd>L3z%{F7 z<=W;23l^?e>$)=dsPm~9HP|+K-RhO{qhipY!LCKCn_Cx_Eofe|FsfzA!qp4cELo#} zMJ#V#uzKYR{p`|M^<&I}C94;#TYlNHg>B}i1(=UzG$=Vi7q7xt;f~QO(k|q^PFd>4G)z;`%bM4&pslbe zrmY?t1f9>MynNy66)M{Nt8ocF*(H}y?B9K(JC4T4jR{ku z@*_7ICnKxFODy{%H(Ec8+-SSYb}F*k(dcx9mqag)jgDAMtaqZ5qm#o=N4?|P6!nhz z%T?`q&$ZgM$@QM#jN2TyIdUWTPe+`N+Z=IP-Xu)z{3rQ@{Hu=C|DqR1ZWMfhf0B#& z&&(t6D>_-q5S1UD95qpYpXI0}>+T3fjz&Fq+N6{R|&=c>` zT0he5_7gP8DRjo?v_8>C->7fVi{ZWvD#q8K;(&ObvwVkCHjsyY&F6QiZ-^S>JD|n_ z>Hb52*16)bs;t`GK10+m1I0 zFEaEbtugJg^@)02~D0 zYovRf=Qn`2fln#p=fJmo!@Cte01JUWKm{-WsPr9GRlfJ2_q)*hUFiKT^nMq5zYD$J zh2HO~d-#4Y@N3{c;1KWL0%ix>Kx~(G&_p|EBDec=J18XV1C;uz$n`#Qy$@PBpj8>P zDuY&K_(`mzeKgTNnxI=5?V}0WmC-($)C}KT+DH>p)PNMNRkMAokfa86sc$Qi)PN*4 z(0-bbpavwT;d|TB4dlHY_$6=$a3^pVa5u1nGCc(B1Re$+0UiZ*0lO*7W5DCU6Tp+e zUf>z>-ADK=@Eq_wupc-8yvVngfR}++0BL`(f$I=?z0UVHfTO&Bi{Eea{1Ih82?W~Y z7d)Q^z5=9uUXFe(hvIE$*>bdOIZ{|d8|4lKVLb1%e7m6hE;LX%)ISRK+u-+6sDBjd zFNXR@q5e^*e0+;k(7GkPA2UXU@Gq}CY(k%op6Tl4s{89Z{&HV?;SXB z6b`%t2lk+u%i+Q;aN!oTb2<8X2pW0_9N7g&Zb4HIL06Zx z(GuEd32n&VQ8;rH&K!j^N8t=-dw{!v9hBoCU?=b}@CfiIunX7?JO(@tJOMlj>;;}7 z|9ynd0?z@@1N(skz>9o)33wTJ1vp6lukrp6dA&~f25^-3Z}IzWoXj7iTeLF(9wHa=;!>!G5YX@A)gF`#u%-wKg7aVDa zBkgdc9d6tQ7uw+fCpKtlCY9Tva=V|(2$64k)$@YPCbHcBs@2mD-_FdkB>_hfrxVRBDGx?NF)RPo*8KM92CLBC98m(-X+) z31svHGI|1PboL|T;X|M=xdfS;>3aoDa0D5A1=%|YRSrUlgUHxHaydyZC&}d` zGPnmB+=C46K?e6AgL{y{J;>l5N^p`AoTLONDZxofaFP<7qy#4^!AVMRk`kPx1ScuM zN#tw~r8r3`PEv}K!5V+dduFe`_mP|Tk(>9CoA;5M_q(*BKnuEo_uGMA0(Ss+0(Sv- z13y;s50NGy^>6Vkk{=Ik#?hw7L6dQMo;(DtSIwMy1mU&3yAId{Tn}spwg6j!`-yXq z@EGB@gt1^c0+ru}n{R{h2pErm;RqOxfZ+%jj)36^7>Mnl6G3y$D-4e^_RWu#dRYyhq${&m16 z;Cf&)um#u(+)vtrgvSW~1djKB_koW|_X+SRd7dKt4EUVqZwS97zKeFhl(xH+w!4(J zyOg%Ol(w~$wzZVDwUoBCl(w~$bqYIuz6*!~VtrRr?zxOOH&gD#lzA@NKs3P-%Dfhg zSA+3t%Fb#WEn^*U1;4K%+`zk^@_aw(jsYL>d=mJW_n!c#fX{$0cy}833iyWKag<;` zCD>01_LEz?ct#_~c5>WLPW#DeKRN9um;L0jAB_9KxF3xB!MGnR`)QGO-%haXgeUD_ z+DScbq#o_m;YR4*4(5~etcSpS5*`TlonYSy#+_i?3C5jZ+zG~=VBE>tg`Js3xNi>B z%tmu$!{xgP!d>u6%L%OlRIG~38ulM*4L;b^OtHWrk!)U9+ zQ2jVmKMvK8L+RsC`8X8*E7W}-s=g0J4?xiaQ1AehI{sm`QEZAf+|wUV*l5o&dWS{JcHxR$ur0h@s9fz7}cU@LGxX$}$|Bm5I--vizUJ|^8K zz^CMQitsbwb6v8DNX1dsIl`GS#!${!zQhs6^IPU;`Gno)XoHcWAwV@UHG;6t_bQUJ z6Uo_$>!Va zfStg@z$3t;z%F1nc|8U^4m<%o3G4;-0nY-@0nY>bfdjxn@_UVRGH-mH-){gie|($p z7A1W50c1?1su!4u@YF#^IH1db-)$;zKU=I?|#bj4dB`i z{1UhWxD&VwxEr_!{PzOC2JQpyCyztCe+xLq^GBpV349E$Pk>XvXTTS{I}LmVe8X>9 zTXeCuzN*XGBe!LFFP>QB6tS2E>HLkQIP|_l7E)EA`FGipMa`tAcnss2c+K4mai4(#4{!&)OTlI?dhpbk&%PO}n ojk(g9PAtLVP?*9aY%s*7ahy@i96%iYv@+sD*A5VS0r$To4|7PxP3hMtO#w2@Z&&-@TbNV?e5J3#X+t(YKxjdyRpewi%i z@3soLVYg&3h_Y<66Ji$4>VSyZD@sCHS+2t=lqTdkoTStd3ON9i1f`Hv7%K33+-fi* zi)CfFVJ{t>^&F~C9w zIEckzaVS2QWK@K_P+7=XTAG)4+iloxu0vpZj{W3I^@SXGjukt7rTm-!R#2e50_;cu z4|d_=-$eme0N){re|rf2l-}=3lIUAi%d6jNX>X85wO`8r#{`f{)KpYeRaRD2 zRgg1V)<3jm!`g=)rL_M2byBV@*4C0LGH`G0m)~8vbouKm^%tLd@`=Ac_4wmYUH@F{ zJK5B}n{RLT*Y#L1w9mpp{YH(vwtXwv+dVSA%k1S>7r#FK_R*6{ zH|~96!{kG^(RnvLF@}Dzo_>08((w1d))>biWt?NUj|h?^DhuM(#DR~j-Tm;I^(%I++bZT0 z19^M@OSIsZA8GOPwd6GhwKJfa<4|QI;0jioQIfNpLnU@m7c6!b6^hv*uTwNXvUcZ+ z^=ls9&A`&%X@|YX$(bL2A#cC*G|dOpA>u;Ws=5WM&@P-LDKgPnj0TgzW|xh`BAU$> zBlwrzDhOq-6g1E#J3O}YOrPQc-^_MpmMD_ACYL;vNSFM!@4(IjKhvctWNEJIrn9FW zPNT<0kV$mo2vVMQcq&=Jb~8b^Ea%84K~XKkEFZbbx<3LgFg2jgDPG$CIrqB;SK{2&sLL>;v2~`Wu-dToLqwA_b79@4CT=9*izs{II3WnpoRpf$LCQb*vI}VA0BIBhzb|*{mURP1T zQ$@Zf7dCIk%Un_kKV$U?VUsEdW{W`@5HWZ`=5e|x;g?mDNSiyCcIeWzG}LWUw;nyZ zbt#7=B7z$tW6-g4E@uLKCaht`9(MvUqlK3?gh4I&aklXNE=mXZ=PQk1S zsz1Tt=@T*NBwL?|@V%jA$x7L|c$TN_z8NGJ5TVP+ znc$v&$b4S{(YXYdmB@)nc4ME2T}Kl7Mi^kmJ{q|O00)Z5O<)d;3+l2NU-Y=a8$1Cy zv%ZWhIxwyML-*eL*w~?OTzUJS+diN#ia)LK&=glXX4w4J(O;S$a zJe)$OaH>-=h@wd+36dhY+(dCIeIo{&&gqnNs?=~k@teh#%p|F%MDe>t@J9e=76iYQ zCtY|nS}GnsaDgr}>f5!VC8UxbAeC#RPwR8Y*Q@JBmPO|Pf7QTW3TPrx=nzh`Nq)E9 z?UUr>Bmu}01l8lg8G2OJ+$W;`0dkfFkY&8G;fQ#wui%cQzS7dQI=rZ_M|=!Zngq)_bcYi7?7$#h zL9SX96@puOo!TaFZ<%J2C1*w_%<5h_{>T5A%q53sy!_?$cQ#zQo4QwTdwAWgTZRr? zCzaRNxK}6Ql7k+eIy=i$dI?kk^oa6DT%mmKm~u1SH1+i@)f!RxcS z21Km(1|bq-cICm}RUW)%>)0Atn{AbAW3!qm|*dPlEkwLDI zyUa#oib5tSA|O&Zr@l;jss3ll`ZK8+O)hPqtLRaNley$2*)LrN)#`*+VUJFf@uER5 zE0qx#PL&ZGA&|tk;c_x&+hCqCkxcMCSK3`aLfS12TfUs$v3&U-R-_P!SDjv!8du~Z zd`S}|Tm4U1QW;rJ%E{~vG%%VDV=GF!epxC7eI>z?DGjFtz{_+lmym3gvtf$2u(wDZ z5NYAH8+%9WG1Zn|6mFHqwIoOaW{Sks+PIK{FjT=3o7$^^UZa=wTrz*a#!(%gfBS{^ zvisgUuFJs&`?YxXrmC5<29BSanZI<*@uzxE9dp-^8Mg=MhcTm86y{woSC1-n(9FwV zd1M6J7F#>&+)09vX{!d(*Dx0gHX9VUF`rWHt8xs668j4Uny;@1I$z=eUE--GP< zbzt1iCwbglhKVhL8M5eM+_1!>2Ga#77cBw>vXkJs%f%$JdUWW{2}9+i&s?I1_mi$9@7*t6{Dp3yQ$HXpiSQvAaOB#5Po8yJdktQ^PQ38Y z4~r+7L1%pJ7Sj8U_vLrxvk(# z=|b;tmP<}C+Wda2QPx?Z59n{)&jzDflWPc;6pbZWI2*MNc3d(h+)v z>~PCL810(O2|66H-%gY5P#EPkD@Z^3>lG^2l7zY)2VT5N@}GJ3$Ul_B`;RYv(r+xK z7hm~A>OTE}`BS26qMt5byX-!8TNZ$up5?M43tkti+hCP^2~MGR#3`Fp;LFvpJ}#|6 z*cmu;ArwFULT8Im0nBZw<4^QEV)=c`E1NIT=jpB|N!dqV@9R~ggy;+OJNh|2RkER! zEF%-YAXP`J*7j#N4H#2~0%OqEnNDk!4REbFU5ce5qDZo}BEn87uKYDPW3~7!($#b_d65hz_tasouYNsC+K~eBJGzlBQV!Dx=o2KBWL%pDr4xI`ywY!& z*Ti%d)o3KLpeOLhsD|DVh%Zr8dPhWw;D(dN1;o_72Azd10yYW%mVT>0C*?)wizA}D z#cJj7CYrM``gN0;3_?LT5w-%6^h7e6ZkULv{xKfHh%v5AVt26_$jsU}cmR)nw<&gV zN@vceunV`|FN)SAy_}pPB=v^orI^i*-Vw8`_Pvo__2Uj&M;|&uhJN$UH@d!dg#L%VM*<{a^BVe`NTa2hS!4wn`z5*M(2$K)^kw=r z{fNFDBrj`wSJF6^Lt(!GTwE{9ilA7`dZ~9ruUCw!1Ra&TL0~vExrLkyvCTs%4dMSG zC9Sre*49d5t#}|>30r%uco)M!6^LjEpP?k2tPrCFjb0)W3^G}jDe(f z)lDc)f&OTPAeN!a!y>?PW2DTM%h$`;eZX_NmJ#gLFSly%Rk2XP1!_&u!($0884f$Q zhZr4_3d_POz{6J-VqQrQXVk1C;UJo-Pu)+|2!i*qNCAI6V+lfHV>dARpYt>V; zV&D2+{RrjowF()wt+apliK{=ysD*i2>_XeH#{fY|Y&I}*htnkKYRJu0%|FHx&Cv#efx`2ie9vekIH90s;hqq6Lv@#(BW?loNkuP4|u%WXaY$ zA~jx_2AzX|;V^;6{@>J^R!-3E^cBYR$Vk#1*7E3UGowHK`?p_z{&y5s^)7lB?#Ki( zjx49s=uY|}eTx*3R025Xlo@>)a6*OQL?iGHJPR(j5j1QxsydysBBGNR=9_W} zjUQ?PTgGhx=F>^c&J4;E=sEiJzFIP0Y!NkW`QnWer%%Y{&;At!9->KWcddDdV}b7B zSg=9YDGbBt_JUp{BY22p(OLYxBNkcb0{ehjUiMmpdDL)$APeAcg6PrxIaa=Sn+*Sz zUM~4d%zQ&vcBLChh4_Es=PdAJhX1iwIMYIiXf`->CZkb@7=hPku;6M|L@XBg<~%q# z$;5mqp(eO$Vxluz>(7j%4SbfYA((-FOjezyJLuW(>h?bM{HNl`=uYME z+h^%#k!x44UUMI4mRTYqIB*Mwy;K*J`2?3DXJ(mGC9fA;*egqrC{0L7z@f$2 z4|5jC!SuMv3nL?&TT)z_LXBburO)e?Mf&ZJw0`BV_a@Zt>%4mHn@`hoA0H_?^wiSL z?W>o6{dZFR(nsC)WVK#2v+t;Zg}q-)~SqwlDj^9RCVLqJu6JPi2L!E56b zh(aVu50{Y)o*+w1z%^xyjMFeLni~BloLh*%$}Ex1N1{LNjs8+cDm!Gf z=%9Jb>ia*ob@whngC?x&3uu1kv+^605;Z+r>* z&9pl5HSKtuOeS|7q8-I0(FJ0?cr5z7*e%+Z!Dm>T5BPc^J#2%YTG2@cNEM=!n2e2K zj%gOa9e{%T;g#!59ym%9_EG;a@uGOKeo^$am?td-w1I%exUqrx<)E_Y1f5J|qe+p$ z4kV&)rWrLVEbz;5pF}_|t9MEIs9k!Y{x>PLTHd^+`r1Umt$`nVD&Xp{N~;RJ3y3km zsjd=uvLROZDa6Pprdx?q;d(kvFOdK}u3lYrHNbYuoIIZbjwHB_0DU+3PhG&>F)>&Z z{hyfo6z>SpXO6CrB%N(?M=uRv*n=j z64-M>*ns${y|ulK^r z9fz2;Oz`dy&VnBdrp3_F(hmCS5`mEA@&(DM25<_l!-8xws`tSZPKWR}tc7QyUsI*bk+9u};61$TJ_8bjlnLEi~Y z;w&*rDsXbQcUE>#$23Z}E>!H}3|8a&;{K2R_0tEFpXp={&aKhwXT4dwack|CjT_}* z^aA}EU+)hXxC+sX2NsUov*P48-<-O1?t^z3HOvHtSIEN!uK?T02I*sv5`3x!E3*qu ztc-;Onz*j>+bJ>IiNz+Po#|iv&4F^(GpJ25*R7ZNi>9Jv1#EjrM&||PmuJVY~ zxCkbFnxTgiU1kt|JS|4x3uR{mKS6$$+`G32y&DVV1b?-}D=zeDd~>QH%22-e>+k zbo;jZw_C-|E8V0eK~#dYr$2o*@uk-*)@KEzul8-;{scR<6c`CsH4QNscz4`pvr9z$ zUy(dsBd!7xAQYWa9&zbx61xrDTdqYP81}Fv+;p%|W}5_yO5mwx#xE1skeEB@cQvoP zLPp&(J9lLF5yOa2I#pjPo$Ax6Gg%+3PFwIGbWcghrfxYOm}w=H2wjA`!Z+pm+h>~7 zIxG1u;!?y`8Odpxeq+~e$+jX}QF%n)v1i1Xp>Ji=+w|U6t;C*@R<@R9JtHmc-nKm> z-lQ0f#cwUV*Koa+mgaJUvO(KmH-WPlPuz8z&8}*Q$4*$Vg1M`-_<39lX^b^vdpsOn zREXc?{4>d|KY1}fcT&Z$mk+)`KcU}#_}zW8T9t--44(YaYeRZa$HtZCPT#fp)bx9Y zEuQu7|IWTw?lmzXIKAht7xnE2x5?eO`tS=o*N$DA=&CI0G^|x{@11og-PeT3@CB12 zJ?@k`&7AYYulM4d4nPKV2c3Hb*pF7T9_-p{@cI(02B%Xlk2vk{`WkRX2%^XL$^|() zBS=t(M!+~H?Zz`Vmd7FOxM%U-AFZj;8}kp%I(=Gv^@013e-J&1+nqDG{eW9vcqdxK zG~?YMl<~?XoR$sp+QIzGgxDPxU9X5mv=K%bZ*z+^=A?zvge(+3g_edgq^72>eXEuo zI<#!nUhYM5N{foyw=XFH?CWc&o5ME4pWiy{b(u^Sz22YbwfBm6!v>oG>Vww8IY(l% zNnA|~OncMWa&Pb0qg(&pjWbfWf2n)$Epq)eJ3X$uEk3!1(-h#;E)+xGw;GKmlU|h- zz3gy8d$rs3dP!&UNH$Gl$OFZ*pf%J6{KnBrxJCp4r&*(J9jVG4N=Cm*yOWEj>4F7& z_UOg@&SS`2+9tX}RPUtYRd@ZV667JV^CUwsw3sYigDz>GPhNM92Vr{;|2oo)b?mS#t2X=r;EA zAMZzBl6!5e9CmET*caYq6ceLXtk*5%hZ6)hBUiV<>oFVbb`Y!GZnXUsu{NjGhP5zC z-M5o1BXo57Yp*j(J@x^g_vQ$nRFedwF(;!W_|IH|1$tVd-{=J&vrG17%)hA>i@1$g z#C&){;~mGv9!-C~vhK^j5%ZO=iLL(my^lTiHqD1@B$?@q^Fj84{M% zm@G5r9z37<^&6MZp1rUFP5=|E&9=20e}9v_Keqe{jgF*jtkaHYh}&W?7>y!)AU2C& z^l;7)({`MtO|wZzTnI64$B-xP>D|HCy|DWEn&mEi=Y8^U^H$qOyP|b+uQQWpVHQ}f z(oD?K0(>A@vs@KdgXLNt@!D0%&_F+V8iQ?ayFzPdv|Y1fxjHdfsF|+P%rD>m_?`iW zsur)Dx@*g#vX4(Z^JK@zADDA@o3Rg_L~72KnjTwQwjDG$eCti6gYWF~z$3kucCYBt z`lj|pJs-vz)2{y@?p4ac|Cw*X?M7ybQ<4*WMwi{yGhz+fZ8{v3E_P6fZ#8r7A@GFr zfvx~8iwWwIkcatF-CohzdSF7*#8&j>?c2*olAGwuBW7E43oQ;ZKwMeb_+c_o>eOM!i_Er`-w|uHXB_=kJoIm*!6DY$lz9cO-vFn9gpe%jn)g7v|PL6-oRj7 zYj%*nxYUoBXmb> zTsaTL?O%A=V4jAGoi5mjJL$-SKU7-vrrB>Eq$2@z&X?VbNI$XNHD*u4R#Jn2mJDg* zHYBB_`n*;Ptae$S=!Ce@Gu8>_sae4+B_L)ABm99x02T^yIvGusxU?yYF{PY$+Eu7; znR@m``u_e&Q}udNzVme5EA8ESIrzdpdQM!@@!Zq5M;F3>Fost4DLqsq&W^6wH@n3~ z@e?cxtIEb6^?Z*RVTVppp?9%F8lpFOWNel#iLFR8hhvs7v#WySC|&YA36S)E&?RKe zarzd0r+Rk=f>(cVd+1m3Z`abFd&E&~esLSy z5o~5@3TFJH&D=Ah*oeMogh{)m1_2j(4K}khrT#1UpwEimL=TF$O_zo&T2y}wa9Qpb z+;;r@JpRN)6Asj-b4!raQc9|;Aqh+i8-O=y@-7o0cn@?rrUw-v5b1z=;!!=Yemq_e zxj1|JiFfY3d-1G~4u5^=l6m}aaiv(hmE=u`tP+QhBn6M`TcN&0FMOD7&i)Xx`U3+kFHy9iGdKL}plIHaqCU%yXIT5<&+W z`8CUB1`!5bZk|=amT-}!GSBPOsjM_qF7CT_N$I<^XSl3a7`r+F{(X_her^hqOZz;OpI<#?-|k1BSoP44#p>&CrxV9jQh(FFJyO88GIajJduKXgRY+}b=5Q2`+=hlIV4aCa+6LO*1F>6Z%ICem_#Y?0qAy1VKT9Q1gV7W~oUU6*o#Yux}Ox z#g8I*8jw;2f);B})OOywdj0}ZLx**#kdm%ld+W8={zo}oRo(AzE9r%WpN?JDdh4pZ zUoI^qoeqMxio$%N%077~H0*5npe)Hk2Ewr|bJ9GXOdUKi$W!P1Pn+FUSdv}h!&&%B zd^%*j=zO|t#vOFoC0QkLtpxZwTCsG*(g}+fj$eNJ0~HlZhA$t#@PV;QhgVlD+CFR6 zj-4}RZ5PkqGkN9k>fXJphd(%d&XPOuuqS?BHu-PUc0M|N#%>f1K;i*^EdJsZhJ@QX ztvZv*ZbnX*K{C6o9-S}2XtnBWD)PAmm&fKxbBQ*W&1EJY)yy)37{5fOIE+7@|IKVw z&OX^zVyDgcD8nPZpeexH#J=DM>_z79rz434a~Ajyot`5^&m}u$>qa6~I24 zEBUUt70Yfg^RaMR#$9Hlqopi0DlJ*iT5yU!AJZ+fRdvOX{uNbcUmfx2sG`wJ`_7s* zf9tW011q-t^SzJn>;Lk=2UfMabLK-2bX|M@6Zy;6J=cA(lrv;`X3MFA=dDP|UXq>E zG2E%Dr2D4H!&c?oymsZ5F6%Sf^zGTULx-?ljIElTf-}JnkAndu*t`s(iBXB1XlBD|24+!FW>!f_R%Q`dP?VWjQj(cjq)abt z+qSSEKffUMlX)A)B0s&m7T*_&VM}Fa`y^XlTAJHn^C>0mk%8p2nbXXo;It!Q*lBhq zBXGy&0JpPFPO#LM*B^gy!F==6J$Kp=^d#hHe!zIWRzxIX;uH;7S?ExYX8GXhON zh?GQ*e@7-#UAV7PbSDg7dHd9@kI!B|c0%rq>cz`x<+RhIr`;h1sz!|&KYpSrXIF6c21+$h-27_!-kt%Lh^epqZ$x>phi1-M6 zTAA99fn>;B<`SPvYY{seI-7n@(q_}WVm8^-gYKrgdXh~^jlXg^Nc50{K2$6uh0P-T z&Zq^377k_m z1lZ`Z0hDDin4Lbgq`ldW(N>q$sdw3&X-*M*-z7R-PS;;j7(0MQp2ha9F%NG11%oJr z(7A#V4|Spx;{PBv@%){YqlVlzi5wi<@59VYHekz?! zM$;YA{KfRu|BK+r^{XoMO?4e^+@JgPW%b%GD3Eb41O+UVhKpBlCz;(I2wN@SddTHY zv>RXaO6=k#zS*=A2|P`^_KX;tN}Yv=7B4xa3I*z zd?cN7fCl~w+CZ+vjakmuv+lcQqAgs+E;>Ra5s zM^?emsf(voZhG)>%oz|%`J6#`Uo&jTZMzuwz^ETHMm)O@uOs*uC=yK^318EjL zyb7ERp#bW(IAvfZO_@3FVqx%)8P?*=f!z-r-Me|#y7}k7nLYQ8F_qoAPHW$zY~_e$ z+vRU6@9^dAzHfE=`Q3M~nLMC-=kB?|VQq@%Lep&;b1@*|CEo|0+!%9VWhh{Jx*%EJ zZfg1)t@nqg%0J(9po^!=i9+m-`9r$H2Ji|+;Y7t~)Y}CVg6Z7|3wUhqB#X#A3(RTB zMOc$~SDe=EJWvX83EK;bM$8{VhR~-Ttyz|6och(qp+;j(4Ou|1KK~v!8=n|nwUfT6 zve~E8dU+(YbeoWk7^4D1QZY$(2XbvBn}SR@t(;8D%#7VFcA0_>&gSgJp@2Mc#``mG zs`}{6$KQ%=XuW#i_ev?`yn2nu6!15TeaUiZ7?WFJBjRBeL>dM}kXjjYg~E)A(<(@H z^G)rw!Uyqu*B(8(#ws5&;r*-w6*`&b;INv&B3QP%)qzN|UC4X0peYyTM;)`qxR5Rh z>Aa+F%f?WfHXR4tJYdSuyGrWlrBRa%lk_c%bBi1YXJ%uaeZ>m$xxymIh%$<(+cBEK zU!`0fw9X7W zS&%DyhwBA@0noA6(?eQ`)X2SWj!?;Og>9|Er>?^T$WtKyR-+2O2gPC z{wa3dOz_qgYo1dmg8o_2u4Dm8PMx-!32xn?Tw61@+HOvfDIqS{Dt|%VG zZE&eb02%3yB#V{F?>N z)J0KelwLj{9+OT-kBceM%VPIyRpi7Ro>m)6o+R&zhZq$t07pfBD%P9@RO^I$L{VU` z^Bhi&cO+$VydU8hA(lAKZX7X3DAPuG{$d2_bN!k&BC+`hhE;8rNrIDORV0B1tagIB zIfSpIwYC!a>BW<9=WpFg4w9S6-ws7jeS@l|>J#E< zEe%&(zERvnm(x9BI9k7v8nrW2%5;pX^BLx_pLI9vn&;_pycB4-73T2~qCb8@H;$M? zjM@n8fEWd_T)##bxLtMHh^*!?*gE(uc)ImZVKD4#>zE|Gj}h#4wbAGRQm2~OWwX2) za>5@r=-|pU5VJ)u_~X)sb0)6D6OB*r8%cMP5hIaiwr3m}L3fTNqvXwF@aN7kisAj-%U|6}RB}No4B%7xyy>5i#6RZ<;SvM)9-}PA;cS(K8jwn;P(20=*+Pr(TgI zyG53e!l$zIGoHNn8g3;Ilr}M%dBQt8Qq8BqavL>Yhh)ztUS2imBU#7~&~hBVUq9~d zI}c)A-Op_wb@!wf>+Xt@i<~><@z`124de@ z-*+HOd!$#lN>BGj;v-ImXO!`iX&*b?a*lU|!DGDh#u0Obv)YKHzZikjU7(FfZ$5&Z zoi@uPVKYBFkz{LU$8I0Yn)!SsxM$!dbA)^iijxn)-2yj}=kdGcVKEa6YF1ST6alZ# zf;3G7u8+lFF~DEW^EA0DhK1l`VK+_JKvf{qw!j$E<;GOac}Ug#{Ag|Mhs003^;h>I zS@VrICHv(o6kgx!!rACQSMS;IAeO~5W40l4p&gTvIjxkGpx`r`g%n3CCBMMwY}K}H zR(YhY(8|N(Zn3~f6F+0z+sY}?ttIO)M<;hFXnqmq3DMF3AS~fIPG>2-T&a`&tB1Bv z>e+wg_?MqOKDlgLuMY>^wQyvQo_)g07SbPTKKbnJ&*h(&&Mfa1NN-gdx_$e&olo`H znw@vJ&*buf^Q+1x6_pMvsvLajT3`9#!GCNA&Ut>48o;@9$L3U_8*)-1H?thEv%m5A z9GES5-u5q^lRh8u=V$|<$zA?_<vsZDm9@`yiQ&hHs% z-y+)~%hrr+Z;{@rFORsb2vP^-S~V? zfBwA1Upyy$a2}wDoMzAYiE8jCA#a26=yuw9UB7<)W6V2-ui)SK4cZ#L87tI3OO!Z_ zCn0AGnv9rpJzX1NiH*4aEJh4&906I4v?2z>86WWjMx^m|XtPYBM=-)zClbH567Umr zRggy`w~tksRfKZ`syR6s`NMv%)#0+MhU}JxwDO2a@;dxh8+0&(iymD z5k0nzztZ>Y=EwKeZKF>=z5D51Vu*f2-zAnWz9#BC`GXf8xwHDF{Mo(xJUDsoYC3~{ zyRMdQe&Wb!cD4t=AG`D028TrNXtW~^ zOIS?B>U3o zu6po6`faDqWgSYxW#TWfU$BCi*N@Ai<)txs-v<@EX~}N45fsu)*!z)Q5fk=yDS&i3 zmhJb~!k#5KL)gax7`%#$Q-nIK!IRm~W&2+1H@XI0G}wXpvAB663emit(;s_M%M0l-yZ@5SDDXgPNZ+}At2h7t)OhiMq7UBNGd8*)bdga8kE8RG zAF7@FE1WXC@sqpp`JCzec?0F#@SOB<^5?;3@8>70!Jib+?piB8fbnr23+5{r&Q~x; zgOJ7t;4bnk7WhrPLWXctI4xaGa@+05sB95*3XUYR*B}{Ekx%KBe7Jwj(oLHtn{pCB zpAzt6o^F~Q^zniimVTBQOJhk#G?onQT07~(AAkDbvwO_49_db`*WS%YV<(hIb0;jpBmr*pnJMGky;F1{PSUu`#&z(pBv$!*v#n2lmr>u}e& zO-lO3EFV$RW&lHzjiWJw%U;s&Km4|6f(_3(1YJo3g#JP<*M~$~T&vO`eANJ<|Cby> zX8cqH+EsBV$2ADBHE))7av3m!c125+VcPrH>*8wuI<$28EZ4wb=P?Zo-6fbEO&at# za1dz)KSf9raF97zI(r_U@dgOc1ovqWGUIE$0Rrj6c@Sw>vj!m_YvXG`Mo7}1twFhj z5o`@Q)caQgRsX+L^c7yxU}Q91#jeJEkiHNSfY89{plK>YIVO-JfjRmLLpcPo!5e^m zD2r4#R?(A<5Zg;g-ebO(AYxqim$1U@SV2rR&fYCPnkogzzCJ}d4sK4 zs*LYjgTQ!%2BANs0LOiDGYA}W8if9Da|m6VLtyt!gD^lGt3hbEv3w2uzA*?4b7>rN zQlADGv?Hs6)nFjDQA%)YfT0^!FLvPELMo5HkKM8~zJHh%fZRvesLkEr`{5dmt`?Bt zALS#`$Z~eB%#_S!6&CoXd;^=%1%FFbxHST+Ho@+e&1RiRZ?Ze#bn(GS<~1Vd#VYt* zK70|g-H8&cSX_gJ$zu)On6Vgho3UHOEk+k|G6MMK_lDQQ28&1$E80XY?CYe3I-OOUZd1lF*Xf`K$B?FWzEwvgdW*$o__*0|ghCFLHh1h!W#VRZy2V?ox`*+|{ z*N~TJSv>1{2Xa~8kA5RxTzKr*!dI$T;`O`H;n2@vf{AZ%j}Z5Ak%1`0<1`h&OV=O9 zh`YI-2eye*8Az9Lia^`EnB50j5*Krxpuy-zqZ*7jPheJnSipIL2BRPSy9PrShmndA z4|1-c!RSwa*I>lC!u2;XqCKZ=4MzXC6(Gl9RMD=iIvK zUbVAV!4Kd=WH&*ZZ-7wQbl=SO$8LupvJ}srR^(V7I`c7bPiriDTJFW*(S@=dl&Q_F z6WWM-`K{Ou%j{0%wPy)zcAd}<5(4wH8fS4FzYT3PTBk8@YWyyy7m_5N(fVkx_D++IkM%Mk<53;Qi56~ zCR!9#)(Zxk97|0E4K}#A;1?pP7dqQjuvE*T*!c2jMYbzqM{3Mhu9JNJOJD+;j_G;y(P-v}24v0|wheK0CDt z?MQCnJHz|fj_J32$9zWRNPI@IJxRI#800fS;kYKBahl@J_&bRzwg*n|To;$$8U%(* zHXrz&C`a$WjO=}TA;&0qP-*Lt`DK%QYf8Kf8 z3Dx4)>#>$>ti{CgD`H*TM2p@mAYn-luLJ5siGmVD7VT`9ixs-DR9QY7K~@#qOMFT) zWO=W1HFuTQ)kq^pOj{K#6RUbnpDU#^JOdl>6SEV{*UpEC+6gx2)m&C_>rl5dvpq^2Kz-bf7U<{w2&F);j;(hE~hy2NWkghe_eDT|$S-Xw%k-iX1n6E(>Md(## z`H$Jc&B9Ww8NKH~8aUx(P60Tv({v$Q5vudrhm-9Vi&gJ-`+TBRN=$MZtsa|%{vb`7 z3^ZoLGWQ|-f+-iT2O&WxbJMW`c&+(>bb54g&p{I>)xQ|4gSS5V67NHB&s6yg{cT4* zB6!#OZ}=an^VOjz-(Sj6S>k3a6P-%|w<={0Dd}|6%2-K*v9Rv0GpAPaGL?POyoSn#fkR(-w|>>u zTAU2iE>3VRUck;An%yaW24aKfmuXd_ITs(uFiiS!t4?h2CUFRyhiee}$24)VA?~I@ zV49r}6JGH@7*C3axILx->FgPV>Fb1KA5F62&ivgx3U>)i= z=r!O*CeomA+XU6-a-|3sM~bSX2W(FG>urL`1VzCl8Bo8DG&+=yOHKhnxA;}%`EKzF zFRc+$+?UHjY)EqFZ3zl|JjTXiZ6sD{SmMZ1zljRthtuciqhvSxBF|?Uq~I=z=r}O$r|HX}wG2?dY|0f%Pf~$g@)+b>OekrJY&w-po9YCpl9u7ld+Knp(p9jM@`Xv6|YpRU9UJLWTnKK|p0`Qy-_3d99i`IAh*T zC7t^&wf-`!fmn0bX47I7W>CYYscfVd7u!uDb-h28=G+R$9M(ny} z=;mvI%}+eG<&np0Hpo2}t}%=0_q)HJH5bLXdPHYh&z&)C9{u%;iF7W!nc3-L>G_NA zpZoOvk3QP7W5*txE%;8%CN0wVPCWXQ7T0jWkELvW`hyt_3UfZ;e4xRr1-^smCF47Q z!1&7o#$QH4nxMu5ui{-?Zu~1QH=51|zN6WnXagCOLyMcu2ix%a96$i)X`p)ElYrA~ zacs>oO`>fa2Cx0#Yle%Bt@)oELW5uZ1_+3X{!`0sF%m zpk>?>)5-BVt2)8!n3=sUywCT2UDX0 zMiPxCW6y}sWKtbrmrX^549cXryia^9zJexq537JA$>e3Va1Qgl>c(;zuFH$Q!JX14 zwW6=DS`{yuA#LdS+3tl_I~h(F!6(YfX08j1e)*K)SBu%PXey7l0k=~%{5EiI?$P5q zJongpaL8icqNp!pOz@7Eyo^K%FZaot;61G7yv2>pc>8iWQ9 zn6?JS`!xvtaj3XQp5_ofY=BTHYy$-5XVVtJc)#YEtZsl&3I7Wlp)G@rxFLQ?1___X zplBv;BNXNt*H$uUf(9iPWnxg^OGD=kZ7tjj02e2lAHKA4axd+j{Pkb>>u_!B`f>{I zz<2=Z!&*JF-L;J0Cm$|Jkn;eDD>j04Z)YPkCn8|uU(<*vxWNBtFd94zH+WS^9~e=9 z(BRY1ATVRO6Pp7yXFtXEp30ZO_70gTYnokWK4y;4Rhy;ZE@Ff;feX^`)Jy=*E3tk3ZwvNaOQ*RqJOZEo}!FcQf z<;J`#KVeDP&@5f7MIU1Bu77FrT7bG3@=ULks zp4pr~-ghQ_Z`Y~kyM{ttJCzoNm5tTyuVs@pbgKMe;j(cvX4e;!10B}1Tbe+tXA?M zEU9{D+_}^7jvb_S{`hh8=8YTow<%pXx(dVU1$(QzQPVTA`|sI}4Lug$Q<%OmH`KhN zhthfU+*xBr&zw7=edo^Y@eR(UWt?#iwT#_KVGc4&8HZ~0ar4MJzxR8!5d>{?Fhc4P zzxg~ThQrtz8{y|LBrPQe+=WNcIE-T$0p0<%0}$H8AxwhS(5R26j8P#)fsnx2^Gw zU#JEuW-jjN@4o()oO>A>NgJ%OS{V&(9XaTzug5REj)31C$a2y~92(uLj9!khnHal9 z`AXZ5S>pTAvrp@YC!aIEk(C`v6YhBnyIXHUm?9ok$j|iFYz#+s4?G4qn0?IB7 z$dMLV@mGxNdO1gA9JEYg$-FsXaVVUAQUcR)b>lB7_J=XoB z@+myPEy6Z~C&BMP>qW=`vxg^+zsW11G>*oPA)_r?vg0_JanpcX$(=iJK<|u#kae_W z=B;gq^zW6P-_AD1GF`ry*(S47=XuNVqvK8UmP5=4-(beWT2~-e%VAYq2HEYg$oIJ# z$&r_|HZaA^hTpzvaaY>0%%?hxBIEMMC9>Z}%0IU|*27cwN;`k|XF9N-Y*+P|!>-N* zcXJ(<1(WeFv*7EKip)oyQ}V~k|Lt{ops(V8`w9|CLVeQ`wGO?g+>l}TG#4NyDG6Q2 z&}byVtjCv>>SU)HLr$F8vnv!!ug`80MsHg8Bnn;9o)63mRen{Nm6Kl*%qk>x@!yo{ zCm(Qkd$zM&*tW2(RtCj?YT67UqDDyg7T6w|R?zh8n^?rm8TYuwa@*k#h3g0$>OE*C zvsI&jsLlfZj_61H9f+T6K0G!;IfN0}=)=BIgZhUtUqXB7&d0RI=MrdezP$m?m+%jW ze5L{vE@N#`ZG@QIbf#(!r4unWpUDy*gPGJ2gF@z%HHAXrF~RHK!Kzuuv}yw71@jE1 zZ{rr24@n}4B|$Ka4F6aUUEl$j!r#mWMps+>ory<{y;BvY=}59HppFP;4*IpSHe<{s z#G0_+B;kb!I;7vC^J|sETdrP)xddp4&B#+2G_hqII`^xpEOv(HT$|&wK@-zvXFWtQ zI~=57XWXX)Uy7EafsPZRWJfDzugsbj@-CMG(li=67qZ5M@mfJ-w8mGbsT5cvcw#0} z9yRYtdb6gEsX-8V4vqM{;7dxoJm1R)? zm3j8H9dR$Y%Dv|33;F($ki;<&~sf~n*6u7-Z^7+H`@Gagz zPkQvQXn#i9SwC#WY1U6ugU$MPVlG_b81|UgQ)PZr;H($s%EVl(*R}~=4N{CcNmTXd zL#Lt6h`?vT}|X z_9pNhg*e;_>}WV1nNe>86&dVYG^wajRV-GWUPhZbYy)k48~kW2KRwQE7?p;K&|hq$%b+qZ1OCQ8)^iWLgoOjF z!jGZ+2EE-6eK=RB4CiFK6Ge+9*KbYL>F{|2t=pLGiLLCdDk2#z5?WM5!~{FOSj`o# zj>Zd_?#}i1#&z;oAiq)w1QxJtO;lF~@v%paCkI23ZRs-eQHTuD=XL*m7K;d$fi|7k~cw{Y2iiIYu>#gnqM0qio>+D44-I6Cg7N9jKUIi{-PwRBTLIQrqUh^@sPLeJ`mt<^0(L z57X$=ZODmxe%(VZ)4=i5(%AZKul)nRr#=4bd`4T9Sl2J$32CS(OP4Gut^}9YuQQ^l zQhLCSM%H#Y(W@Ht=)~#g*NUMx#G3)fDS@dv{G)h4)B)ZaCjgd!j}!rA6cw}bRVi@g z>fh_4hsm9rc9PMg18efSV-uMiJ$&TJ-<2YIJ^DF)_WtV4bI6D@q?nAPkJD3UY0cf6 zs~;f!KdUDq@Ch3E|7Y;WDT-(L$8F|+L8oOtg+`m1d7MwFuvW>v*uF}tQkYgN7RI&O zVaStN%Adnq$ezE>ZPmQ^bMEWs&+pfs<9K0FH+Wz6ayh}^YjUEBzJl||hz7qn^LtF^ zl4LE1Fc=WH9MRT*LLn{V;&7B7ltj<6&~U##N}Yl6a5G<1rH>1?cRi7Hk;xpDK8^xV4Y z53c<3`Mh1l=7gL@p5&^OK-(@iN1T?9Ps}~}vF)oJCL4{H~C9C3Nt{6D;>SUeqYA`{Z|eip-q^y84e6VJH z%#GW(?|Eq5^7*VYv=KY8B7*Eh&8gic6GcXgVS|BK(VPTP9iCCDaUCo!Scp$b;{9H3 z64CEP6-9SlIG2{~`*DOzujJ(Zb03}Du86uvlB^kWr|8k|??`=UkvQer`M=RmfHjug zIue=iELzYpoSvC%m(sjmgd~%d+}3s>L-MPBL<{_0-Yu~Swwi=`Ig(<{=x!|LM-F2P zd{77Bb^BU>C^4{d*2{bDc>dmA(;mHf@Tkd8y!#CO@rTRw$1lWD^H;t7!ohXB_e*>G zKRRad<~C(ZZ)!cDYtN~-&irZ5_w={l-hGBBbmo0WDvB=beE2D5EDHPSdzc0%~s7Hj0$uT7_9A3R6(5AXo^6q_X?KA0A3DITf-tEE(I)AFmWkoA+KeTU0FcrN| zaowPJaJO{RJH`;sQdm`ieFB?S7yv^hUJ`%;IyG=W{P9EjKM!_Xuy4b%g*6MNu0C_T zKQOIIzbL#yXYHKXVB&PxMnMCb#dL*y7^LGu}D*)yEJgjd4adqW(>~ zq-ibE%myP21?1kAv4C-dE*ZNnP5Swh$XTvY1eu~Dl>jrq2_`U zIU$*QhqJwQyNaHourCvnyy%i<^Sc|sBn^1tJQ#P?CZLNuvFfZ2yA2)(&R;lZY1Ff|gl?Ytk6r~1 z(!cjDnd6euCKl{mBloJmI$<2|ZngJG$praw05Tm}7m(>!nM}X_GjLN5TQURr&PjUW zk)ZSk6M%#SZ)#c^!jdYcB)s*MM#x-lY88I@?ScdyG|(5Jh@FPlhxQ5$EEtYC);!8e zE_6sO=$7B1w0qgD^M)3a$*xwxq(HmQ6@yZ?Re24`L#bS{dvJLROSPlZ;?4Eu{E%^8 zLf=88?#Gd{&PBP-r^TjwVa;>79)kbz?pRg~bKlJF;<3=;|5^CN z)3xHy2Op{&-MNJ`<*s2}HjL@HuCS!DKjZZSE8opFhcC?DQD9!Rr1Ih0XWohP=Fw?H z+*D>+VV8cK_b;F;R>PEHH0fOE5o@w2UZ2xov7wzJh|HsaJ+O!^lUW*%Xu`gDq$rqC z6iY^fA2J96low>R%dNUdgEgP7mixXRn(bVY5EakWmrKVgx<3@{R8az%P=o8qx)s{d z$qL;kJ?L$h2(u?8Rh3Y13L$DYCh-jG#<{f*S7Qccj0y1Mb*(6#f1Dk{z#a#vjv-ba zeO>tO*}L`nz1RMEv7QEYY`W*jqnjT_n)*-lgAtwIqZj2g6bGi!bGy%;qx;9d^VtV5 z;Wci#8|z9J+J=2re0Hrzmn3BbWT!L1lz_bN1iMK)X||e1wH(JAXOi5VgB4`4(ka$g zG1f+jq;*`qbNvz=WpTIAU8CMSN!-5;*il@xc*cQR(YW;f%8c~M!@6$1y?lK^%e*XS zCO$DLl@tdOR)s!}X;6o8Ru*urc40q0rctLe3nn=!(d;uI6CDXsz?vMxmPUxNdYXo}K1 zA|PT!L_`EcqzH&8h!JB{7HJ})*pWq4*0msutYTTqy6U<}GKc?p&z(s@cfUU&Bs00W z_tf{i^?A;3!J#J)Wk2%^dm3M=+y2N?&-D7+TdeWnf8EK_x!mKvC-{F}dGGc8%yH;l z8X?c6uatFYiC1bHL6Rdf6(*B6`m#oi;g+~?veK}h7yDLW|^{% z4?R(MLvrS<4*Tl(hwIgf`=T4q+Kn|%4+t$559&+L6Z)lCr9hguc~Qa$EdvISuwL;15S6|m|+x*1#l#JS}6wZAYnFapO-7mlTL&Ke)-E;q6 zSAnktD?ngFYJ;%?D2~x9l4()X6B29^YCeE^u4ZPrfL)0r!#aw)mO2}wHx++a?gdw*}~f< z4y=6c4ZtIOVuhjaWO>n%^FF%`l~(ff7mo1L7x(an`%OGY&3*p#EjL9UaD4~){T=qy zP_+_kw>%Pt$*cJj(u^6wu$Ex*gL*jGjRw1u4Nd{S2?43^X>4rD`$AAknDivOy+DS7 zylj@=Xo5ocNf7RPI#u>)yO#CX%d3ub7}m30yKY_QuI{>#Z`{M)tyz=Tt2gr&mc% zNs^2{Cj%NMR47-#Db!I~(TIY?Q_GvoM}`3;Ia=buBx}%{G2CMIgKlNy;p0i!wLLl1 zzMbG1__cQLL4)Vw6z5lezUA9p{KsGTcl;d}&tadKSq8`}LebN&eqz@)lN5mMei&;m z7p+UPC^=p)ph44JijtR~<916a24?{9f&Hs9B`kG8PAotl0g)S@em%mcWxysUZdV;d zO}A@x2_;Bbu@nf`!@z#of4r=3ec$q1rj4Dled?HLx0Lm(?^S+$|EGP;y?|fMw=%YQ z>!pW}ezc~BmGNVDtorEagG_z+0sh;=jr_0s$=_opON}Z)bW0793oK`0E$Sr=@#yj# zagDImq^afl70uNm;*21Bkj@CQ7sJAyY^q1ltv#{_b!)E*PCQtHdi9Kc#%PCyJsI~sSKo3Sf+R5uXm5rJ(dFd*? zw2tpq4)8u~)rm8&`2rigx6NL9$DC*~@m}z;FYQ@#

      _T_kl)Xoan=RX{3lY#VQTZ z1Ngku&*6tipd31b;ttp}x0;xwAm*unSjz`j1Pm~^A>i<7who)KxyUBWP|4@u!4X2% zK|HI=n4|RmJO4RNJ{8TkFzatSj&1)tSebUPg{<=XJp-Q9N-q9uBmV(ZE1=lA3x(I$ zv9%-C4@0X(W(_iN43vq3*Oz-E|37>ZasDIRqaEf|#(lmqW@4t2P>DVOIDg5>Y6vQs z9mvcHnFFg660Cs$`W#TKKo+oi)Z`Y(0{<f)6=@xRUgI0)qVGQUUfHV?}RjnFc#ed`t{6Mq@C*0Edg>reca@lLoA3ZG^+stUC zH*!S{oq!(rv}iYYiJ?pa6SG0nJa#8eU9ZyxdLog>E7@I&!5;Gz7q-r*6K$fkPJV?J z>tD99`%ipaSF?)O(6axPt4ht`ebHaoueZmRyuQ}e^AuSnq#o?o`O*%3t^4BnBgt6n z7mQ+)0XMUFg{%=)p0*RCs)?WP^ex1K~j8r3I%nz(a^XMDqv~%n~}1nzbrw zH{nYV?H7G%HeR@!_o$QqSqHe+8e*!_QewW><~W5uuPS~GZ7Bc=p6-#%Ao^2{5*QiG z0c0JR!Lx%=n=Vm zr`+X8^ksS2YtchHqc6NE|0ur^?Iypbe?=Yg`DikIp(TT`)=J@3&!dq_(HWY9nKnay z8_aD2I202SEIB!$LDe~`1ro>dJL%fOE)$Az6GL+|6l9aH4GLN;heAy}u>HV`fII)@ z(qq@3o%F`>b7wz!@5H$sD`xJQS2dww`MFE#_4n^hFeUC?d*b|%ZS9LUuD<);HS6x0 zc1Q7`o36O>b+r6AcJVNbp%bx|1cxj;4Q4Y~99&3~vDqPCoE`)G6F{UYiJ3TYl~$ir zFIA2rshhSi$kZ~AKjJVc9S=R~Ha8wW1dysz=k}|wfkG|&*qH`V^(USucIBSfuB2=; zoWwd75$#H>%kyZFMQRrbfW2J>OS-CBvFK!@3Yf5#OhoZ{6kAeEo|s4V{5#RnkYwv# zjV`1a0-+eqTNm72x`fy??(W6i6BG~L@K_X|--FPAM=^ob(d1P0j0FzA1~AmL2F_$CbWL8Xx-&e8j`yQ(K@SC97zPBprimW zDJFL%lMsQr;Ek!*Ekp_OKxwT(fasF9etW|n61-cY-!w^H^uG?{@n&q616Gq8Mezvq z`!!^+YZ~()dMUy`i2l$VHyiK3KGCz4aDUwa>Ml{=tlRL$)RSlH7Oxrirzh*=(WSXZ zo|F4UPcFO}blz|4E7nN3D^}uFP#N_Iv`OL|krfk2sbVNpy0Or_W@875ej1}o#)ZEP zt&>OKU%h^ah=W9&Qy?3rJ=!$_-zav0)nT`zfJOy6U4|Kt5{&Ax7-CEUw=hcdtx9@8 zY~1iurW>q^1M209y80c>54-pUjU!2aI^BbKCNbLKtO8E5ifEc@07ki}05F3^0ZIrs z+OTKh+6M;n56>#-wuoA_f|SvMJo+0S0J`>zzW^1^)A;YkeNR4n{4-rSdN6wb37)ia z)jbFw3D#PJ1#Pil*9MW#oUVWs$_dT|ADx|v*u1DiKoWBRV%`eAS~`d5#HQ)R_ZG{C zMqKDaL|3b3Hk0b%Tsl_wPihD?>(~G6t9$OP)9QnDNDM$zPQLT$+SRL8EQ*q()Nyto z+|V8mtzfEUav(nH^%+qq0lX?4;AlpCP=2?_h*M8Y1Q2$@RBfLbRuA!iHSokb96(l< z*!VmvVAEfChyTRtj-RC#oZ{&$xO_3zT6df@D{#^XF8c&OJX8Uvq9&&Vp+E$KHqC}6 z*t{APd95|`b4y{yG6YU6P;il1K)XIrk}i2Euqd%p^*bu+zx$Y#@8Iis-RE1cuD=n6 zzRwgbl1~KSLN6J7f7Nb&=nTJyPcPr#&tPHZ+H{@)b6Glu`U|Qep_mG^ZsHg5+6_p1 zHkg%Ax+{g^oHoSmZE%SyAWA@!m`Dnk zWC=adO)eEzxI|vWmmFhTwm*OY^|A-*K0k1TEjlCL5Zxv(E;+gFwNn=d$v4PHZ@-V9 zffNL3LDcJS(X>9z7&l;W%_?w<2NmyGbQf2q2jU;LV10^sirAF^d^ZqLbO z{5SqRb}#w!2g7UOkO=1(oYO!F0Y8A#h|OzN)5B_tHwE7OloYGYhUlctX$9#Pkygak zLrkKy(2#U_2KS+^B^gM(q!V&Xi|@sq)ci%WRxLUA5l?03*ruh+xAIenV}wGBY7nSQ z+;Y28I&GHM?7e-~=2|%0r)s4N?-1&mDekW8vEmKWGSC7Qzzr6|DijBG0Pd(oe|2R{{G*(Gie>9J7+?7Rwr+d z%8w4edlO2%9_Fw9YF|5@FC#cR*|MMg15#vw(&=x!d@bn*y`&m@B z3e6cy+HC>D5(-1?M3-rWDFjkoI>|ZbQw9P#9 z68jO{gzQzdfAo9V8~r|dLN17YAihfIHQLe_xd4md+039Qrff$f(C!SI5)5!^W`r{k zwsdBMG7$U9$Ou>hSRjFb1wkfDON^6v?5o8Z!OWx!F0dFPHc?76frtrbd>D1=OITUs zmA6mqT6gTlPfpx^^S?j(e#wCs?qA1`ZIYMN??`F0eDFheA1pUzub8yy$pM>&-#F$a zs&JtdeS?@?u&$WsAOvrm&0upnfa0p!oi;y+(Ba9%hsXeW$u5xY{KahO= z=J}n{11WRw$&{BUhxk@L_7&SA1N)NwQ}2sbh?VI>wp5~GFlZiRGia`W-v|x_tKxPV zTz!S)#v^@X z3&+G|0!b%o2@~3uZuGL@^2FNO=oXAEFJ;FY4uYS+BdXvpkABfyS759~78(d=pgo$* zGQ#$NwV^>4M@~~zfuJo(9Y&dkvT0Wop_m3DsHHUnuRB<;u;Z^5Iyjyq@XicUe<1<# zp;%G8L@@4>eAAHo$}|H4C5E_+YZ)gIj{)`PI=;1*KdMY6QKuZb4^I*`HmLA1NTYFQ zRAIu0-XD>DH@u8#$VbWbm{Xjp>bGgo1T@kF>{4>ThSCtH&6NVW7f3%tKx^qw*Y*A; zy{$RB%qLMXHYyc6N>l*9cD&uBI^ilp{8{lcoi0U$1DYBxmO+!X0#o8lZR5gPxqt0L zB&iYl9-utmH~{NyE1uX6Pw^PN}V(^4y%Ah$8dZFL&<4KER6mv7&4JI<{3`-G*7BKd@I!nq+3GY8LjF zj4%ugFa%feyqNXXEHT9XiklGluAF0E?z-5kmYuI%qdebmQ16g9J^6AYq_l3apv>RS zNZo)tfh-o;;-uPtgni6@V|>`iY1HybP0wm!?$PsVAKrDbQh(xs#(~ib3?K@P1C;}$ zAz@8xspyvt(gQY&L+Y5Pywot%;4QB=K9sA%U zFRxWIYik?MATh&q>pFSqLt7qyk|Y7dcRhdb`O#Gv8_`gMtR-_cjX*6)_$qZiFW&wndL0#&(`Q1q(w+Ih~a8Z!0 zLXp2*WZ8%i1RMUu{BEm8J@cXk>P5cm$?cOKEG*kO6nH-te%4pME|_)b`PKE0c3PE` z$xkE1_A?jUfXHhvLHo#Jxhm4`cNPl;Rc#Qyf_}sbY9x{Vqd;~MiXDv<4HdPr)zKF} z0Z6Zwb4hf<^rn2ER%C{T5`O8k4=y_Rx!PJ5_BEdT6h#d`Jg|M+h6lE9e*ifFKl8sZ z>woB=;H6(QpxFH)ioZX8|D%sEpU8AqK=23+Q>=A2R<;B9`Laa~`0ZFyvI&WXcz1zB_dp22kVvt%Uu6{e-FsRK&6qqTloX zC3G@pTqmRRwhk*RkybvUHwyUN6*Vc@3DM(phMZXWl-^~46a>LA&cOd`<;M{Rj9B)- z6=-4_mKR=tpTOHNY+HHJni)ImTylQ&19ROIs~)`B2NlQ#OS4US;_8yeo?LxoAK01E zcKGK+^_YQb-0^w`dH^O_oOdvNP*?IDglS03>CgBLVxgB~gW{SPLoEL0N}Q20>4=8j zSdV_>UZd{Y?30p{jR8bAd=51&B!TN0aRU$}0#4Rxb5o6>5xRU_-uxFqEY45LqC<)Z zMc$e35XLXq!e>HO>R=@#a4=m$Nw2+6X9FYV13^s z1*JsDYgF1wA0OwH{CaMwuV)u)e`LNpHUk5w>t-}kXoUTVM?U(DF0?T-K1gls9gOlf zVx~xn+r+9v6dk*5K3o>?o%`ZZY`xwYmICQwWGUcW#P4++OFH@aa+9biCmyk8ieyAmCYY-dvqBWk}y53ys1Ck;UcWA2k55k+$TyGBa z*STWsnsUxBzr%dCTfakQJmT2;4v0x2XIb2%3DHi!2i2Q*(RUUjG&#zhFKnte7k3f6 zZ=`;WbP{)=Yl_7*$10#&R=$JLVZGiKOS~_k5eR1H$hl=_vwJUUHzUCT z}6`dyjT3CQ%Z{1R3@}w$)wnD2c1ccQk+;WNK4VAaI8mcqqFm^i_*H< zqaQZ+?L%go3oey zdfS@^ATWymdh2-BaSV+_BL7X^EB$~7I{b|dou%{xOfp97_6p;{OD{iXv+3W><_e?a z2E7IY$B9B!*hs=&uEhB{Y$5qQa=S-h+^W3S^Zj!%Sc>8UO z7A#px^S9~J=h`#UPlyyEfCDYe4bL@%q2yMnTGB{NY38noD422DB*c&c`b(oC9VC}# z=>V{R#DY+Ec0o6PZ%fc+8c>l^Qj*s*QL$%~m$&O{$7e9X5KJ(7z>^7fb1`PI$s>y{ zKAW`9^-pYC3az(@#DA_)}kg`Oh=#*mq}|U;pqoyrwZ-rqnXMY7w=r z`fGVN_P7E5Vv-i%&yz!f9VsbdQDp^D1d|b7d7W`mFbLOiX(`{3$4AZfCyfJ6z)9+?ng z9b-b|HH{0`D9aHbq`zm<43rMM3Z5yCloN4F9WF7gRYj^Kf+|dnk-{@hsatUl5%M!KV!RR*+q9usDs1@Lc%9dtFB6hRe zospJgb^2vzQc60wJ9AwrsVSA!_EcMXI33KfVP`lL?in5uR>^`%4F{#5Z*X<+_dK%H z$R-;`w`5{%jR_MpH_n&tM@z*g;vM)Q#w<%HNXX8YcOToN%nn^TVEK|mOKO*d1<7oOpEL3ENn)8*q%FuMB}T1)6h;Nq^^vlSl$6w*AXv=p zX4zv7T2l=LZId&usCIcPtCeJ{%i(ZYlNAj#^lh%FPBWw?K=-dkcu%*3Y4C&8FMJ4A zk;@=KbgpyS?J=%%0*v(`?OP`t38y!wF=>?!$B&RFvB!#!>|bEYF-OEzfaaU_09 zJi5K(ThG@Y7&gzL)}-BYFFQH3-%F>K@Bvp2Qy%>MA5rj;oQnRicE?(Q_|P*JEzObU zM0%zMQQwsWz6fJdmRq$X1r51*R%GW?R+|#gtJKPBIn9|CN-Io*0h*AOkmk+EkOo(0 z7`+aV0mu5;w8sAH^u?tdOQ#}aqn`lpnzEK91@o3$T895AgA!{)4h*uzxR3m?jU}C@*=h1mEqI*6)PYjM8@W; z*h|F9Kv)acMT*SOaG2dLr35luu7bRPWW@M~yxED?Jg<^y@+Nu{^Gtd9gR1j9NSR_r z5l)T;lxZ6wazy{=&}^MaN3#+}s#Qxo&6aQ@El*3OH0)R3d?7m6W9Wp(CrnUk`9)s(&9RrCWF_0jTrrYuXd8Yc zn9aXyc;kMiY#cZJu8VifzZ1R4#Rdl62Q)XElo#=uHH)gL4rDVk4TicTH2aDkak& zY;0^?O?Lled9~nURwRGKj0`=siCTsRg6|q2lw02M2F=lhGZktCe-L-4!zFTj5e%X%3n%*-y70;*`Jbs+Kz4wf=l9>YrkDnl) zT>AXfn^JyaD*``XJh%G= z@AAOUT8K?0S#|?kj#yy`vaFD_(mZFf;#7({T$L@6!mMfI{d zY*~~PD*xquHnh{?^6BgQ%pP&&sCK1ybegca&#VyxM-`RdQ95;Fznh2kyRN8gNy+#v z{pJs;x<-~F zp$B?rDI zN`LbPNy8?!P02+P`nRlVF>fb(m)Fc-L0-3neHi^_X$?ENg1J_(Q2CcD_>U|2`7+34 zoxS>F`1Md`0?Ca93-)UBbMTblT-W7x^EKf=z6NtU3$m!Y_9|qNSxS$9nGrecip6R+ z%7O$PI!=qZpn5o#X;x;jS1Qiz=+6^ z212eI!9ToTSg!Oww5X={htT^NflJL}TYroT4d0dT`VV$YtFQexa?y`gL*uZkQh^^_d#jlW)^ek=pk?!Wyt zl)g;DJ}i+Jr$YvGw5#^Vqfy>lvzE_fn^xBFpI~{XrCE0=zPI0=fMevut9M^v%Wc=r{VUc`SGi1H zq17QGR1opIloXT%Cn;(u%?nR^azdIN!x&4RiHk+?Kt1#hu|j#NdaN1JCD)5>Y3#%* z*6yyW9kepP^QunQ&73i@d~m31nEjJ4_4QCEuUWL}p-^yXM$XmMl_T7yeD>(%FKr|< z^D*u_Fz!JqlbA7+Jz@AVkvo~4m4;*@lq<=ALr(CAO$sq(>tg|yLrg}@ipA0d_jqxT zM0PA~QqLyK(c&${v66rK^q``SSFhe^F{RGAre=NZ>djBHzE|BiV)!+)rz)>9?UM5* z&*s{KuIq2Vf9Lx*j=6EjsF7W(Z=?NIj`<>Ai~*+xe7F?vFq&i~O%ihxYY19Fj&eWS z0WQ{Zc{qO*e|6#Jv5i0B+$=#K*P)MoWYV=mmUKqIp5rZ0QjA7Lb3s3EU#Pj#)2;c* z`Eq`Kq9vROStaT^3dfM~6nliOqto$({uyI^Bp3jJ|DnW}WG+ruFKj2I-%v6D8-Lq_ z>vunX?*oqvyk=1O!=+WjM^=rwDy5=-ecvnkUbU)HdvpHeI|mQiHfO~hH}|`_efu$e zC*B^N*lpmzZas&jSTd^EKf9F|4hVM|SKbY?m5H8gg@#L9XIQ&Q?sRzBG`EtOmFloq ztv;tCI2l(tC2@EO}bgS4QwM9BFlS4x_1L~BJ|Cw z$`KXwr_H|p)=A6m?$4)$X1#ih?mpdKRIE(1jh;Sj z+Vs&1Yu7e4B{9M4aoWl|mv#)NQ!*>5PqB~YpI|@3emKroWDFlGwfC*X!ToPr%BB$1T&JjWPh_UAl=c2&_Tmi^F;rYns+IB}(9n#eSym zC9$7N>3w{~9Kfxe5fmeCP6y76VT}QS3}|FnpP5Z50R-Sx#R^~6-eDbP{3gwMS#!KP zBRY_|0dnF%9NnQFn1PRmSD34@!>n2I%gnWaxi9XTg%1PAFCJOGc=7V(i*LV&&s~lW zcCLQL?DlQDx1TZgjW{P@*b(nSZ=q(cci{du7Iq zSNMDM1OuNn>*6ld*T~FyvFnT(tQOB6M2~4KWUhtG8J)~r1J`cdy7qyGwyb;o_1DJr zxw@z~PR_oiS5>ncGN2QEzNH|05Wi@dXd2EQ;5S`&BWcf7{HU=GXQLt!u2K6)BC`gw z5rGF`T0z^qG@m0Q&EZJPP-L9th5BS;PQ@G+bucR$ZFNz*PI4|tyM+c8c9*|t@8dTL zSLz$98iy<2w(T`(;0uorIW%C{qa%CvD5|LHnmnL??|~J4D*73Gjl&zSRi11pEbdh_ z?Di4Mw{&i^XK0tg(knXkojs^4|GsC%z#fr--~uB3K)#mZ+A-#y!Td>RA&6Wzp{2*7 zfQT$Y3xSNVz_WK2h&k<0H9Rp=o@@+SJ7%{Jg$m?$a<-yP)!{1t40V|cT+XcY#7@re)z^&}<8*z)7N}?Wu}AmsdY4IU z*6=q|4lEioj(L^J_PzKcI9^ATFS<2YnCUdn&mA)+bLm}OPRg@;ww0$GUV8sy`~?4S z$&yn{y6%eA;JLww5X#@Hko|cP9||5lKGf+*_}}BvEQ6X#LE!0&bql@mc*wRECmIfQ zY&)1_<+rZBa&(stc}bzXuETGe5US^yhvXA*0hUeg;9PF#yuI^AEkboPU0w_Tj~OZ#{peuc~e*BRc0r1k;@Z(qe_bcz*#rdRXcCD#7eE8M}o_w1BlRPTNpbN|~ z&V%;e0Y@>#?YDyc4y&(;%@DZ=hU2t8$+IM`fD4_jXu@fhq92G_UxD5VzKl@}Gz_@+ z*ysn!iDoWId6e`fz0c3W=kq>lWx&?%Ba2})AAmcWpHt){_#0p`V0`Bxm8}$-sugET z3%p^!;w0Pm{|j$eF0BFH@GTQ2+&(i7BhH^0cMI@_=gr4q+pV0yu4QSHI$1ucy$;)* zp5$<#tlS1qNJ64)MQ&o@YoAiooKzvKn>hS!c1P6z6LgT@hm)17B)|bzJtb0X%sn6?5N&$8X#soHp;S_9+Txp<;k!`X)25U3oHi_ZY zKluF+`aN2UYDvR*5#bG!eh@G?-R=}Po~#BnBhv?p8gmHc@gS)I$ZEX6h|*3F9BR3% z(HBu%M2zz3!5(sH2^@MzWf!PDf*w`Y4x=df4IqYXXX98O*6+FEF3I~HsY@qc-!Kmt ztH1vHC&4FAC-tS~K6{V}fD1W{|_ z!D#qxMqs%3ZHmQ`jy+;=Dae_r+~s$NhLkNtdL( z&d|b%0~%l4_Vlw4Kl1F;TXkyOdzt(+@#3y__PTLqaO<-<_db8>^yyQSW5|dZ!mtSA zESQEhWYk-IZmT;r#ljrPD2;ann{guEh^dgezMv~#{`ioJ0hVG|nmCW$T4I;}p?!{U z{RZHPT>z8pU+Vt+#zAxGh{_C5K|NACY|tKO`tH>$63iX<%{u-TN(8zhE${=m$3p8J zTY`sPax~Vj8+Of+_vGVP+l^PNA4flhVS|jDi*Wm0x)immAM=gko%s!S(L0Ta>c^KD z@VLPY*g!FP7B#5{`CP*qXpz|GvZmQ#`b%CbxDyJo&B^u>Y8P6J?q(wCl@f3>$P<&! z=hj-L*oIg38Q3E|ryrBF4_9T(+3hGUD9AUAx8UAg`J-IER6{!IK7%9yGJ?_vV$L|B zMp347MozSA*EV@wy5zO#`crwQPUU4~Wwciz8-a^L$SZk5^7Xm&5fgb+_IRezap>m5 z1QNp@{R(NaeuX=Jg~$;V(o@e7?Q?)+XEO^HWPr}Qt=G3meLfYkwJDc!8R`nx!X7B1 zm{vxhO>QBe5^|FRT5*XKO2l4w<>h$|iE7(yZ#GpwqPRJRe1uUeY*_$D6OTcR9o>{t ziH?g2rzB#J2xnnnW%0A9Fo5?NqCnf(ZEU-Kw{QG{F@FBi^!lq!L!X>>Ny5)xY`(33 zyD{C?J$~|ql@seG-uJ>gk86ebp$CSdKJsb4nvZK&QrwQ~e& z?itLE@v_0-J?Vgw&piDMH&FI^D$Z~m-gbuuT$yBO^d6MI2Y`U7(t5^12$t)4dFY72 zpa5^#OV!$)yh@4G0!&WHQF1N5=RV+z>wiseZ}JYhuO8%n?JTCJH`l&;dVfur9ZM?V zcOhMS;FuRnA7feUgT_nuw>`+717GDvZamLMj)ufSHk#T989-Phz!o(Da*R0x0Nr!K zX`KRuUBKO_k|_xeXA??|WXTCM5}@CRjRy+?!Qj@xcs=|BdjN@gA`lPFNk_A2g0Tp) zp7m@8AA3wL;{O4j#bkbzefz=7`wo4wU%d#@k1y9lp&I}0C+ft;f))4Qdn-v+YUfrB z*)7PpMeQlPfnKLI0AGdGj;ve7?f@>5iriZu3AiMO(~89Cc#R>%1By>ulc^%)XUZCV0Mk#I&bDs_`pgC~f(T_qUwLCyBG1mJZJQ1db}?HEeC zD^WE==mwnT5a6wG0vn7@gI_>;wN8|eZOpiOO&e0|Ni9QaN|eun)gj`_O?l3pplYZK zzU9AI^0_1)B457b+g~Z^d1u|~!}|i+ z>&W+p4~^nHgt6H%k_`WfO|c^}%*z0^02yKeC$34cDk2gqlF=wa)s#en-G%_A8_^4l zO(qNH<^b(n*|dJ^27Y#rJb9Zu=|FTe8$7IYT0%swiw>tRigYVIPu@TNhMlxV;l)s? zDuh}9EO5dA7r1vC4`EYtCe} z8k18{=0_f=oqwRYqqT8 zpC4iw{5mYUbIhnZtdO&m&YZ4rT3V`0W=2C+W*Q5JLSZONo>U0O3^_G5A*8rW31Vo)1{aNN zrJ=?K*UtdW>CIGtVJgO%>a&$hFK}*IsTO7cWF0ww%lP7D&#cJoJE1zcE@{+_z0#LI ze^23!8|L!~%8{sr0sVa$cN;2J?gSO=v02yWM0?9ea<7|(RP)y#xo@D6{d3u7UQWG$ zt5l;APlp$)GSUW%$(0dG2&GXsjM-TsmXV$gBx$$9lZI-IOc@cUbSL^D2E4f+=u8V$ zySXFEAM``f`=PGn&s;leV9`VM%QCuD4Yq8yUNNk5?(({&2^|K`yq5n-NssOY9Zba( z{;09T&?!*2hh94%CHkvuNs5f+&#yL)!s8|pvhx`g_~vEzG(m+x6#)|n3jSj~qVvDEl{ zISdu|Z>9N~d}Cg;uY5S~y6Kp&BL}<0#Pc_pvoh)_?GlvviJnf1bot*s1(Z4Uv_E=U zh&(mXUDTpbcUx|n|DXQ;!DP1XuAzuOGh)`j4q{4&53+2rTv1h)gDEY{#tB&~mjc== z9G}%~#-hq8{3bSZc>k2>1=*C`b2QEBEqRD^Q^o8JnAkUQ67}SjYN_Ep=?dwt$dy2> z=$Y8RzolncpKfhZ+U0qzgRdMI7*svbTi&sV96p8Z5$|l@CekMrX?laIQ&U~t+Oz?u zK^xTqDQ9uH0Q%#~6o?ckLlnVsswmK-<`N{H0KSvT5ga{(uwnUY#gqroLiPp84N(^N zq8NqQp!?5Us4j02yMX&VH@B3SlHwOr{JF_xscHtpkJna@yXBVt4-FXlXx}MQraX7> zQT@9KP#F`Mjb{NLj2^O8up!k;-6xCu@6`E8VI%Og)Mt-SVviD*E>5C24S{IeUeA752ig_Ue%X z8#NO?G)GBsNio2NGfPS`bCmQRJvzhP)5DdX;cYvh+8aOpg;3D*1BG}fz$m-^f>A|S z7Sf{Cup;$QkF(Gap>ChX$)P)c%F5_>Ihjpk4TyDA0+mvQ4S6hcX{7ammM@gFOUN&r z)b7?*r!M?-Zp@4hS5yzb_1T(Se^uFYcaFaQkt2Iq<<>{mF8~I>pfw}9-gf)wNsF#n zHR5iq(mC5Qq({oV(@^?9;O+Ih6gk+YZBc=y<*xB2Xxf6=H`gfYk#Qqe*YC*4T3+Uy zcSCZ=6*I4awFLhJ@lPP{XrAz7#PCG%dIQvtW^NcA=X*dTyEV=?ycPi#NBI^syS8Qz zh8U+f(m^Z{B_>>(-53%pMxDJ!NBPc?wIi2v!$ilO|Bt<~!2a9UpEY=uQ|@1Yj*^Pafo7wok`48$|&SB#)Bn|5b<%EoE6S@QNr-N>{F~Q}6p$qUCQM0gXHE!}ymF4{4>(L+fvSAgO>HT+ft(sZgW=LnPY|_34Ae1!@e{;?K zsG56eQ!npoIPvI-YIk**6o3`)WI<e~>W8qrsZuD0S*l;7b{C%YC9!M9a5+rR{BiVtH0RI3;G?tXYio1 z-ec!wcE`efl+O|icOqcwXX6#9poPCJpB3`=dqr2CoY=NOUfPhpf17)mZBq4CjK^H= zGcCe87D%#KyrWg@RGgzVmzEn`hIUxpd8h<#@B(UYm^DN z_1W&u2xy{K5)zM-0#9r>K{6FRfEgs(Ufd?WCcRHVHPT zxga^)uYiC{Nl!N05G2maE^rnAM9AZGrKcsE;j>59kkAhPS1*M{3N{*=)SO&6CV?+Z zXI&r|@DN6WIQQVTa%JmR01V*dXMZ+FI+u4hA3y)uyQUuHog?PA|K2#XW`(U=>7<0a zZ{v;j?j@z&Y%6Ny>~Hwl>`v_pI%lz*^WU)atj=u;%5(Uq=l=A5s7=P8(EHD_>wC0G zPj4H7msLnv<6=`+Y`T6xlf5R=zE4@Gpn&D{zhY=bH(QzIn&EyKS>P_)ut-Ihj7XQr z!0Nm%YD%CZ0IAw{Y~ekk9w)f|n)K;kP85?J-g1Eh5ns^c`H>z$UNjKpk@%vN1@ai_ zhY>2i$@z&PLAOPndP^4*oO#IR@7Nxrg^|*BxU25FWnw{Lx0;)0cbUClS!FbD#8chN zo*Hxb{m0jJyRM-B!Y$-~LM&-=-QX*pn8_RY=Zt-^Wnuq!{i;pXYX+*r3~yFlm7-_I3T9jQ>v6kM@qcLl)SvQ8IgYWLA^T}EklMD zcLD5nr@_4~kq|uCs6Yq)yR2qgTuFYF>YE7#UThE{FND7Ziq+pf{R^VXf}c|OXyKki zl|NRI?t_c@0Z;&3OhYTs(lSKVzZhjDHVZ8#0er75230LsKX&w*a~Dq@>aifNyn4XR zGg#50**#|UVeMwl9Z+4Kx1h%h@BHT~SJj$ZCKODonLE47ocT*D`Hw#=NXtsYNcwv9 zf;4iVQYSAxab?M0|H{V>ZIi=x{PnLTD-+WpaQ=al@)N`3w`8@+n&=LVpOsyZeP(-) zioDJ~w>vSJu;OJx@-i+)^_yLaATzvUhph5WJ%T~z>6CBFGWP22XqVe=V0BtqSvXfS zo9z*^v%(I;JL1b#yOxAY239A9Tdc{XzZDmsD4<9@R%A;DMNAj)!#|uE`znolIj#@F>u2lh?)=8v zb;sxXM|#~^Qa5q!;lDg?RFmr`;iEp87c}`mSMnP>bSNrhkFuL+nKk)9vCMLEbSLN_ z$S!h%Vyy6CNY!>?tgzA|Sy{}Hlx*`D5xe#~LIK6?X*ooh$sVVhj@FWr=22K!?(Ed+j}n;k8V&=j~$M`|y`*=hpIVyXK-D z$JMn;y|x$aK(s|NSX>4d{H}% zu*%p#pX-F8jVMv&i3g%8lr5$58A=b{RSr|x;(y4d4nRj*E!tl zWc*&pPPWLnmrZVUuU4{DJ{gya+QIFSL3y2g9#>-W;C^uY=OgRnsJ0f?0o4KkX&V)g z*TEXPBQo&1yuKqddf1YTzG0na4DK+=HFK6^>{3zDYe>JoJq8cI@y5|pCtf#lMq1mp z*(HS;c}gc&&^To9)EOn6f=EA^F?H||qseUdDKt6A^%f~BqR0&67{p-eO4jdwh%X%2 ziqNeyg(@P*BIt#9#p1sbuV8QJ7ykeMj0`r>zkwF&gHp|I6UwW!t0%YXYL{k8=6eFJ~`+mTk+swdmF%`bdq!kFKFq93ZK zOdQPu?Fw>>MoyVHCjMPH9seo*ohPMqQ6W|cVLMyFi=rcJ9+I6bpd znSyvF)#x*v;8>Em8iEs0{DTY{B5G)i@R5?;uE3*ShIfOwimp)0tnU6<<4WCKt=@s5 zaUDH!renZcz0NFYnC$5N(vUMtlv=sZkv{$YbW7tu~$>FhOz^ld* zQvATUM83=4icgFwhJ@GsqFAe!fzsj~1jGnRTVQFIz{4BE(`HZ|OGu}r5i}gzZUF&6 z$AbsgyuS5OuetHqA-T_?U6zL|V`fgd(Y(#J4@T*G^WV8zedFTaqu;U`CHvX`G?W?& z*6f^KIcCfw>(Lh=fM`Tq?Z)g5iL|j8&8X5bI2m)f{Hfr{4W}y(m)j{wwOoF4MvBwV z{D~fdGmP~@py6MhFe-Y0a5@1S#+#3L391BJG@lK&0O@e|lKPs5Owngfu) zN#iY)Fa>k02Bw9r_f>O_!sfgKW?EOi_}x2;Z<%Gj2zB# z`Nu4B_QTK4@Gv16AsBhrBU)%l^!%2GcGjuemi)WnGd&ndtC^xu7)227NEMNM7h*uD zssLzlz~J}#td`WYfH%cugTvGesCt6}*eEq>+gw^ZBWW91K5y_`&? zyB|h>vaBAqyJ8Q@cWJE(3`6KF`LN=OQmug6H063c{(vt#n30ufva~74&PYsmf|3W& zYN`)bwbKPiwPXotG;wtXfgrLhe%EN4cMa=Pr|K%v3HzWZi+(6LG=!!T?}hx#v_EyT z*=m<3nj%lm_~iQobH^_oFk{z7c}rbk{=%tCCT#Q#8MZ!ycQHwUi6->Z@L<4vi3d8Im z_lU3EgjPJY`gYT+*hxJ^WLh;vr?FjpMBSDxAhYK3#^?fh#YUN`*n#(;5H^!|9?&eU zfB?Y0cQ`e(#XO)|wpffd8^CRCz|{U<&l5%SvB!m#-k^aR$!lx3ZId5R4)a}$c``e{ zn0-M#JjNyUGqZskcsgI0`rgi zQ#KVb{%`VV5*Ct0xrk8_Z8RnN91f>b_9UvQDFGmofy~F{!fpkpp4+v@ct{=aH&7xGx_O{go|h0{EpK;{f;u zJ@yD*C|Oo4@a}sYHje@VUX#TjxjY1ejzU8Ui?)2E=&abLdPm_&@KXb5VX+@UCsb3z z9y%V~E8p-IA6z5vnkM?2y^@VN#omZ6YkVDT(Aj&$`@QN;9drei&p`o z5SqZ;AlajlAgJuin-CjvmMaE3)&`UlFqD&@SWsKLU@3F#=iS)1uka6+ussWPXyb)! z&1T9*7Xd{JZTs-4qnP3GN~>Xb6XuYi@|}1`}{_)1fl`eYH}!Uob`6M$4tsa zEIA^mry8_e%5lwtpf2=5p+O>@;>;O&gBX=}qW{`lw?$L;?0JySluwHhdHwF_Q5B&i zU?-wI$y3m5~DAN&rRIqM3|F&4H&A=c5yT7sTHPm)hxc_$@fIEPhi9DWT((HZ4!RB{&f1 z;rJAa8~zti)f;O!Hi1>^`Z(pi;PEsL@X*Z?bKNr%CbV&YpQEl2M7n_4LReiW6Yy#& zCPfdA#I*0$XQmJtlpaA}Gyq}4EWW!wDq#R5)~Kak{Jg~g2>GDtc&CUxGGH~zCcp|J zg$tndPDXID@%|%q-7heiV>6G%7Ds4p)p@<^q*v&TppMHL-`)(qO77>YHs28gHFZNIbep zt%+$hB6a|7p=jSa)z!BK6ctxNGvHen-MyII`sLSc76Yw@KoXUn>^FE=*PmwbkwMW3?EQl4OTRO-&18sDeQcfG&v_)!}KS+{EBV{$Qqb{YHQ} zkqFbr46YKm24yX{bli;TKi1awsn``gTdT~vqW7=N@Sf0b*!HvU`gy)`uo0lS9z0)D~7ri^;{*A{GU!nHUNwnjn1MVF6Z?uIKiO5EVi-L>m#SW9+0I zy!YnbBP+Xi9<^uv#QXW%$oe11x-1@DST&NDT>7?C`{ASCcy`~>-IKFONqu&&3 z6S)wmc|+%k?N`rh*1Jib1ieT}q*dr+B>&L2XWy%rS6sm+uDkR4MH~Kp>xIupU76i! z$kw%WJF`m-D|bD({^4n3JND^al*@h^yS=pSwwd47L~pz@ZzbOefY}a#d%f>{_0Icd zW&OFQkG}Y)n)f0}juj5dAFqM#b&_;1L|lcAC1(rWYc6!Jm85$i=S0`NmI~d=#g`J3 z6=KoFkz|7vx|i9?0Bk{whgOd{1L_bPSWU;S2+j%F(j0Xb#e}*rV`q7hI*1qT*io;g zZrh0iGJaPz zo+AB|dW{d%V<8Vj>Lz+6!IG8nOuLjHL8gF>0gKRS0@fhG0Ly;hgn}zVoQiQ(7!M+y zL8NwuYsz^r63vyLX>Fp$!vdb&0M$iPpGS{3&q>F3nfNHnq zm#MSOnh9o0k9mlv6IU)C+oI!PKXv^d%*U&myI;jTOZWyhZ3*Q@#D?G?B{)bXte+3W zDP0yx7kazRV78b*XopxgkZ_>8oBo&X9zU2#gC&)ugsCMvU~eqov-h$udDp{i8k@O~ zch%MRrO_I>Q9cxX5vA{g@$_6U1(ac)of5c?EoL)vP3#gQIz1=OB+0IVo*8iBHoql9 zWjLRZrAj9R@}lAq7tFi<*qC{SW*p>8Uu0pH{sNd03pUjDL-F6+hvgzUNQiIIZ}?c! z=84UK`j7o%*d_WOiDbYbYsVNM%h?Fq#Q~Zy6Yyb73IYd6EQ+yP>MQ)Rb>3va;9y1p zF8+sx!s~W4?B&DNLY^)E7^7ShTMKlTVqa;SfGvz}MZnjBtN|0A1mwmCFn^TK+B1j3$fij~L*#Z*Ro`r%(RnL~v-wR?Ppa znEwE^gZa${2Ufqs56FK70XA%UW5DSE2DZa*i)&`B+o5m4CRPVsEz_e=?ijNJgiUL1 z2PkN?0Gjz7>L7iH{VsrdgB(q^`u(PW-R*X{0y0|S!yyYsWDbLO0>XJ*b3 zbG)6Pi=S}u;dD`NEjD1fVb?vZuX;mLaIy`33!D;5vWy=mdmmWc7VVUJOqrJ!n{onH z3A)@oeQZg>Y8raIG;r(H&Uvf%?OQg#(|b2uA(-bL)uQqGz_(2ChB3qc*Dnuj75*^z z?3a6-ge{zGAM7M|N6B@^rhH6Um^-Xby1Vn43p`+=z2rqkSsvubp|j_Q6;uzl&>6ZJ zPaUA-W#KLGwxe4QV3f9Z<_m{vDaZz(?G>XWP z>|h#&JA@GX|8K$<;wE-EqLG9UF#G~$X~)IVotWa@dbIU{=V|w`q_Jjr5;40JjM%^3 zT!--nJjQ89Vw`2bY7TZb!P-=qo10xwxL)rYMC7Pww;&w`RblY1$pV}LJaHVw!wH+! zoowC5af5WC$+q%eaG!G^6$=wM{+yB_e6zVJo}u}94L<6Ua4<9!HjVWW!vi*X&I{E9 z(s9^J5ly46|J#LXeyM$}yY+(=P|iKzvEe@HvGd>RrRLMpnlb6g!|G$;mu-}JwYjGN zM+CnKj@cO*1Z&yF^O@27({I%NIv-1=;1u`=@^s97Blzhg2Rl^sFfXq_)OJl|ls8N) zV~?tn&I#6T-JlZ2IyO|rs25^N$Xi}vE_7hdP~yd+cO&wkyWv!Rmt{i(Cx^*U(}K2rO)s?z&+-?`m|4PWm`gKo$l6eK4Mtv9}Q z+5F2#jVF&A25*T7k*@CDb^n80LTIzOghDZ=ot2#gBgGLuJ}y#7Xr#9{j_UKX)S;mU z4Q5Sv4+BVntcFw3^eO0B~@TVY%z{rS3CW+=ZNioB%V zIDq*+r1DpCGyP{@u_7<0Ksuc!&F}bgsg2N>w`vcoB7?**kL3~z!MMLPJ3&ay5E22I zg+ORUubE1g1c;i};Hkpo3=)-jo_;0ESYksge3{}<_rTnhQIw_mvM=i=M&;J$oC zeh$IZ?ed=pFh@4us~Igsh!NjZ6i1x*!H6$bjBlZX(LSrIfNl|*Eu8O8r+Vc~xx3|5 zB$UvaOTw$yIVAmRr7z?;^5xBXvnw=-Kos{3>=Z1H1G8c#GKRlyU%+?i)MC^L(rlLO1XC3 z$3gFam0eKkg7qzz0C^C+gki%tR&d~?#FhIP_)AJdEDBS1oRH)5Hyb-ZIy!alUV8N@ z9EKn_nqOai*RAnQqt@K>%=43HVkr1K)0%^1^T+0&4&7KaY?EKa#aAz!^+k(IFh1A> zPWoV67A$c~b`**eUq5dQhfvtuFx9+yP&kl-s5{}xx@L%u-RuRJFUS6gCt$@V?H}9$ zbAIVj3wkhC^0c9cn~C$l;1K5)^`oFD*AVW8NwSO+{*oS$BY1=s3Le%j@#WQh=6?<< zzi0lCLEeMrtqo<%d-%LWI%2-ZT=SyCQZ2nmf96#1@&zx%(B5-zyNqH&AY(DkZfYn%Bc z^L~$@ZAtVOW>k){FyU_v<);ee#|s*ZNbZRPrb9ho;1!f`CmdFXDNe!_$AI-1K7PV6 z0^6aS7#3JoWcmq$s)AiuHLIm=$^71(IDt~L|E$?z$kyQ}&(h}R*Tw1mP7Q*tq;7NbZXUOF(Gmb{?vBxso} zgKCOFwG>J%-i^`Xh$`KWT**4BGq2F3L<>0T(;7lSaSv#e^F;!g+nH{G{To zS$cHUzkmOpON{UB;9fmp(>dFKbsLQ+FrFX}r#Ri=)z1}1uDD*_+l1f5{o)yO1K2;{bVjLk>II$D$wH@M-GI|M zTA|ZP&>5ig;;`a^)6=CG-MM2G2PN3YMeprxZ_ixdV#pPX;jUOxbi%TdlZO`5GOg{X zd!Qaxbm80{uF`uO+|oiAk&@jrLNKqp=jumoZ{7)O0xjl!=F`0gzcIUT@eQJJoA0#J?44rKZV#l>T-QNb^gWt>kU4NuEQu=3ymbV^nrP+pS>?s`tTG# zD9{1JWd|((aVL#hZvuCOP%ib4Hv0(_rG|2KFn4*Drg&%Lrb5#_pl}R<#ns<`f(^~y z_s;#me$+g^uj;{D%l7P8y~C;Zc(**${1#66|7t!(ovt*#{!B>p=BUsc0T1rHe=pai z3I6FIXLf~40CL`}%DLZXj>rL*Gy90kfXx0FxhrpitxKTTN76cB4~{y*=NgNfCc!8aaIFdZ z2?m={684dDr45`l`_M3Gi@r4XJy7xC>{nj9a%nyNx_7*^MjB~<;n*9WPuLZoeDB6P zaaQodd)I+f5`bO8@KI_*mn?%WdzCJTA19iiuJwW2S9tGI_eA_TUEDAb3Iku%;5OL! zDLsX(gIhmcYW~+ZUv@q9>fcYi^V&jsqIa#7Nzdw9U%Tr=^VfwtraixV?}1io-msX< zU)UGXJ^`r+`yzB4!Fen`PWG_WK#Z{EDoqtqyvg}Z$0dnzw3}eJ1%i;7fB?BZugWak zDl-KJm%V^NW)D|n#=VWgrwyRM>X7Dn4uvEuk|*Z{6p6)Iq42h#O0+kV70F|zlD9YU z1EFxs5L zQ=*i9rRF%%N~`eAC`##P3KV!u;AOoZN&ukzDmecfhXT$gD4h4Va9((s;+$9fjnQ$B z2+sRiI4>XwIs^o8eyB=Etd$N01~{+4AhVxR=!miAMuE}>*(h{OA>AsJ!2_WP8#CC* ziZU<-m6_)z+6qNz#RM(K6rP|BP+EX!D-^DwiW5BHZ4EdJvmzWylmZ1c1fu{bz6Z$llwaP{h_Gbx}g}+}NR2 z_(!O!k5s5JxL#F^ZT=jpiQR4XglbSOTz}(jKHNvy;w!ZdBF86`9Mb@iV_E7iu_*VN zg(AnFs9Zr>QJP|{oJ%lw@I0e+y$|nn(lWAHlvQ_g2j5qQc<>FTikElr5^aanr@)~I zGE_+|DZ6bV5ub{-C4$Tl@ypq9gcKG}iM$Qz-O%@hx-vJacZOFed#xizR}R9yjLsPj zV+nI|b=UOow}5%de!UInuc9^wo4MxRNJEy=6mut6*Ec<7o=b$pR-Pk@VN69xH53ydVD@RV+8AAqK|NZf(JeeWDgH4qUea1r^F8hOK{VR zJ_2hQTs4U@!B;_f(XdfYriO)}Uv>*Igi&?zFJ&d{{1{FiFjr%r*^iRL(GOpG^7KWz z_pLYB+~3E(P2azLL(eL;Rmu>42S-6hpar9%PSIZ(VK*c!NbJd z=8MyldhXQBKn!Ecytq;-SyAtcO|3S|vlwsk2^zLuLh&F7s%Huw5d*A_QpVaE5-HzrR% zz+POp&HQ8MTuw7uN$nc!oe9Mr_DR{%u^vtaL!f_zq}4`vNC}BS#98kT2bCC&4aRmq zj9c_Z)HZ$pDK(X;?vQT3F3nsZ6x+E?3>g3^q#x7>@k0|u5;C0IJWZKt0!j6n>Pb0QY;nG zgmEQqa|7t}*Vpe}`}7;e&rX@gzw;)_>3HOAInERf=KJon9#MY5p`qS>9@H5wdwgK? zoQ^GH24)D?hr_HzIJOFoErTtw{(&1;6N>Cvhy7yCJkVx=&@XeVsOsQW2@ zyA<>JZA%xfp=Zp|Cto@L>b6bD$wc(3CouL5=Q(i?atMRHVHCh~(d-Lc3g`>Y zLkdQ$XGHn=2N^>Q25*0(tk;Hx`)jb?3Co)rY~_b~-RK??>Vcu0hbje|8hf+x12Ixk zhfL5ia}URg5mwObrF0?E%1=JJc>c_ly=xkeH%_fIuljfIa}OLjesIr0x^wk9VfJB2 zZtK|bFF&;YDL*^E$G4cxmeucx&^+m(e+}X^>)ic<1_cCo1-VPkT0=1Wp~&cIWEpOd zFz^rZ8WbwDFY1v0ztPOiT?orqp&(e$%UKNbyO{eM+1uvR+d630>dt$&4gdS;7v4U7 z$K3n(ZKZLqbRV2Nv2e}U9c!M|`Dt98)}H zMNL6=^2ns@*vzpR?3@6{KMzuFrKRr19HbhU*~FT8R3Bu%lZ`@k_+6cam?e6hQEu!ea!I8#(eXDo5} z`BTg_UDeH8j<8-V`MUae>0us=H_aC94(!VPLFtE1MNxANon^&rSI*xN3EG1ozM*gHkgm}0o(B&O@K2HTP} zD!Fg(OTJw2)s)=n@7#h;`9pa8 zrcaocvTvusF2^CdiB^0Uh4QPz=W35k5dV+UW_^U>2MB4 zEO8Op!2vGj9rXO@H9vW+s*f$B1Z6KGuOsMbZFWbAtx&x231Nrfl+0}Y+Wam|GK9SK z*wFp^&V2IviH9FEA7CT%dcTEHQ#}qw$4p!~_QN;NUfO-XS`*|SQQEj(J5U3$I>Sta z>;-#)daAd>E;+P+6lxBNL5pSaUyNLA7{|TJDD~8)Ht6MH=7i4H&-Y(=?&KN1pXWxy z+Qrb0oi#_lc;=1c(r%7D8XQ@Jdg#e*{4>1{CnWXmz)sygu=<}9O zY?hQcH$?kjgBSB?X}NjdocZtWd+x;-Pnq{irSK+w3UyAVIlRuDewmG258N%lU5YXj z!S~#G8JtL%Q4`>1BnCK{!$3(KMRE<40)1hJ2oBE2mPxk#u^77D)Dd{3hNW;PgAtao z8_v38r4_9N7FRK*zfWv`(c8}NFvfb*;cIOTpMla9VpLDe9h%!9 z!wC9MjQud$mwp%VdW_%O_-LQLE#jAR8{+t$n0F7bd|WSoA>zxiH_F!XalO1yNiQ^i zS1{iTgV1!WPQHOz2{e1w`Mjd}>p_ZqHN`$)L<(gx$8ic6=PfX#PWcltvyqsWbuI@e za*lSX1q#w&)`FRh#QJBJH^W&VfA9x5ap2Dx8#oL83Xa`aD?JP4i+_N_>G9G2YNcnP z%x6u1LXRDvPYd2ii>&$TpRoz<==4@Ni{wozoILA{O~^!8Z3P?>$%ITWgD)a6g4dEV zDx8ddP6>LfdT8km`Kn4! zzg|#*W6?zGkj6sK@P0VF7U;$(^pHq8p2T6df==N)FT4GEZC)=lpD0wJmW_w0Wu6Xv z=jp8a?NdmH6F^7@%`+^D*UQjbfP-!3-LkvbISGBUh(99Y6-_m-SBj>(PUe~;=*jd# zK>SOTKzA*#S6oxgYaYJgID;)xR$wGpV9?2ZA1g4FYGr|;`9OuCrIXJI2w^r5PmAeMU|5|CJswFTl5=ny4BKtaiR1yFd&Qi^!L9=9LPfX7QiWQzc540%zf0!r^Vc0%wtCp$aE`K)$L($x`4fk`*nT#QIw@ zArn!u0Ed?sE)!9*cnw5bo@C`UN>-I9S%Ah<%4d)gCCf#WtYjOGYiopkv0FJL_(f29 z4;EvEgpslzhr}v41Tr6|mQaS34px!-00?S zw8h+>zw(_;c|XeIMSaQyhk5-NE7I=;=d<`2ij*qxveI_L(!Rz|)M2FOrC$TTz;j710GN*C613TbUH=6B2A)tn)DhUXk#T4S z9wuD1%N%jOF~uLIw)j>9Xk{_qF>*^BurJ}GvEG@Vs7O5&esoZEl6hxE=A+@y;7-0c z__dK5i1NJPuePHz?#S4$-3*~dGTPw@c=BAo*w@)0CesIcy3i)EujM^N(VT;F= z?|W&+T(9N|p1U2$-McE*!P)LOndwFdN9&4z9)E_`l2aSJ;YN{dAM?BAm94WIRlL1l z-@0Jx!PFx8Voq&Ep^rd(uzappQ>B{Q#oAwyTkb*FSC6Bh0Z=7-ySRi%4qA5ycL%>9 z3-)B&T#IwTTr~ll{W<4uD+c&+NFK<8{Y$kI9kNDd#l-vjWgNF<1zlVpIBBK5AtKyQ zJ5dYD8o-gybb9P<3x_5Ee)a6_y}Tj=ftE(8 z1FB#Y4k_mB6ze!JW~W#j@fbk0IWBxu-pD~wgI#7g*A|73$r}+6HP~soV|BSLAGK*g z;Xz5sv**F?P*PGun|W_PKS3|%M&?(v$53KDNm#b44w?#gIPS>Sxj8IzvE!xYgXFj1 zkFmd$s-?ukAoZI<=`Ej_bZ}Z{#R&5?*E;*+*;Ui*8XdQ4er%}qYMlkMRPyMooa_bb z{}P{^P5%;~S~{cw_|BJiPD9~H8;I|NS*<7^=1*?M2TOVI8QvZdnO%p(gUl^)V`nLFq_vID2Q;J?%Xi;PYjm%KNP=6HR92Z_U=4a8#S4s9_W9+*i$DMN-oJk9mG9foy=zxb_YRf- zUE-@W4H`OEnr1$2{_fo8U%v7BwJ$ht;y^-%T#gfn5fDGcp()}B4@58^VXvpyJf_;A zv8*MEqpGPHTAeNuF2XYYZkIP=+jP2g9ZULVeO}_+R%!&yXw28%MUMPPY_=~yDDQ*) z{r&-ZhoUllgfFZ*6_;_)R;8cNlL9h4i^5HiID~0gIe--+pAhNY8nz_8ff2Z!Ut zLkBx2-l5LgsA$;-%RNOn`05ZI9#m8o9^v3tROS$F<*}+3y-5SdjoR>43~a9vd!@O} zk(&*B^TiJ7>-#sF4wyeQ|I~hL#;xyG(8cERjd%a!)y;QQ9GEk8`hQlueNED>KOAJ| z{lwkpuNY%@B_v^e&!K1gowqGYEu1@Q5*EVY!Nj~@t`kymC%M@nFm%WmoVF54BYAk@ zII(6Nb{N1$36?-4on8tPa>9ue)YtxnaA@gy!_G^$IMA*5ZH3D^BX%mtbr<*dCbQ>{ zTr}V52&I&Mh)3znjNgQ;Z`QC@qX7=vloLmh~2Fka>f)O;!eDT80(?^mA1rRU| z>-=6`zQtu;daYAYnf4DX?AL!MV_%Z8|3?&6IIvZGxJv0P?lWqSiuTKj!gIBzEph0TQ4mzd-m+yxOv9CrIR<) zZ*J`K?D0pTNttrX=jT4AcK4d!r-?`QZhUa;vcks0b(}we=xAQnDqhvN4`*yA6gis7 zKF}P{9K$+z3YnfAADfsMKR6;H%7F}W)3|w)sCaE!YD`j?6qgVeonSOZ4RY|r$^EEk zKTpqSsOe#JPk#oE%P~mc1q=TM8ah!TdEaGOY2#Un6z*b$y-G!Ejr!+JC8G~tFY@r< z%Yqq3iH+WR`=+t8JGc0xmn@jHp?S&7(3&a3iriyvT|SZSJ7uTseD?V1CzfjME=UKf z=7cmw)r3Ta1jo)Op4J#y9UdPQ7&a&@dAj);!_JAx?0oNeme89(_pa@|0DidRL@G`a zV|)w~_h@?;(UD?Q9}HiU`!$7#zeR~Bd@jw%JfzRSdWsWP)zN}Dc==0%2L}a(c}X#` zA(4e;VIcv7T>V{r-F>l1-&gOBm1uX{g%zCP$(74zOF|1j9rf1GQ0Qf{p?HZyX=oR*g0f5G&yW>IPP%8 zG1zgM<4(tmPO(l4onCkP);Y|%$hpV)1?Nv)++EULCb+b^Y<79rs{o% z8vmZe5NV3f-+g}a_4nQCd)jZ5-xj~KeqZ_hmMPlmq{F(jfWVn#$$#O)E?5xXJ|L_8VsO2oSnpGW)} zX&>nqIXH4?Ev44*}9D6+Wt=Nxae~Qz@xy1#>#m9|^8xuD*t~RbC?r^*u z?-d^&pB`Tre@lFQ{KELI_?_{O#6KN>BL1ED&*FbguuJeyh)EclP@FI;p*i7>ga;FT zP3%naPKr#*Od69^mQ<6}0)s5alHN_~OAbz+n!GsqiR4dHG%4d#ZcV9AS&-6|vNPq8 zlowLYrCd$|Ep(iWyIPy0OW z+w@`S8`JlsKau`&`djHAr~j0p%eXD$T&6a&KJx_js1;}J%X%~Gy&Fm{aQ*OGhd(|1^zeTVzc&2F2R?e zd+y`8&*UD@{V^{lZ${qMy!Z2s`IY&P6}T2mD=05GTIf}nT6lZms>0Vs8Ar_+b=#=> zMx7t+IeN_KNuxVPFB!dh^nuZb$9Rkh7*jlE<(MbNd^0w6?2@rZitLJNiykgIS{z=S zR=l!!UGe7P2a6vWryW;1u43HWam&UX8u!k)-tjKugT^O}&mKQ{e9icd@pq2DZ@g*z zk@4@1|7C*Lgun?yCNxgiG2w{`-%p%AanZ!CNnw*>C#6ohWzynFYbNcUbYZgZL0OE#7~G|ge!h-uZ+woiL>+MBl|-%@qU)za9~5v3DL zmz1t8-CFu=>D4k;7E+d7)=~CU*^TMrrZ-Q&FeCD>|IM5?^Yxh@%=~0l)2xTe9m@xm z$CYQ57nI*pzP|i;`GxYUw`SkE{npDBNfkpX@+yie7FFC^aiHRviqjSESB|f|xALp1 zp;h-)nX5-uFROm1Cbp)h=EvH`+RJt8>l5oU>$B_cum8Crp`pHEVZ*vc+UVT4tnr80 z_un@3w)0IxnwB)ZFh`n`GN)qBopTO0Z)^!@S>AGKZqnQpbKjkpH*fL0Ct9_wC9V5g zuea&iJlZCf7B5=-`RzrwA6pW)ByCCIl3SJ>Tyo=%{5#g% z@%mD)r6o(ZEq!lU(6W|g&n-K#?44zwF8g)4{c^YEe#-|hAF_Pi^7ocsTi(0Ebw$XE zlok0aHm-PY#eY^@U+KIuXl3HcoRwo%PFh*LvVG;9E4Q!Qzw*e+*H*s2@~f5RRr*yS ztBO~3u3Ee5zEuycI=t%nRj;kqub#Gg%j%u0f4;Nk&Qoh5*DPQ2)?G1oExhaHwH|Bh z)|%FS(-qg%)pd8*uC4=JPj)&4g+4|qQ9lHa&6S{M{r*+Tn zUfjLD`=0K--KOqmyU%og*!}GWw!w2l#D>fbV>is)(7a*ghV2{nZ#c5y)P{>2zT9xV z$EjyfPgGB8Pj*jn&#a#2o~1oKJ-d5MJ;!?9?77pE$BjN4jT=)p7Hpihv2J7Q z#(f*FYzo{od(%sse!n~U?!|YXx%;QhzMF?`&fmOp^CO$T-ZFm6<}Dv?`E;xOR=2Iq zTNiEpc^lj2vTfeB<=ZxG+q3QPwpX_OYugvwe&6o2J#c&c_L1AiZlAHee*41h%eQad ze&6;-w*PbcbKBq84g-{;XQh7c2M_<}l3QJe{Y30BqQKv&v?Lr9A>{3t*WbRrqxWt5 zJ$BH+W8XUCv6v{GA;(1MiT<8nMskmoMpkQLNDB5%tkiBJ4X}(pkDef_Svi@EYq&g{ zOvAedY9WWvtsSnR zWQlxU4Zp{`8ZsLu`P}i{Q5n;9T>}Y_cM&^zG4i|#`Pf7Dfo30sDe^RuByEGO`(1P` z@RiG-5tDS5v>|Mhmyk9FD`IjLi3Cm)X0v;kwr`W1BJNGPdBnuy<+nw86aPla$M@%v z&kH0N@Ak^zwKj|R$Vq@#Jxh2*efJNQiLlWmp^!)BKXdEf77MC(P!qe5OUzMP%p?~n2^PRzCM1hd4(ic_Fc~$md4zda%g*0TP7pbs+;SNmD};>ho0D31t*@1n)QD+f%%(%Hg$7kx%CEyT4q-TN3sBEm=w=d*XFI_749~nB3E<_3*E{+9zPGeX`reZ6>HAo_ zyzgVpYCI!kPxgHw>KfWQIU3hyv<fHvOFaok*{LVj2;w6t?*(?;`mqHRQbC$A-A zc{|7Rr>z2C(Z*@Z$sBDinTYR)@p$x8kk{R6nop3wUr7MZtEiXY3-24m zx1Gd=!_ppuZ1<6|T0e4{UoyVY>{0k-;iH9Dq8;PB0!$09G(3F|IbfGdPTA$-emQa1 zUO*U*%Yh8Ht3&96K7zMRC(u`Qqivr9erzVuynbu05*d8UhxJGkzfh-4kfC04MSbRZ z{}pj+pCUbr?|6H21iVClpn=tN`2(_37fejLIDFGXqP5Y8dlq=UC0W{ixSxme#oH3J z0~|Nv+eH7O^hMWrJ=Y#4Foce@NF!+AWuD{BKt04amilmWD9Q&%GvX{{xd6LE0Pr6rINba%UMQZr?Knlkgp#^AnS}GQiZWKG9Zp8!GN|Ef$d!*LA~k5kAiHAR zH=w+P!`o1&})h?pCH3Eg(Sri^7e_hGrX>Y7CyE# zX|Tf?^=leIBaN$iKCe0ZVAP@o^SD{lA75zW$eR7Wh8)uikT+p>JQThWGRGWjn^X>ACWz=l(H~ zrGEbp;muh9Ao@KAFQ|trwg9Q7r@WJdV8Kt#Sm$Y8GN4i(~yY#ShKzc)ZQ+h{s zmM6#)<(cxW@}00VwMl+Keo=k{c6dF4Xwb-@)j_WWy%zLc&<{bs1&=d08oUfahEPL< zA<>Xx7-AS^$T!S2EHo@N>@_@OI2fV{@d)t_F@%JMM1;hLl!vlVZK!Lgd#HbCP-sl( z=+KJL+VIz|%jQ0HZ=3=77)y4O*U32=g+tj@*av?%-Ax}uK7MDuA|EG_kAEQ_#F~#) zA|D%&j~&tj$j3jV^T-F0U67AS@+`SRzDwRH-;I16g9p=dK_qBI(6XT2L9Yg#4*EXm zN92PTJPbYtgUCm+Axq6iJM!@W^6`(G@-airhnr14YLE|teB8j=%2jDx-_^dC*y+BP zux|1ai|c!-&qQ7zhe>50Usol4+hCh;m3e+&Ltk}YRo^U9-4<0V&JK3;s) z`J>z`-(30n%2!vuymIBre(G;}!dlnlGC#U%&k0 z^%Qp{N1CbJ@}X3 zSW^6D0`>qq`#3Lq}?#hJr;-A!&w8}2^Xfj z=-=pWX|MDkeSq$z57K?~A=b!lqd(JMr2SGM>P80}Bz3b?xCNQT%Ckq!m|j*|GIh%2 zNfRfGA6Hy7cFgEeg$4O}xjES*M+_e}bVycaMtWLOVnTdeY*b`Kc$hIXB-q!}P4DXB z?C4-`r_*X=iBS@3Fwu$vlN4@nE37mY7%NA|#Tp8H8}j2~3yg&oCPSscgg}lkj>a2A z87obO3WF&EL8VP}g(({;>uyS!t)$GhrlfkqFfxpzFd9r}@{NWgbo!)H+;`_2%M7Nk z#QivNFGq+6XFP<2;0r+&r`AwlDxBZYRZsyM=^;mlJY!z1LtHF5}SJxVoTZ}2%m~V<+^sz5^RBMVg<`<@@vCddhfPeS8lw%@|3vsp6U#Hv$)zFuFQ5=o>FO#p z8Vb8Ax+;(KEvqsb^v143PEK8O3&3tNvD8Ene{@}dsc?OnNng=Ghk#U(^}@+TCXY!o zN=+=h(9lo`X5jxwV@PH|h?_MH0Q~uDOq=q+9B?=!gtKAYk!(_h2h*}irOLB`R0SL+ z*$FXaCRV}Uyl8pjRl?sav%Il>USWi6i>8!zK_xS$##jK3ud6gIt3v6T%>`rBn_PYl z2r+iKyBV?)KqJx`K=hcJMuSNcVbb!iY(GGmI|g;A*SpyNVv>cT3g189XJ$y8R+nbY@w`7EiIa2 zESfaE6jBW-6L0tn4o(g)xCxB0G(Z7^0%)=ew=MUe48JB_xE(G% z7%pOY!Ob0JD5U|!k{V=~q74PL`D!};JOC&jr0~4a7HC@j2snA8142;6#a~=3!)t?z z7@ym5rjF+C_$xG1cx{LBnaAT4Ui$JvZzwg^8q16g22=LLQqCgI9>Ham8G`4EREnmS z+CJGZ3v41Gcr7@mGSgHTW91=du4$Bbw(uHdX5i~Fme+&nvVMZLdL(DlIB>*P-leUYQ$)pfq+F$JBHgr<4v8snMP+30TB&yOSbX zG&MIa7EN33AtSwO(xGg6*Ocj{Ph;9(xNB=o6oz-DPa6oy7O{-S@<^V< zz#llI$#}36=>wk5CS;j-D~ky6Tz!NR5vL?V93|C9m=dQK=?+B*MA=XjR3DL*H`x{l zGUDu%xMd=)jQ<@XoTJ$enrypl`)nuX%mNO9YEXulqv-$bDS6z9I@5qd_zE;4`UqWi z$UZwjNwEwmvda{TOAx52OQx4T?nLmJ_ywX|{)gh?+kl#Z{-(fC!wbsoWer^wWxTQx zZ-@r}FD)bA6 zO*~^tXvv_IfD-Q%uM#)vTB1*JEpc_7;L3ts&%%|YYo9A@bWu`5Q%Fe*SxO!w-w;_( z=rV7rp-1S(LsO^3#1tLT^`VQfpE$!r?=po?;lJ6Frkk{PnMld>8Ks9P-B7l=yPM<& z7MYT#l$s2IWkseM+~@-jd6V3-wzim<^0xMl82;BD)7CC-EI;DWx13;p?1>$U<1wr7 zAsw10VO?;*pFA3S>#_f)njc1tuld;lpZ{M!K&8b0 z){*7p19A`PAsaB$x{EBQuGq1||D4GlKHHJ)ao>tdx#u1UTh)68UuUb-dkIbV-RixJ z{fZ~mdkyg=pR4y;VnDH0;ONL+YEbX(NECfpy>}#;^qP9_L}s!u_1+ox%hY=p%*p>v z@<}79$JLH&5ve6Ln4{H@N<3HMu9~!vxnv<|<*6DFYakEfN+yXU30ItYmxPm-1~Lk% zTac~^a1105_pSJv|8jVZxNC+bq6vtr#hrmnL3}f2)|2sEPde~jCDJA#4Tn7xzd1;g zhdawRmM`LN`o{9UAEx1^6w`pC6}S{C3|1_bFaI}?8v(~4_|q=(Rs+~`1nsvWzJ=7` zcOWl#J~?N3s)Y!v@Pz^2@_ch>?SeXm`;CG()gp#VL3y4H{%|R@0Zt?2V&P{SxRrxg zo}T02x%?~MbNRJ{`$I?q{&k9f@xUx{bYiXD7zsQ(^lp}dGU+Am-1=k|x?(Lp4T1Ys|qfrOy$8?lEwoJ3$Ze-zFZ42InT z2~@;`GG6zRv6nd&{*%(Nw>OhyL8e2=FfyEsAS0oJ$-!B;Jk;$1XjMiDy~5NV zC4kr84wT^eD7jyf1!%7plEq{RxgEN#zoSFC1A3(83qhWz9 zhQ?yQaXd|+iEu8Qj6U#Pnu^mU=`@38(kwcJ4yD8Ba5{pHq}eow=8})dRoIQmrv?xi|ClM1lL?_cJbSf=@>#SR7DJ_E^ff?jFxj{ZApU|0f7A>c@ z(hBIps-O+4p|!M**3$;sNN01iesm6P#%k$Yob!2xw$e7*PCLk_78^9y$epfy68H(o_5m> zw1;kl^_{!vX1axLrQ7IsdJnyq?x6Se>l>h*+taUap!?~=^Z@+_JxCv+k3!#NqKD{V z`Z#@p{*yjQpCb2RobWU~N}r+6(&y;&^ac7NJw{)m$LY)T1U*S#p|8?Y^fh{#zE023 zH|SY!lo?qV!~SX($)Z>^ z8_Z&$C3~F3vN-Z2d5XNu;&C$dpV(!;g1ki5kbjVqEP*AG=gA8!i9E}a$uBHL=w;HO z8_Xo@$=_HO8^VUNVQe@?75m8!j4Hk%4`OVvlRSh`#ul=bJk3V1ku00#uw0hM@>u~Z zWTV(OW8>KbHjzzYli3tDm6fn*>=sta%Gh)^gU#eRAa*OOV3n+jRkIpa z%j#G?Ykh$XPq8EHX?B!715NOA z?0NPAdyySuFR|n7Wp;v{gm(B<=v7~1r`hW;zxM_^%g(X$>`iuoy~W;U@342FbG^vk zgZHj~LyP<&yTmTDE9@h7m3_=UVV|xhb@;sMWoFg~{VkOkbht7ODQP7gTt31Ws^kbG@y?P9^wWAc@^P0uBv0(c z_LgAEOY)Qar2uJ=6etBr!ID7=kwPV-6efjB5mKZSB}Gevr5Gtzij(4{1SwHUl9Htq zDOF07(xnV3Q_7NtNJFJz(r{@6hVR+f;hihxN%>L%bbOCB~?o`Qms@c)k_UhqcmH(O=^(9`db?vr*(_e;B=$BZRsp(j5>-jH@ff4>Lo z55IFArnFCb2wF?7&;N&XP_bQn`v-O(;?UB^^6wpMq{scWiTz+X8-;ZxgM+t%17pybS{tZr>-)>SH@Hm9n! zc7Cn4QUto3mim_F+S_!MO6Zu^56iK-|6b0kX=#_M@uSVJt^_!3jS}Poc4fP+Kt)ok zA}LUi)QZ5N0N+(sSJyVTJJeclbp*B3 z6t|9J6k3&3%Du}NYnDXHF=Kha8fQC>?MLl6yZ_#)$c9#@+Xg(;x7OA+H&r&*G*)Yi zg@mrrQVJoZHW-I zwnGHE5;a#HYOYF@Ty=;*Q_|YlT(9Zizs@DLd2;TsebSYvrMN>09dGHU(6Q6zzRc!+ zVgJ2$x{}vLB5;^)ElUoIthbt`mgf34ha66elC08ttIH`6QM$@n5jsw2YpQH(Q0`m$ z@13UDFvtn5iM8rz+FP1i+MF$IhIn$wX`0(mDQ@j6n_Jpzn`#>?oeJi*HG&ioZCB8) zymuahx-Fu3v3Hs)aWB@*mz$fR{wpVIKRf06p_N8Q{ z>QtQabi9&)Q!}*zcXk{p$EPW(oIAG?Rb@_9O(h%G!Nzy6(nb_mK?R%GC{1o?(M)Nq zpHnGKt?baL6iE{s8l^m36WbaUR5@9C4%0#vhwZ8+w!}G9TAAWhYm+lO5Tvk*=S$pq zb|~*HpNdG0_+G9;a!ozYq^72}sl8IC!jc!^g~VUB^DN37nC3Q-y(W=8h0xqQ*4)7s zH0oNEJW8z%E!sAo!6c0cq;}*}#V^f89;lz!A5)+eCEO%*1*1KfuUH{uuXZ4G;G|pD*Q#yaLrq8h6Wm@mDEO+UN);lY% zbPKMe)HKW6BrB98E0iQFlw>QEWGj?p3zWp^Fda*7pl ziWPE-l@}>i94S^DDOMaQRval-94S^Dsa71RRvf8T9H~|usa72Qd`Y$9NVVcfwc<#% z;z+krm2Uka-TFnk^^0`t7a3NnGOV;@SZT?y(vo3?oMDBWVTGJwg`8o9oN0xeY0Yn@ z6-TBON2V1=rWHq~6-TBON2V1=rWHq)6-Sm8N0t>wmK8^q6-Sm8N0t>wmK8@vKNJfN z)D*Q0B_&$kCM8;M;GC@Wtp!I?q6J4%q6J4%q6J4%q9q?miB=p*Rvi5_r&;0>vof`} zh=HTFQweSn-GjDM1opRBngjbz%bm7NNw`o1f#<=GL}0wzMj6GP6>Xv~z3QcpF#H(b^(n z)02|b@`yWCKIus*Y037rZS5G2wb$0zW1L#sSl`gz;MCrLVUlv+=2+J_-*WHN23nfc z7rePF%uP&E!(`{`g{_TEO^wx}f44*L+Em-tHd`+S2{!$qqs_gW&3!9x+-sc{)wZ@+ zGUQa(($U)g075Jej%|$#EcecWD(jOV&+_Ed+}LdWh*O%Jn3$-Bf^s}3tIsKFn5u?p zYM8Eu8ETlRhFNNuqlURWEG#H26k%auo*L%!Ffpl6;7v@<74eBF`2s&efghp3lT?sW zz|$iXaFPlXdJ&5BNrkBjyyU_Z`$ojjiNh;EJO|vrfMcMw6r#BTEuUO`0e1o zf|3*kVq%H{5n+nl(9&`n7g<$pQ%k2vm8zynO;qrurYZENrm4^A>T|l1kJNM}AF1hr zip11(!AFFGj|fHn5DGpb6nsP|_=r&O5uxBCLWNJM=@xu~FNha>NleX9@n@*`GgSN; zD*g-=e};-bL&cw=;?Gd=XQ=oyRQwq#{tOj=hKfH!#h;<#&s6bes`xWi{Fy5LOcj5o zia%4upQ+-_RPko2cr#VJnJV5)6>p}BH&ex%rQ*p_@n8_g%U5caiYH6OlcnY}OU0L^ z;>%L;WvTeGRD4+~zAP1AmWnS&&1a69&m0whj*34=#h;_%&r$K`sQ7bK{5dNA92I|# zia$ripQGZ>QSs-f_;XeKxhno#6@RXZKUc+{tK!d9@#m`eb5;DgD*jv*f3AvOt&gd> zD*jv*e{P|p;A>tYA4e%X&Qoc~Q)$RkX~xY48uC>d@>Lr0RT}bD8uC>d z@>Lr0RX*gaawt&o7pOcaPzd*%bpyDr3 z@fWE0)%G*BP{m)U;xAP37pnLRRs4l2{z4Uhp^Cpy#b2o6FI4d>?R8>mp^Cpy#jmvI ziD_zkn5MRxX-dC=cm;o2qJlpyQNf>Db9Dfv%J68Mt}lzNI#yhkYBBec91^x;|1hfvUmP|$}^ z(1%dahfvUmP|$}^(1%d)2ce)3p`b6RK&ht)Rs2dl#j}cEsi$~W@hkNd&nkYUp5j@> zuhdgKtN4|Aif0wSQcv-$;?EQONh(n4C_**=N*%?snt!E^;#tjqUIwpgc+RxcHQZ;Z zaXB_|DjiB)MH-ckJe3c5DjiBaO)608DMFPFrJmwhr9-Kwcvk69>M5R8IVklM&#D}h zdWvUN4oW@6vx;A-r%44$Jw>R>L8+&BR^_17Q#`A3Q0gh3RXHg26wj(0lzNJ1HUCOI z#j~1!rJmwh&A(DllM0l2icrn3QZMnW=2xkgcvkbP)XU^VwO>d{EcEDCX zTAqLpW)ZTwI^Gl{CnYD^O|7o2!Kk~^u3f#+)bk?iYNOKS&kpq>d;HdpD|h*g-d3l} zBeaUvR;Q|JwTgOHr|M+cn6dU;g(H*{GQV@;Kb9$tI?YNBA?A(&Ds_lUFY#l7>xZ;^$bzAvk{OkZr!yotI-Ss_%Jyc!))O{vLa~8FO`b5$fYoenxyN2$b;K7gSV8CefBZ$f16JC# zc>1oC!S7J)8d2+xT5J_p+UR`Cl@YZ zrT8lO#3ofdxE+f<06kdC--NYyzQ+D7*8hLRdUZWknz0JZ&of{RY7}5ntn@srQo~Iv z!o!_3m&Zc_gKXj%r(7g)exySpg0xIaJ_rpV<^t^~VjnvC-y9 zd*g1N0^eCu2yDayF*onJ;1sWv)(@b}1!q&RdPNO11Q2Ag8vy(51-F$vr6b-B{Bi;n lgRoyCij{-vF=PUpCBkB<8~GT6n!pg3ir-><0pBl#{2wSQd*A>7 diff --git a/cla-frontend-contributor-console/src/ionic/assets/fonts/lato-font.scss b/cla-frontend-contributor-console/src/ionic/assets/fonts/lato-font.scss deleted file mode 100644 index 66f7a9118..000000000 --- a/cla-frontend-contributor-console/src/ionic/assets/fonts/lato-font.scss +++ /dev/null @@ -1,5 +0,0 @@ -@font-face { - font-family: "Lato"; - src: url('../../assets/fonts/Lato/Lato-Regular.ttf') format("truetype"); - font-weight: 300; -} diff --git a/cla-frontend-contributor-console/src/ionic/assets/fonts/open-sans-font.scss b/cla-frontend-contributor-console/src/ionic/assets/fonts/open-sans-font.scss deleted file mode 100644 index 8268beb80..000000000 --- a/cla-frontend-contributor-console/src/ionic/assets/fonts/open-sans-font.scss +++ /dev/null @@ -1,5 +0,0 @@ -@font-face { - font-family: "sans-serif"; - src: url('../../assets/fonts/Open_Sans/OpenSans-Regular.ttf') format("truetype"); - font-weight: 300; -} diff --git a/cla-frontend-contributor-console/src/ionic/assets/icon/favicon.png b/cla-frontend-contributor-console/src/ionic/assets/icon/favicon.png deleted file mode 100755 index 5c41fc05d696c0ff53bb25dd6c3ab45857d489d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 796 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^+km7#f-x7=Hc-(k~epN(~qoUL`OvSj}Ky5YL|!f7A`A_CSD7h%1A63XsWQmMlN} zp3J1n(tYPZBE%!GMxb7-2xtzbIGW)Q1rR|X8_q!1h^7Uu=KufyF6#~`1Kn?1666;Q z@(2UN%jf@AWaqBWEXygM*w=5%=;NjE<#WcrY(~bd4b3ezip-&{O?%!uur0M;yyWAX zxA(1CX9B4kAGW9N1?px@@^*Kzw@WrC1#&nGJR*x37`TN&n2}-D90{Nxdx@v7EBgZ` zPHri^z2COl0)=LHx;TbdoPK-hxLA{cfWyUu60S$u_9h+eirx6>|9sVrb1WPZ7O$&$ zy}PD>SC36m;h(cYwUdLNhG%$JmjjoB6XO!W6BUl%IE!Wl?^9qqdO}%o!k@2>nHKHUXu_VXhf!IDo-rc;4dFnGH9xvX+ diff --git a/cla-frontend-contributor-console/src/ionic/assets/icon/logo.svg b/cla-frontend-contributor-console/src/ionic/assets/icon/logo.svg deleted file mode 100755 index bb07e6175..000000000 --- a/cla-frontend-contributor-console/src/ionic/assets/icon/logo.svg +++ /dev/null @@ -1 +0,0 @@ -LF Logo \ No newline at end of file diff --git a/cla-frontend-contributor-console/src/ionic/assets/img/boat.jpeg b/cla-frontend-contributor-console/src/ionic/assets/img/boat.jpeg deleted file mode 100644 index 7a64d37f3e684247b940b1c9f5de70d69b2c5bbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253267 zcmbTe30zZW+BY0lSuG)n0!j-JA_+nXBv$DLAwd!nN*1D`cG#Olu`bX$&L#*LB3O1q z)ENj#NP=|K0<}(O9;ISkTLOW$v@@^TWsvDSPdn4j{!HKheS&S@@ArG(@B7}Ucu3B< z&$;jGy8hSyTJC)F{x^4NQL=n#KF!b1k5+&`v~TXwE=vySYiKlug2toKXd$#9zi3(j zj{NY4<`+i`qK;`anctdkk5zst%jfvhXvg%lz~ysF@jrQCWODWL|G@)(-;Frn|M2Y4 zvVnky$H5z|E59AxX?^6|<8#)i@2(|7>k0n~HTW*1dR8_u{^Utn>`pCXrMT zNS+8U>NuG?jPk~}eeeqHLg0fVq2|y3Ci>|&_anAR^YVUBta>hADwkkUXtam|xqk19 z{!uhq?cTZrsx6{~ZQDx|R=h*=r-jmjX?z;3yz<~ag*snD#*|9(5)R@dYW#11{AHe+ zJ8d?tpg1An+qeI}9ndTH9jL?N731`*s_KK4IBvr6o|o(Pk@vsAF|%SfIrb-WWFEi+ z;y8vJzu-IGK%L_|&LPKDwR@{@4teH%Rkc;*_$rQ#FV$D$I6#i$mY4L^FXPyYhgm$T3`fuzplDchvN(!M;y?o@^JhVR3_qu?;Tfs@3^k|5T27p%X@L( z4-e>H(A6cRRB{ven>TMtD5!pUPjy{gT5);h?(zdw33)Hp?knH>LmJIDXL1!SnyhUC z7I`Co^Tv&7ndw;mZ(sk%55GO@|9n7X>pSDmN=jyat8Rby-gno2_uh+E=+xI(n~v|^ ztLUfEUT>z+Vivx8FKK{Ad!&g*n|Swo^I1joMORn1FZ=PwUw--JbbWPYI$6+fU;nQs ze0%2q{@{D}ODFgHcI^_x)iveyd+HL%qE^1R=f(O12?zI;S5_ya{XdoX|N6r3J=XW0 zV}q)?rusnjUffCxEz|FP0pi|UrLWV!xHmz+_kSnh|I5q1_YCCFr`I@O*#1pp{ve$e z^)8bZ@WAiQl(!lZR?)#PYU2_Isb+;~1Y)KmV5>{;%Ly|AYD$63E+m zDosLV{ehRrF>HeT2%?429-%R5v9w2N>u4#oG+HL@3EGpi9GZkCqdiB{(6-Z_r&ZE) zv|8E$+RL;b(;8?kwBxi6S})B)`vuKLbJ1RAdy{sBc8zw6HcgwOeMq}Y`;_(v z+84CH(EbS!U*Q+&$MB2wd(1D{FV!#8ZqpUQ8$Uzy(vetZ4u{eJ9s)bF@o zm!HLN(9h*}-tP^+D}FcpJboYe-Sd0k_b0!9_y_n$_%r?E{W<=R`)~5k^)K|-`0w(6 z!GFL1VgDxo6aHp@tN&U53I8kpxBTb)@B072|LXu+KzP8afW&~bfK35;0nY`L2Gj%` z2>5A$DWEUF8t`hsrGU2sW&`d9d>-)kz~DeeU_xM8;FEy`fhB>Jf%^l08rT+S33LWd z1YQlC4ZIilMc}`KB7@cjr3PgONrScpy%6+rP)kr>kRxb3Xewwf=+mIT1P2GN3SJ-l zL~udy_FzNskAmBR&jkN6_;Ro(_~YQOSFBhOyCQYP(<`1^QNE&XMazo*6=zrc$BLO1 zpRV{@NO;I&A(eem(qN_}?R< zBX|*c5j!L5BThsNM_i8hAmY!Fk&&FpoXFD1y2uleuE@6{e;4_;m5i0?E2S%|R{m_| zsg)BeXIB2_BcYEZKa%^%^N$>Ur0uZZSEOQNfyo1%xJ--`a25x`i-$YWG8jxubFDaNPFVCH(Jlxbid zXO1%8VSd4iW(inY)*;p@)~{K2R|Tx%tdg$Uv+9*q7go)$`p4?{)w!#ySGTTyb@j~Z zzs9VI$%(0sF~$5cW-jI*u?ew~*xj+6v9HJeHZCxZ7pIJSIqv6iQ*ocKVXk>{P30QX znhR?d*7~pItyQi4(b}Q4x7U6h|7iS{_l)y^JP1uuQPPm%zMdI4Tg2ej7!NhkG|H)p@E@3yaf64ysy0CSd*43=* zSvR@vi=@Yr6iGi$8cAA64o%*atV=$b{C4tRIqNxFImbA!a~9Xfte37oynbZ;M=6mh z&!+58v8H&r!Q4&U-P|+WX`Vk%$kXvo@!n1KPu-ZRPd$_B*$}j0^M<_}tQ+1-i%82& zdns)s?Oyt-ba{GX`s?Zc`FPUfrH^+${`TYl&Jbqo$*^U7$dBgB_>KHa{6A&#GAlDr zWqPwBv+}ckmUS`fOF^ojTJUqh`$C3LAv`9W6#n z-+d-Dt+;;u; zi=`_{wWW^IuXjjx^zQg{r*LP>&iP&IcOBYw?-QAzP zzjyx^2MP~34*DJ3b?{;x6J+XkeM)_E{k@l-dFj+ke|uT`@@t3ahxQ)2{X^alTYvcb z!}*8pKMMR&<&P$RocQC0AK&{)?oS4P>i5%%pHBX4-OrkS_UVx=N1P2|4Z9oeG-fn* zHGXw;>(NV1@l8jXK5pL9JkqkV_P+6YspT@s*!<1b6K1nD2bH)7eGuI^6Yfx1xKZC!xpG^HuK;dT;az z`hIaT>}37PyJorhVt-P9NB_SpI?LQC(WzgaUVFOr^k2_ZotgQ0&d*={Mf@+0|KcA5 z`hoWcw+v2LIo7_R;Gvg>KC@|UZ`(K7U5+@%amRlT?;XDDR5`D@o^ZL`Yu&Gm_>a_& zJUF}a?DVL3^x~^&uMYll)h~~qqn)ciw{*Vj{QQN&3zM&HdhOM*(}zHz4dnP+y8O>>FaOa$iDH$%}qC7zqRq!#BJg2 z@jJph-Uq z@q;}de7W$_!aqMe@=?%7rr$>Ww(oaqe`oz4-2d^*yBqKR`kwgS?fdHcAAVf<@gF`p z_{qOMZTc+ov%cRy_IuZ2=HilUHtX${!;swfBx0(oESqT>irII6z2u4N z*GlSby>B)DX*U0*BOhqzxc+b%U7qay?y=0&1vu4{Cn?zuyFU2Pe1$p;)A6>fA#fW{`$AS z|AWjDv7T>|59diH5g7E{Ea^0Vf6Oz$KOo3I$UiXfm>kog>}tAB~i>B!NhX5YNJdwP9S8?r4=?!s$h%k!Ij>){M%A5QW9$IFxa z)3>wykd{Xaqh8TrVbQVC(b2K&SiIP&Z1PGKq*5=z^6OdZB_%J_x36z7h#!aoi@{1_ zFp{*{8QI$Z->+{LXb}O_^3kDOBiqmEZ&t)MItIQ<>X$_s%cJrvJ>DrUXQy^uBrYgE>gL)Hmr$$F8n zR?Xt8+;WYCD;QO?#%oKtr4D`84h_$`+aUHZV!V5jY%`YLX@zLldacP5BuSO~(hjw3AXHSfd7#1d+7NctpSMw^U-yOT<6$TE5Xy2hVnaLSrnk6&N&^zZG6Qja$ zu~#8-b8y7ip(XE(D8%#RdIR;Ik~*OlA7u|~E$yWS;qoWrYStPRtHyV=k!<>=!3~>f~56*%8$R28+KXCu1lvBRf}RV2|}P z3N-69OJ}4d>orR^or)dv!N1uPZe~u&AtO6yNVBwn!6Bh>xtHfyXLY1vbw}MovgQn> z(2D8wvc}~&4a=d@D`g5E4|A+1Dztn|rre9)$auX>+f292bY_Rv(hB)fqd;JgW<6&& zR@%TB(#~ZSyX9ord2D*1T-D8z$lRKUJ38_VnyFOHR8>yHP~&O;6WK|U(uq+PU(L#P zB#!G`rZc663ZChVY%Z>QzzKo$;sdNayU(P-0}gAZcsw;8D$$xX2&K_1&F;{4v5C46 zRr4AsowQ2MTMZe5C2`rj{bMF9fJDBA1F!5g%;=y{#pjdj8KoFTYSkx2-N;kYh2f?W zm8I#ehAxKC+C5@8kf7>XYg|n`-hcdH&dLXG&QwIXo2Prqc%i1}T_&^j>VXtfG_#7= znejf;Y3$g_d`eW$71(CS{Lg#TEJ?(>JYi^e9 z9dX|n-RxPIU}CC@e9XAoD_3SLWK&B%u7h6ZkGiq^mUgTR8GKw0QzLe%cUl&R1wlI2 z-LM0k0r5G>f0W@mtOm$?#Nbg`G@7X*mx3!8o3TRVcyE>rMy~Smx&^&6C9q-`C9ywS zbk`;5RF~UtfDw~CxL7MwqzP;@vrrrgb9Nif6voq;xmS-iY>6(1dP|8xQ>HFc(3uyV zyAn$n#ctUH*z*{%Pwy1eqp0a)oomiY9-k;JIse?)8r_4Y0;?iA#bV@ak;QX`*;WJR z!h@{sLhGQW-#G_O#ZzynDdp%?+^NfJQtH3hr&swy0agFY99Iw8JSm5F) zd%-GyGim9@XcC@Bu5k=&7C+b;_NR+$Qre)>!djIjGq)f5-erc_Whvdj+oAFN(ZR7g zEIuR%0l{GN43H`mpvAJ_Sm!fNA|pT&Y{b!HF^|G3h}oLX#ECHVkUN9V0ZLcN>h$n*+x?-8LoFk5z(<1np_0MqF|g@DZpcF ze&>Oaa=FHKQz7?W256k2fSF*u*QR8M*M{*@7F+F{WuU>sDMROM9wESnf^&cm$Lfch z0!pzRb;U5sT5^G5|(?>jPg z72S3$G;k@x8fr2Zxmf9`@}xSaQ`#zs4+UCqj22+YaK0usz^)@KI{fH+p-{|LZQX1hac{v&aUbRl0&pH*gf)XXuM z8FiXxVJr`>(RT?$Jv1(!B+k}~0SI)uOr`9L&Yk{-(2z$lE}V6*G&ymEMduo?uMdnRh8L=h!9j?I1ghb{k?>7tI z36LWA8i&Q^x=cMvwQLUlw7%$s8OsAORYi=cJYq!}bF8&=nJR(B)K!#^gJTezhI+$d znWJDi01m)+JrO!(kfl$G)YJ&Mm8My!xZP?>kY)4q>VjPnEuy6iJ4eN>Y@98Gso>tk zAIo4@#LVlN`xEZwDkTyEzDkKWeziYA0Vo|m2pk4Jo~)CWywfFsM(TnqU}8Rjx)VNN zaYsjTO^Gpe1+K|f+YP?EYzLhV0m^nuhoGHIDQu%%-5leI(!Szny00n-(dK}n7*y`9 zu?1a6>HhnSXG_NXjG62+K^;tK=RiT=K2dK0oQ;wW-l(-(Qx)rTI-{3F&ag(#5Ll}) z)MdHh^6FF%qhyIat#;;(_VdgU)Q&a#qZirffRo+OQ~wWWsu`nbv+8t9{-SH)5a zn#nv=HJCVf2VV`x#B>57AW)gkX*xH0IsA467PcMSlu}s3`-PIIx-nI^I=E-1D;evd zA=g3IC27HMe#8T{%<-{U;A8+hXaJ709cXMqevG{_q5F?<| zRLWE7jZym(9_U?0xoTFtR%=p~<$AV6uXH2;3W!mk0RWQWK_0j0Fe)KXysmA=`?;y| zO09gO%iGgw9h>+0JO@F5A|MGdN&ub$UaVJD<$PYYS3rgXSf((E@y}t3*=$!aLMD6{j!E4+nwZ^uzTw=LF5+^Mnt(0SU9@jpy+2p zEJZHUP0SR2l`HI)DN?m2$Ry0RHJ~M}&S@Aa2}Q;!OkI{G><$P?$dVB6lbC|CI;Z|G&uieUYF{i-(X1Zax#zeq@&s&l^f}Ik4 zzyaJX$g5m?Z2`QL0+4V+m-jliMBhZ1obHd2cm@LTV_rPJ1^k~#&mgyOx>=!6UZR7x zQoT}TQV|T!ofs8#g@)PgkkAZ%_Wci`!=|7~%BrZ8242*qwf)jn*KWGv zH`If)7S<|8%yt+!PpUeL3qtEi{FZND+S%B;(URXdz7j+O+9Fpfi4z`| zr7Q-z>^7NcHjmqKsa@^b^kE*@JYD8l=yHM*4&_%wT|7}{^SV3XU2HrncR#dTZ*zmz zf6L#p!LC%@g$k~4pI6w!1-vbg*@31i@;72(Wd(hfS?R7sO{=pT!H`c{a5g>;NK8OT ztpL`NVi847TtvA%>I2B_;TgV5iRtKZ1F$8_0$>5xjTmm)+&H8n%-J2Po|Qmsj8e)i zu9k=qFoRMM_$5S#5Cxbi`54Pi_!V{Eb`0KpzOOT$_Dii<=Ta1%m~O@WLF)k=3xgp#Wp zp%w0j<)afW3(jcFCM@4-#3~~uC-imBalueenKZFYc?Al4!aP9T*C6oclI z6~{ZCuCV)vdV%!W2yHmXC}15BD^y&N56)aE+Mzz;VMrD7IAUCYkyfXsLPGh238H$4 z4oK!>VN5%B9yBUkw4pRKv20*cz4K4GE!IOvxtffS!I2bGu;fTyVuoh=Ge70nOhw~w zUwDh}tuho1*%;|*ni9qi6$wqA$`48SIJww}r_yLyS^ALpkW^1}>@IE(DGn+L7(S>?zG3n*kld z)bj0;TA2hOU#p!t`*8pv;s}B(CN`Q75bU!m;$R{LL)ymmI0I6k%w(B_F53^`57|%p z1Vp?SSyL*SpsbV<8gW$15(JWxg#w}xk&sJ?4K70}OIpGqpNzVRx)J_EE~6|O_(zRF zabI!?w1q^f!2Oa@ktH6NAL8dcnNd~tHyM+;eO%S&0CEL9D>$nxMK>-BZU zSbh_o>r&)g{XlS!`6Wl|^x8bf^5YTav@6~9n{u+4r)#4CVpB*%5%va?Xj)u!84N+&bil!0ECfL-g89?!v1EqjBu@5u^P*8@DJ=P&LG8DJ3 z$Fj*eN%yY|Zi`D<9I~IV3kPN_W?yWJBpg=r#N@IrEDI1m#R})=;Hg!-aMeWnm6(E% zf`kW`4f)lMHOR-k5p(Cpz-ZBwf^$;vAirO*RAishK|uy%4G;K^r2T$3y4*Jh`;IaD z?*BB4gY~g5nNJuI6apHA1n-F}Cf*MZ3NMIN0ZZ0gTP;FrZAz}PPFOGZ8M}B%xtlwM zj*fzS!PNt0V^fF0C%U6#x^AVr1}PlG3o|v{Bz_{=jUhA!kD3Hg2uGf-iu^;)F6amp z628W(Bcjl?ElsD57^d|mgmwt*vmHU%P$#ihO7|HQ-?yGvKj@kd=3p4$gx{JUvBZbQ zvTRmYUm$KsStw!F6LuR(4dr#7n5K}c*Xp$05siSpzM0Iv5IMFHlh*+vU~KJ_(c$wiiG(<*O6LDax{z)$+wRK7$H|696fIEU>NFo2=4?%QRx+2wo1Wud0X}! zT6nD@;{EI5wXfSvV{~dtDIeU9z^6oJHq%e(6nbqTv;rrr7(_xAV))Dwl;^g z9H<76y<-uqvpehi~y8 zf+`8liH#BcqEHgk&!*7vA(E012Qs7nl23dg2ss6)W_Ec;V->Zb-t)+-(UCTw=qwov zp$d%bp+txRS$88(1Anj5!;|-zRB~r@hu#PmZbtYr>QX9^M?ko8mr z&QtOu6@d0_H9c<`2-B&hPbA)o38_laBnH_=%hgNoT-E8=a#9xW5;~mykRqA6 zOKR#rYkD3PiC(%Y5hg8c6{Uq4*bZ*#nAY1r>b{(N_LCU!%@R>lt3Uy=12e-ENbn2| z@>vqKl6pWGPr(b~tAou=%LYbJ4Dv#Jh!uj-fMyZ`|E(l_j*6VJOoAwlpjHzJ;5l+J z;XP!O|f4|w(q5$AIYFksOrv#;hHZE$d9tma5FXN*qxri&;n zjP`pGUwzaP_aOTM_pk$Dy>@ywW_n>bzoz3-oUwg}=JH9Gve+G)A|8Adf&PSTX`$Dp z#Pge7M=HviPnFehrDtWHyj`JU4zJ=2hi%F<)XNr29R@b&=WZS@K;8p+NquS8C6S9~ zTPi|YsQoL15J-bMrzs@`J0MiFb~)4oOyO-IU*rG>`mM}s@E z<$1Yy5@M}HSMi)cS@K|8<2S#-@Sl?Lw(d}M7avi!SH*Cd;yAj}UptsSr26{lP?JTy zKP^9(dp0C=h2&|vFnx3WS58HJsjxnzT+ zsykhGnBh{&Oqc1~OPb<@2-Cr1W<@dP%beJ==fjYntw%AD#|I9=VCkn3)$SbOn0UI)c);(jHv?Wc|wvX9Yk^v6ZCl= z7)U<&uTKyazTb%X_=pRZ7y=?q+VGwX+LKw2WSN6kR|7;xI#+5Jg0=3$iPj6{DG-h| z!8saS%B?VUmL{gyPqcVjS?lSz-+hDYs430)jq;y6oU>PB-58t6Nftk7#~XDWUkHX5xj$x=iL%0q84)`i|} z+dg#mrmWZ+}{L4xPEc=|bN&(;w zPYwVf^c}?jXHbO|^%~w0by#^EsU+`EeVxY7ZxGbYbQgMiW@crC(V#3fD>dGZ+geS>*lUai>#Sv10V_!VzQP zL)|P4p$C{af_B39sk$?NN4TL)@0m`|EhrK4*Mcb-T1uYK$Qw3P6%B=fKZfZ#x7RgK zcNQCeWlZPl95;eCy&ZMhHjN~&M`Af(h?d79Ga15A25(S*ZK+!?IUMC|7uheLq*n)P z=BG94=%d?mxq{DIo>^yDK-SHg;uqA@_+Y&B$gu&=+A3TJoRx%5$61i4F7n~{d_Ns(y>po@vnv_z@W{(^#%aCVtMrn)d z3Wnkkyud7k{8-b%8jK1HBAE~ZKg-f2+K%{#GQ#g8t}kN8A}NKG1Eoq7E)p4#0hu<> z7I92Zw~TmV3RwY@ly&%i`_`&`GM#8SS_FqD_aUX;c~y^qPR;5P6XO<=TMU{>)AfVwv_eIEMOeYM2L~nI zPf+a+DL3>MCyBZ@)R%?*)XK149BdQ2g0~d4NQO!e4YA9uM~-YOINkK_TJy?86z%Wi z4`v00M2F^;JY(3+?|6f1fB}UBZ2;Ps(S(C#9rGgNGL@?83i~=z(-l*AWDYqtcxfye zzfb|fgL1o)eL-)qq2^IMd!`h5DDS5iPrrWrv#%Z`bOW=Cc}eq>F~QI=Z}7%c1!$>| zP*dVdd~}3-wCv>`BCXHVKoSUAA`J@2hYeEa;fHV>(JXudS^w{ZhG!+g1Ic|aUk)I? zx?X$Dqi!CniOil@1$iPs-jQE1hFs*9b`1Dlvn7Y+w5V=|6 zn9yr7SGd!{*0tIV5z`#5_Y=+MXcJYsGWNKWR5RS8vV(8#vC-zAk79(8mI$b{EDWm# zlPH@YIlXg`>6UOTfRtgbRo0-_5~h^zsLCZI)_ zNIQm%qf#DP(er< z(p&sqghgw+oaG#03slbV7>UhUsCZ^?<%!c_r;ZdUxBvRd+|li`1Lkt3qpKlO@cLKp z=&!f^_xY8doeRtrKWBK8oh=Vx%-0((&#y~(EpOL4D7?nd>H4s}=e$ z8V;aC1|mb@4W1D&M)Fjn%(H3j$U75-CSrW(FCPkrSQTYaK+)w0!&hk_ry_X)7DN$K z5>C2F@|S|ue8ikc+2H{o$#p=ICGspj)(MG`q&U_eibR1mx}tbUxDl#Es zJUt~uH0B(c?U>a>nOlzHrkyB=#*x7$$kVehd5S_oig7&P!-Q2CKAAh!%WmrI zW-dJ+HD7VBO4XNFt#NacSC&=HjF-=-3(QwW(xXhpSH%51JExt+PzyH|Th?}rO?}jw zBCygq=yr05H8HJc6GNH%G+oI%6FECI*ZVAQIsVKFJH0YcvV9_JqArF${lWsv9hf^j zs$VqRN!p+h_a7eA2uUddk7F@2uUHN2UV?mB=CPTQVmEm@NX!{+<;CksUkD4qK9wTb zmoiyaH$_LFwZ&-GJP>9nNlF9hJ?QFC*qI4NpXap#p5*am&qoxUNRuELS<&z8k@}5@ zmAvK4i;)`;ttWC`CbclLa9dyVw`2)vi+Ww#Vr{%j3Ep@nk{SR%9FgFi#L~!s_yGw% zOxEB-06Q>2hw3qTth4AHu%S-~@}TlMtVly(-o#gYk0cVMY!iPt3|$I7+KpLZyyj?K zNj{UPH}Uu5s2TAm&JJ=Ebj+$W_2i>PIi3#Pu4FdbP6I` zu$WU(6`EM-S+{&%Qe=I#@uG~q&v?m_a?`tqD_w(7D6XPcm^#`-_okan8nMxSf1^FA zuvTUgEgqE2g=c*=Ls#4_OiR%u&-5x<^SQ5G*KRVYi*~zb>Ei%Tib<-hz5d|fnBEzX z9@Jb&P$__&&X!8XIf^jXJ7D+vM+?J z-?JVyctj;sJ-_7j=1_gY4BLE4jm;!>VmX`=!WD z0+j(53y3s^i|?BUjK(w;g~n9uj+@OYo6k?IqLMtD5SWtLw-ctta%8y0Z^>&24lmH# ztrM>nW&NHHIk7pLC48V z{`y4QS)(U&AX#&}E-$z1(#>eABL&(W9 ztFDIT=RTObqu6wm$90T^WSt3gcewMZmRHi7>nV2Q8ME=I%_@RIJP30)8jYRskYVV5 zt1PndNXY;Vx)#DRht?DfsXT@9qU?R9(gg8L!)j1dQl&!*5B6*Gyj67SXoVjd?Fj88 zU`DK>okCF`EM^m_&*B3jJYPMQvRE-yzqlD)qU86?bgM@yHlm1yqK8NA=F^dmMg5|133>hPCIGp6qN#6pD+2@SAg)tw7)eLVj$mcC^6wRt6&tP~^ z_F7ZtSE1_dnk03Ta>wlX;`u5Gx5C&!mtBj~wGOd2iKcbi&Zigz$eQnvfrIPaL9*A?Mv;|Mc%U#+_WV(D@teg$>oT}b7Dw(`KCXKIEt1`Vuzo^Z~41mwi0 zIK9G{;@8%xYKU*>CVqmj$8Is23an?W>GuI#VnUZ-LI_LyOh2VPKd|=bnsAsRB~Ky& zpWK$Mk-|KzJ|!E#d5UzW>XK|kBGXHWrQ!T+l%61N>qN^OT6u-!t#K+VOcHMKJ~bfq zD1`J5ESl4T zAT2LBl7DsZ(WpzO^#xaN-cBh8XQV1>3`b|A2{#R z4uJb$v<`Sd-h-hLX4M1%&spv0cSyJit0YE7hDVHuTj250n-`YBy?DJn65bgtg%}$Z zoDyltI*mNB#E}K3uPRZxvO+X3jk?)%aACAYAT@rH-;PmcrO5W7$_kV&-QA(x(`rrc zW{cznPSf*)Q+xECkHi)n?Fr-C*;B{W=vY^JiZ8{csC3(Gnoor%Mc#F)M{+Fh&$?^I zJWBs3tfoFwl2#$k7jDo<8|tfI?gOtLDebvdAD5sEO=-P9AzVz3f9&L zn_FTEp!;td*k-a$G%a4k6~tr!;fQ94A6-u8;0j_VzFZBko=A=GMIWODXT(1{5L)B~UXZ(qAmN;WV$uZ$!ea zXPx#EWLhQpfRyvOXzNNuqd4Uj(0kE}jUP@6NgR`X5sKP^5|rs~px%#e81+b-YNI7H zFnNZ5k}Yx_%=3y>bGa6oQEPJ#=G^3>2HDErXXLS6?mr~mYKYR@W$v5#%r4{&L@fYN z?DBOQUdyF31qyxJlFTVNdodlY!j^65>1;c3`f^v}3g`LggdH)ZaVghZy9>qPOw9~u zOtpWMksI{n-o)TnMPgH0iL|r2r1ARcDtD)#7X%PXfhJWu*@<%uVL#{)bIGH0ug+5v zrcM~R|EzTTP&oRhqTZtR#eiB6#U$Qwp0xKY7~RjQW%Nq<13G#X!3?6d5$0uSS09&f}_j=;QU)*xmM& zf|5s6=R4fnme_XDP7OA52vq6_^Q*j2(XQm0=*2FsLR{Qeb%!Sw&rC_XljgY5j*cPG zQWa_zjz~w8<qMhi;=~7Gi}?<1cE505YyHY_Q`xQCJdWE+9fiFo;bqrbBV*tu;{=EvY^3 zCLf^3>7vV{(iyF`=lmE0(T+aeHm46G!I5Q(O)v>!1s*jhD3Q{)Vx%NOc~r_1qc{Vt zA;v{uTTj*$cLoi$7QnevYP3erqf@WOI3gs~mx&(#98*>e2+8>ZIaUo`lQqw;HZHy{*=>JBp1p zf3QyJ&K4xsTf5tBg-3N!y&|u3STt0*E~K>0^b+s2t0AoCkI1usRp|Fb(Eg%pIXhep zu^&6>g5RFqaj!Ju*uE;aRgb{2N(x`&yaspApf<+cAyZ4 zL_pJ9HaqcEo%+|v)a2Vv92WvqfyBB+)NOH z&Lkq!?*Sw!vZF7EbTk>|`4wxxShIGe}^n+Lc#|h1Xos)*pWeowG zLYh7WfM=B#&-x3Txq~Iy;DBiJ>oec>%c$mL(L@Twi4sUjW^@TIlFShZ>-5P2OHqqP zEikuu$B@gD$Wp}-F+Fuo+alHDNk*mHT7Fg;hWw4qh!l%8a3W(u(ZQT4V=q2G8Ug!GmS_GMUhPDhA1 z$#M>tpYLTAJ1$t8RgMc{mZgS4Pbwad(MU9xJGw#z3X#%^eBk9%aqP6neY1tv@}oX$ zW$H#WQ5?_S#MF}4@`C*9hT@sm>$xm*$d7_s8eeEglz&!`gjTxHJb@Dt2*4 zX3Thma}sq`)l4$AS&r)auLQHD93w=6-dEl5z;LIU`E;J?;E18YqZE_QPr6{#CpDF4 z^{C%Z{bGWjJp59I6v{AW;zN3bXe|qt1W&L-%6k$6gSQ;ZBTTm!q_)Tx29j+>T3-%; zz_rP|5xFyxVe_b2whzOhVjCylUm(SwN%HAvJG9eR|AuwZVw*{Ul8QsR3r$GpAQlpI z6_2maMR{S#BUy_b1cA0VG>S{fJK;mJc5FqGYjWod2?jP@ zw`dyTc<3xON1PWerpuqsH-^@QS|tf%$y(#p8ONX?ZQFR#;F5f1oTtyqt*0qgYm#o6 zcB(RZQaYF=US8PI^`Y6lOhr+6$pfdUOEn%Gu~>2FqsjQk!{*+IzCRK#Y4gFHgfFcl4_edZp~IU2|BOj|Ukx7lY_uvi~Rf)OoKQ+P2##Mfvo zH%}0<+YCrxWz?e2GF4Jrnn-d>8MO#=4E5-71}P>@7!Dq(FH;6z3c z$$61U(y81aA-`Awjn?$p#=S4~?4MVxTa#{0v2?QI?u;{qj4e^;Q%bwkK_!9CT_Wzy zCa!HNG5D%;TaTq7RyW+<^hrgpdHsNNu!JFNsPY=v`phZ`L)F1fn3B^4Ps_aL=M@Y| zwh`N*UbUv2FF@)4J*B|uijOJg8Io>J_78qsr3?ZNwQ5^Vr*O`*{SysU}1YFVlM7o zUt96_(PxfS`#(=v5t5%tKIkNgb_6)YVf###%KN~l7p+1)1UWUHS|(2xW+T)pP)@iy zb+)rNN+LDQA`VW6-H#MlccV!TO#KRhYC^KC(3OC^mA1N1gdNo_YtX1GGrD;dLmK(U z6D3FnEewh3>NGmuIzpLY+zV?|M7L1sM5*RXywEzP(UB}C9&CzmeG1megmka&kG&** zA&0(3b-rhi&TQ<5Vqt@jM!Bz7(5#IQY*{<=o@y}L5Z+gJE-JTwTVZ^1P3UgkSgX{^ zYZgTJr#7XxlqU+uDh$!J%x%$*PGdH^X|kqyB)#iWJ9BAgp6*kviQUYXnogI$YSr`A z1HV>X@)Ve~gNjOVP)p~ki1q?6d`551u5e8k_GAA}fBnJ!9oP>QANkuLkHsdNt8&o6 za`Z4lOY5Nf81N2jVwZ@u-cgtWDW#wT4Y8|6HGrM5-N9#MlV+!~!wo_~xXb$vb0e2aYDP(eN zEGD9Wv60Y2rUx&<<}SGi*$>`C+mu|S7R&3rat14-8u>hvvBmWMEQQ*rf)X6Yrk&j6 z{c!`G{pAgwn24w>ZWK@g_t4tzz*WZ~fsYm_g6=~aU9HNU?=oM_qY_$BP!l);{*Agy z(J)dqPY#7h8G%MnRt%z*MRe}MNdJqcF6xWc%-kQ;yx|f)aZqk5pGuirm08o5yS{j< zD6#31%ym%KnN~KI=)Ws8f17hU1zI20E-(+BXba0QRD>y*%xxxNdSaBEbDZp|b22Le zWpN{DdquWRfvx7?nQf|UPG++t$CPbO*e^5f1B972%&_gu&%}|k?jjq??k=k&S+Loj z-pdL&Ytg9Q1?Mh_N?mnrn`><5*w)a*LpLb@gF;^qc8T~xjUvH4up@}rqej0MwhFtu zkqlRLmU5Ni282A=26Mhs(2H#aoOX4SZT{NAaOk0Tt>+dmVewi=oB0w_oWoi?v{%Y! z_8H&Y8~#b7P4$^oLV%4ZhDROj@qMoW$-0rnMM_AnHFAYn#ZG%M7&$^L^B*ILO}eBM zt0m2b&C{gvL(~GUU=o)p7+R>t#Mn#YNAen!L4^|PTNH#@%tiDJ0J||0gkOY@u}B?3 zwcC&+$tEhj+km!DFe!3`Mtc zUH)*&z@6Nbu8@SI?ukLGow-;t9$CU2I?6{B2ba%P?N1I`4bLV^gXA8D>xZ@sd zY9EoH(wy4xv}NUR9GDAHd?mi;0OkfqOSV>$SjfkD>)u~Ic>JOo5d-Pp?;-hf5?&I4 zKKx7N2dT)4$|L!JxdPb+j6up?JoJldy}dEJZHwUP(r%z9w%V*r81rnA?X9xfGd#c+ zQnf51l@5p$fwFQayirPbhkEg)Obl2a*-Dly*6=!!`$K?AaxoC38Gx%0Plci9a8QZu zFcY?}mPSCb8L4K zOW1-tsY=ed#rt%dqWWw@8nfKfERNUlIn7L$E1|=!9mpM1IlA15f!=eglH5#Q$E5^r z0XCJ{?j&_;w34)_4cDp)O(JI)UzjM!^Be=9y1++J*fy^>umD9nJW zh7&WI!}fo&Z)?VC2TUrl`_s?_n#QXbWa^^P$+_XJT`S9@Yn!>p|1x(>8!%*Om&UJw zXuK$ROclAjPSOO1;34cJKa8D**lEwa$ehv%IQVJ_iIi-%loYYgxl;JYinH4z zshs9q_F&0LrlMM7kU0-V;(HRdNVv=~uxjE)2kD^U+O|89CHUfuXt(Ts@AO>YZS49( z4HU!>`Cg$MeH%00#HCjI)t7Ae@&>*%nT;#~_U<5Q*jtc%fPbpG_>&n+(Qq!qJh1B2 z$5k_5)BQKON{@1Gz@@3)!&k3_uWJ0vQke)gwXH>#pUK+d?eyL$a!ty$-RqSDQm?L& zCH2T-mEz81jX5!_O;T`=XHREi3;w{6|4El2DpaOOt|4*s3_HxyG;g^+WE~VbkT2fS zxuLFqKx&kU+(I@2I8%>S5p5{Ulcehois$Hgrss#IHnhlG33O(@SQhu1IWm8(rcxi> zTOi1hi3T!@9ahD-jtL({Wk0p3{L5hykCUWKWXU)~#S)<{B|(i!h@CSweLbfaA#nPR zx#O~zcyZj1(8f!up%579yTayd$j;_P+G7cn8C!8N3hd@UN{2r$;$A`*k1m;P%`7R{ zggaGA(c2`oz;?rJ>79c9ghQ>DhjW$jBVE_G z3Q}yjLn=pWld|~kEtBj)psRs3o*l{2hh^5ZVrO-8erHlogNj*&y&?CFe%UV;1y=El z7Pfda?l9h_+pZX7t(i-FCYTLJF%Q7#;y!#6X9cU+Q43mQE+u5(#tY^sJ-dB zoCA+Z=qwy4Qt?QVxoh}tU%M@~UhwhFHn2;SXfIqnD&{p-T?bRrB>g;b7wu}Ih7nS2 zTWa#U*LATPjH3hMExN?|F!GfFl?0pZjos>l#g20iLbN@nWuZ+p- zc4fD`CTnI7PX2 z^dM_jcbYJKmFY}jN%?&8N`3f1&-;x<8DER9q>c{JC6aWpZRS|%1rKxf^5#I?`1Wrmw36>9EXh1d%?oi!F6%TE@ zO;-r>av3|Lv`ikK#g{5WE#leBlKUwZtARhU3Y-gK1(p-{3cMy%6jB22;+tCRl4{k~ z0(7fE`068#;{IzZNSKv{jWGt!8fe9Y3$Y8n?*OjX2sOoL$oiv!FQp0T1>y)$JhjB= z^VsC!s01T$W5m#_^^k|wJBz{m5EREpY1^g{IrlbZ6}J|jtI%bN?jIBM+c&7`X2jFm zGo%qLL|7%ciz^({;;A(D!eG6Xi|w~Sa;I_ViJ{;=9$TMPx*P66*NEhd&QxC5*n-Dm_4XyUxJvtC5xw}Ri1d(Yoh22bagL&7?(bp=^=9v;6=xmar;ZT=1z%2IXG;@rtz&AY#ZSy%NESIguca(+kLB;S+ zN#3Vr*#A;+)`XZguOw%&Th_Tg<{;>=)FN8kefAO@-Q%iGu|>qCOH4b7X2F&Ux;gqk zm&Y>yzGuaVVJ2yV$G_8q-hYyyjlR8^YV;#{dy?{V*Q2QiQIk>U^lBuBJ7m#=^g`nZ zY)hs{Pc0bINW0y_wZaPX$eMCAXCe9EhPj!vM7oox|0s!Ip(d1JlhT562GK1x_nC)i zt1GN0lHjkA#3xninlZ1gN|TE(f)S5t@I7H{?6*Zh9EET|07?T|g75`xRA&k;Tx(v#q8^L0joc7@CKX*shD{$%$%B;-8PnD zH%h#%nG8T(Q_xJOniXQ_^;vi+LpFAhw7M|_V3CxGo31v zMv0v)*Z>gMX9?BwCFo2QE0u-jn_PYfkN`Ds>zHahIz^pVLTB!h&291sGm|RW`qG|D z=UAI-@pYumB>$GLs?_(iFFyA_$$As8rq8T>SP+oCBqRt?I}jm4SjrL*vCfbX5MqKP zB&Y}ugvAiWx`4I5?F_ONA~>w07F*GV}KR z`v1QB3AXS5y1wgD#E|?#e$RQ%bIyJ4b00wK>^j4_#3TjZV6G(rFe$fS+x5E5BN*B* z&0*Vo;H6o*TpkcLb3@BKZ;@}1PV<4t*VNtFnQX(AcY!~Nx;2ag$k71VTgZb6V|vJP zM#5$+XcCwgV1wvp*X`b5z0ASc3NR=jPJ6CO^%0yftO`p>d^^knfAA3ye3_dTy_)@3 zA%<1AfVU#4s+JbII}zno{bg(ww!Ku^&%eC3O*e5Y)mYHm?YPca$cRP+=0a-;pXHxP z&~eqqQ=Y1{a!{P9dghR|-*rLMCPB78XCl~|jnJO3wp$gQ=bAOi+Nxb%@I-8zi-$E; zZ^raol;q#!#BZ=4X{QyZZZk}lOSDZR%LZi8K>KlPqUamq=m*EQ|ntLkSd0+;oOCylm<+|l1@ zr0@}{Y4oB1;~0VmF!Za>HhKM!Qk=G}j4HNG-YHNOhl11)fVcwp-`Jjnjzm)M99*2a zsl)z3X85=;)j9+93{|=uh^0K`1<`a;y| z!d)ObJXmKm)*{qqBWhpGps?~-JD8G~l0Zv7oQx%K|V z4i-$(vZYCt$ns)*Q5A(+n8Cmal(*WaL*!K@4=WVdc+0AqkPDBtq={Z~Oig!b+!sou zZJ^yE^70-NCQ{$zo3lU0&zNtroa-av$cZ9Rib+TM3NpCaB)~oTMmIF(1sGJ-ee#Gm zjE#@0oH~6=}gQh|_#L)D-GNhp=x|jAVnKk2E@b zL84;%Klbp8_%C5oq0zA;JD7Pfo7*8}oiD%E>3GrcO*ii;rqM~)BZ&~2nbpQX%6gG7 zJG!m8P1?kh2{Ij-y0LsMTZWXUgL#SRQ!G;3u+Ta&T(dIE3-T#yT2x6C1A;NQn>tg* z9JDC_5hNmsfuFPQnyB7CKd$*}Y+RR*`@noft82hOfn#Ygtk}$MXJt4_3PE~;vo2pD zokB1Rc6?rJE)=EG3&Mbg(QgcXOu*f+M8BvLcK=G4ZE$eiOmzfOQ^1++0Mdu3Hz9h^7?g&S2iNjXeZG;u0x4I+%TyN2WbcdCpzJk2S&?yq23kh2vE}G4D zcd=`N8Nnv7#spv_t*rR~o-O%OI7{+vA{QG8V0CZ?5NU^$vmFCbZ>R{nCrLfAge2Z?BXSD|MJxf^3} zPvDi;M|iirIuH*b+$_nlJLDfX$@XuFo6NA+R0^zCUPuY^@x4~hxy@8e(Nr~#=|aA0 zPg2sLn5km?iINx}p5=RUua?a^OKTIa+eP1)z$|Z=Ss%ABaGvh#_Fa9XV<(F0N2?!d z|LztqjkoXjs7~EAV>34>P;r+}tP$mr{&tewf~>aTllfm#lVW||486fY z6&tPn6T(S)(hT%6+6lD=dy$3)IdS~Pj5aTsYqsaD#6XbuH4|;ox>!`J6S5K;1&MoC zkOl>@PA$4N?gEo=gGk{q4n$G$3@a2(V1L>KB5OSxS{tw(N1dr*1W^Jx{Yi!l&LrVk zk-Y01cs2q>wQ*B!9l#7NSd0P_9a`g&L+s633W#gO72u>A?xpc(A9{&=e9?b*jGq`N zqSMOjDf~?odO(LaZPcBPrf;ItR!gMiVtn8NAWY<`K>@n+VRFlD`=k7%%K@kMBmJpo z2fAwQB{^C~z_BENwUsJqLS&)+?9w&Klc#|F#Hu!N8&|4TvBTtxSsU}TXUnrw%g-_! z))qvxP$`LJ+Y{?F66N~zf@GVZ{JBD-vezW>&kPB0=wx4LnHp=+O{tk^oUiK$kg6RK zRIx-rDuNum!zvOsd2(b@kzm}`E9SD%zh|VnQk9_kF}Z`rGu!*HwXP~nk>{QEY1@>k z&T$kwDV!F=0$bNN5D56>6;x6X!l1qsp1Z? zi<`-|YACHL*R##cSXbxI>SAw5(-u5$*7I8<(6r$~5*y<&F&I>$yvbz!(J(tk)qG;` z3tlOH%=0uHn?gw&t&i$o9~r=8T~>T#G?y+Wxu(yL&K`rurY0;5o+RLvWlJNXtGV6` zC>7i}AhBSmp{YqidAd#XP(F#H2eJ9Fr23{dBvmY>cde<|k}}Z| zuo1Kjmo=WgtYlh1<$wa)B;6>5oa>N#LAfR09 zN3ZE*36Lw8E+~`3=KNMRdhMd*rm&?gg7#6!?~SjgMN~P4>MurVS#9}}iI9NJI@V=l z^h9EpWKY}GQHVcbltKpIY@*f)2@QjCAUZ50Z){kD!}@Fo`Bg(ZDfMzha%GHwsh`-cXy? zr_v95e&D4mDgVMw=_2CL607C45~z0eCd#6UWc7(JMy2R;P(I`>OL$IkExbpq<;X`7 zGD#eC;Y~jNOGbS;)&bD|_o>yX*$opGq=6(wC z2iQp@M0DXI|NM6k3;64uEnwpRiXP8mVv2fT|6hwJ{KdCV#{$BZs;Re8&m zIRAzS?3u85w9GW^@@Z+V(}XT-Do<}b6BF9O^w%`f;M=<&jD85IN#k|q(9EN?nhc5A zslbPj-qdNC{>2eu+SPW`Wraqs%-04YBX!>ep%v4hgw<#~p1b0++{ZLjnzQ(ycR958 zHOGKT;$OU(5}D_iN_TZ!81gHs$?dBXRCUHy=J3WJOoSE19kg>Ocwen9%?ng$Yv@X0 zi*mE8EpRsAYr+e$YLHwO0<`gp9#H*G&7tBt4Szc*off7xe|wST539TQVqyp14}ex*_TgRNky%qdOi< z1j4DawAuW6v06L}yVq2dC#~vjx9nq*c`tXyQ^HzF_#A!4U0`_$u?>Y0=|8RlfNbbQ z^(RS?LavR6!i8=&D{eBOBZ%^%M(#j3Xu;P*+cwc$s;H{5Bu~{6sDM}OZRZcgO0HS_F|nr=5x%S=eD5Tnr(H7*KDt1+Ow^IV1e`1F0L17o-^v^ z60=S4vZX_Q(u(**Et*R5s~k_yy+GO3lEASb6_&*z`_YmxukPYsSLFGoCaHL5Rz909 zsCruRodXVG9Mjl#{4H28vZ=i?eI|UeCUw)U_Sng+cA36IQWUj%6%$CumSX3@VcmW#DSCAoy+^==ci=4c1WcnvyC>$QEu9vpU04+CE6~ z^YYf!DLW|EZnydKgH>m$>D*&>A!&J8(<>G7K!q<_o0+NpfqUe`1&3&X8UOaK#1pvf>#WSduR=}e_c;ViS#B%OKG zjDD<@R^ul4j+rKTNwRd;PwFF%N%j$;5}#BH*GVF2UxQJHG+3=2%%mv?yX+W zKGuK+AvcMqS!Hg&l6<N`0vw?-|3Rn`TE}-ymvIgAPeB^ zYiGBq-BT;{4Of9Xb3o4ZbM*$sI-qSxd9tR_Pr8Tp*$+F5dgJ`IfN;oBd)erM62%>o z;`I(5z-R-Ntwcx)h%DGpx>0OERO-g5$AIaVHqs}Ab9HNnEBgzQe}uP&LK;cO z!qJ%vkuGRM3XFj|ko+OC0lPse5sp4VP>iuA>T+D45;mJ^!>fo1IR#C#pQa- zrLs7;mM(uuuGxD=@M6zipNXg)K4EF?4dti&I8sS|WruH5Y08V%cNe1hv{0AugDF>d z9Q&};U9p|girGieW94Ei=PS-l+JvF68CUgB4Bq|A2HglbsF4Xg zYpp2^UU!i2qL-;A)&7XF(RAdra)ej&i*(J{&7@LvHXIXuYSW-+cBX8c&l}yMpJ;#U z0;A-#=?=?$d`zX4VOQv< z`<;*t2v%5^-t8*r|FHLwcdC|lQ1AL*Vb*7Ct(xu`%;nAhH1 z$#p4G1BK4CuN0KSRS$xik0Ukuk&C7 zKfaw>F0j~JgYrl^1&3@%C8Vr;$-@*wZo8)BQSffN37>0<BvA?B@N*wr+FEc^h`a)taWrnOkwe4n@@!&W{Z*rf#9liZ+%eOA^fg-S!eg>|O2P zZg=!9->afZs#?$Am`vGTlJJe`V%?0Mwis+v$E#*89{!hqk)NMHa?pjp%sJ(@$;-~I zHffAe^CoSb_d;WQ{x{AwGoPjijy!2@OZ&{`aY5)4HPfpgRcPiV$fu+060&Wtxvt$k z?!|Y1Evsq1-qA6C$|omW!qlGU4DoN-C>&cj!(k71Q7)YeUs`uC?J$42sL5;o*fzQ? zD{MKyDDtBnTQm>M=57@5HL_Huz3`2Fytomi$trQ9+4U`N$)kXVwDb+%*?Bti-K3AL z_mv!TYYe9QpE()|7KKY}u!Bt7UQ}Vep+BU&VHD-I`szF?xjsc@GrPb%X;6%n2PL5B z6sZ=Q#F7Z;;4Gq!2>cCLlvIi?tQOL(0_|VW@UpJeD62?Y?X<9Lh*w)}eUCfi~u%29zX<(V>*mK<~vK6KX^#02u$z=uV9{Ls`s^U^z1FQ zGyG&cKOiLl20Z!5b8N}gEbO7xaAEKu2x;-51vnajt0`IS((sf| z6a8GEKr+RXQjwM%YVU&|Ge9z;4*mZGBr@?(DM1VSop*1g4tr}mmOo(7TTvivZgxC9 zd9g3#!d!x#)w)eVVp`}K&t=N!?iPoyP8p4^>9f}lefpoT$KDoxZTdWW-|lp`XG6cw za(W{Df%AFoz6I5yskdNdMH5wAzF(%A9O(d)Ezm8fvfPKULDN&W8bsT-J{9+wSs z&Z-7eH2=%qp!@@EFVerceYEszTB*t{cR4BEA@t$A;lw_v0kVEN$_@K6=NaK#nm{f3 z#BNd-ZIimDxhXc==*0@fz<_u^IU@C5ailL5{azjcR^elD{nlS|bO?V`EnRE7M9LYv zkf;UihO~un=jmjF3_YoxkYC1*2C)pHVOatwp7$n|@y*lxxqd==_gY}MnRqp7i7cs< ziKfgH6Sf2K`3Swxm}6-R58yz(cbZV~+htCMOy8ykNs_&cN9cqoQ63^ao(lsOE_Wnj zaEztRAv7?xHTpMd#Vp}iBrysCuF{DPXN0G@y7U+zS`zyqq$6*JXVYUMD#Cz@b`W%6 zc7DHvaDYk3slv}!13(Kn)U}fzMdCn`#Q_{5=-}-%l_e2CUVvzk5G9Er0@;CcxOq3P z_fVzFlZ#ustZlxssnAuHP+cAWYI3=thRnS~MuG_h7@Lrbtf=Dacf-2A=Kfh4-x>eM zmp@70`NAd1y*#=1qleOa&%$0^ce^ZfKz;Vc;@!IG+xuVEAIVy${$r8;ttXnE+ZWw# zj%hxy6&bIK+;=5*vV?IVTAL3%-t>9TQ2W`~>t2ly7xuVIGZQX<&Diyy?|-r4yxL}7 zntbbt@yKIwA*1W_))Dy12+n!bbCFxED!)vQ?jJZ3=Ad|>>d83) z4^Tg~P7tTEe(Ki_l`&dHT1uCL=M7D3&&b?!Ul5p9FFYHc-BP7>?XQX+f87;%J+vL; znTZu37n9&$HE&@Fl7j=lOpc|g27;FWkYNm5UtJ%m$gYM7STbmwq)pD*L4Ym_&6QCz z5NRM$f220TBC|;rJQ78DWDeEFmM%%Y!z7_4fe1xh2U&E&j{=#Bw8rV( z*dd`$^$UY1>Qg?<>+!LdQVk#S=9w+MG|vL3@P3^(iK%l!fw^?{lHE=Bs4tU!;#!7_ zxl&tAQ^`<#67_=D-SR?8o6(o*+HZ`m@Ca@9FJ=dAnP}Np*>#N}%~(piYp47v>*cdM z=Og-^N54+j)U~LGcI2&_PKT(JA zo*LxI{c2lAiMB!Z-Xrz;gE6k<|A;?+qNm?qtLAokWwnBJ+s0iN+lZ9hmW_MbSgSox zIAZ8te32s(E-o-Cs)Q75KPafZxb`K-op8OX_!N(0XRp7 zNZ+FNP?ik5bxN(hxz#ZKD=G&!gO##igdlK$y1GKr(L#c$6B{5_LYA5wA26$v*ze@& zupE@^0qW6jG)C$hOs+oZVVu@d!aS_S)A7$zN1WoRZkoT~_1`~HYBRc=;&)J~iQ&vm z=lo}SPLD=W3>+Uh10~xKZNV|RC^~&=cC$}nizn^7UivlokLmI+JAY4YjCV-z8n2jj zV{VOT7q?M_=`CM1{7z99@uAY|d-*35>r+GDnqN2Yig&&7?M0dTjhHpjlZyXGwkSLB z^BKd}raO9%p|F8o=U-|vhHTnf#wt-IY@gMm5w zgD1HAHS+UC6T$m7p6k$T+_?1qwkySf^6RN=TI{D*{xc&Lj&x8}oOP0+KqpqGounXK zdYq4R-+0vF+q`UMVv_{zbO^Utj2t}_%D_Zj1jA+lfuV2?|ErL)b>+T8NDnb>$8!6L zE{df~Z}4M)G&1bCYBGXMW*K3G!i5^DY%(5(A8=aBr>s@76843-g1NL5ztxT zTyE9ae9|OBN#X@W=#wLWpG7WMxuot+j45E3=w_ilv8fw=V&L)1xno5b{UrfK`49S4KneO(q z_h4{jN$73-jtQ!78%+L&X90m6kgM_`I0?;|F3DwGqE|aBo$Y+)+hRRdK=DE zyC)tKT}s`_Qx;UhePwg+A8!tFOkbQjxc9~{*FIrPkK9Pq1&05{`ULZWFIyk)9#`h* zB|~>cw!WSSqCZ^kfPo0U@aRdyu(hxyExJJJePr4W24bsL<4mcSpRT#V0QSPF&m{{& z+_YYcS$Bsl-+~%JJOk7$u*b{-7fU zZQpDd(l*lBOb_F=uycTMY#dGBj8SEJFbXTA~&%OUCUwQv{z&Gr^Mbk6e{W-!-sVAyCYa?EZ-NAWP__o*Co4G%?z8@h=J?tcz z4Dv>sPD$8*!rl>m?D2t~ktr2^cg8j}d*tF4x1gk+4^7=!*E5o5KH2MV3!_eMZVCvy z3p`TK=E!00L<}ShJ4fUL(h@Z@<2>5Qyn7SMArIpnV=Am?|C@gDc-nUM{4Ndm zvGwml)xW&vQGEQ~V!|VB^0}&i{Q1DWKWxa+TQ%=ph*9HgOw*|2C4T&q~mBm*q}H`7p44gh0%eue`r z0Ii=Ws1qCsM+Df!KGJQvsYVXb7=VEnqpC-q4C)pO>r~xq6xgPRAs)ljMjc(WMWQbY zO%88z#3$C58#Zr%qd?J@jn`w(6CVL})WS`)Yc%#Y={4J!3gi`$V89}{#mvDI4(4i| zVW|M-P;0*sLA%W?h#Bt7T-?vuc4c7CPg(!^Q0*EwZ2HQF=R9zIq``DI-D;_^f}yrV zF=2b9;2*}>A1+N>Nf~j^nLn7H`@@04zGfSFr^}{LyQQ~0BES37*xM1U_fs;XT5qt9 zu1WT$RHbfSSHqGxWntcHK z`WyGc&HR70zOUb1(72TQjb^+4F997Rif=UV=Ef`JshRUTIS(cP41KaD`s|78>)Ku{ zQCcnIpEWzI#`j;M$CO0 zF~29wyl3l4_MybZOv4=o|tJT zge(IYqcFQ_Etc(%D`{^OBDO1turWm#k0qI}wToP{3^glKR19Z|SOqcqLyF1roXzMO z-NdOb7YQ-7C8ZuivSi7&>DmBKK&h4-DL=P+2p4k3Sl*U)mfVn^l+O;<(yff`E#Y z2S|q~^8G_(R}!~?|KM?qt+=To#71ez0GAs;dg7&Os2Xt8Ddt=u6Q2p6tD5|Zh~?9< zT!@^5~!~Hcixgu8fY}n_| zcs2L#U35;q;BY^tXemUKsyFwFF^xyNVJ8{Y+F)wJ{M50Xk`@CeYj&2u(Sv29C{6C_J{n3N`$T@4``r2(ryyjoK zG7aLzb;#9)_sCs97{7Z2tg|+aWIvT3(l;gJY^V#_u(#-1kz*3+%ER3Ov zWHg4MYqF8jG;z$?RNd<~>!pG^POX^IQm$>PGDx{@DY4USU$*WMetB}vm7zYml)AO) z+0E8#sn)j6oemOLC$7622(HQF&t~jHO--Wy(qV4C-0v5~$kxD?3l0lA_I;oHK^}F( zQuG7qLhp`DKpRbq7ysv_U1_z-v7W%HW9eg~@3lAfDIt12B%bnun2itFK$zPDUJSx+ z3q75%>_}xAD;VjH@ruHal|(>EV!E|1q``CvlQ}ClQuN9aHzQW(m50!wV=ANg@D6=-+JXjCW7OM9DD3v{r@( zzDIw2s?!JBu1|xKLuu?GmBSj1xAes6AZc^mRo}t2DPMfFx1KUBd?@xQad2Ks-*DVM z+W62rG?-atclaA)u;9BlJgTSP%PZx7ZKEH*NI7c1F)UP`Jzi#01&gTa!h zwY_!vC?nL+`}8B_as0O{_%>3+s4kbMx0FS5u1l%OvtrxN#93DvZLM7$lh77yBcvLbiL*d*-6TE%K2RuWstTqJ%s(kU zk+Hd<`!u6t#dh~h_1Tqu55Ee`OEZuDC`MO+?t;28;oYGq@yNbe>R@=TNyzl?^n4C6M5dFg=h#pqm1e zLXg+dKdhTh?i4W#+%s*C6&oW#+E!24v^A6_cJai!EWJAGleXEsZp6zx%MQC9ld=%cv;(1<;Qt2b?x7-s+cQrZWD)lX*bu=re<5Vmu<0$JSI}b zKIxENXY&(d{Iz27PGo{Up5kcc=kI4mooyaFITx~&Iz0F=4AuKhVHNS9{xpW&m9+E} zacAQaTsG1^e?MbuS@S>qtyKBbq}*9|Ix@BV#?b20+$pJXX39t2 zF$y4Jg_6a=~^ZceAZGbPS+_^06CRX6^e|9>>^+rDR_jB*25 zHb)dk!#(o^8@0K>}PX-HooJt^SJWeprMdU?7zia z5xYWX4Y`PcAUCAr34_B(SV*e$CJrkH0V0-{knl9O48gYYi7FJV@T-z=9xFJAZUqYh zL>lZY@u32tBGw1T9WH`aqWs1x;uu8sP4`l`3eQ=Tai62l0F!0b7NQBDK%7aFnBPZa ztOZp!3ktNpbbUjEy{ZZ%>Vl)r`cAj>L#!@FB)BMhm#Z<~i(uFITR=`GrPP^PDxey} zd4Le6QMkkz6k=7wWA*;y>|h7T{uIojnNQ5vAFaP*aP@c1a&PsF-4RvnaqcNXydv+I zcK>oimEzXsR!_oOX7wfzW+{Fk8-k4b-RS3=Gi60F2)M-?|3gvN1Tk? z$yu0)Iwrd4cF*!p>OtDQiLE=<%s42}{Ze;%8=^m^?uo7V z(rP*QGigXedJ_M^Lf9LkjWZcDXC<0bcRYT0GIS_@Z~2o84QrMw75Oi`wFCDBuxs&RWr>beOin^I-81qTreLq1qP{@K%9g8Rh zfhTCt)bNTS_6NdQrcd<0y=>?cj%SZgTzjO8tTguKB2H9sHkEdVd{4^v!CjB=(?hWZ4wdk1i-QuLzq+1jI@iXuJ zf2M@wwAZdn%J?KHj^uRpy`rPr{*v-{**b2AMNq_bYY`$@VYE)M+2;J>-2?O!qxM{Sf9eHrDcc z_k(uIAB2CG<<)n;J^w1x2_9rzD;RgH|pEEKpK$C>bDwfU$bgq>eNw&|pT@2Appd zF%}+!#ghRj!Cph56+H(4uB-akseH7{IbaqxYzY#45bOsdkwOuONSu@zk^KAfm`883 zi541hG$TB+EPUf|m&=)=xl5t76|cLxbNVADV}GB$^T1GxLe(=r3>V zUGt{P)BBW{5AI%>&Yb~x{H}ZRcue;CAAii=pC5TXx`^u&`-!c#Dz1xc1`Hr@3g(lG& zq_$J#T5UeJ%{}D>uFR(=^W82d;5>d{9qF3O(~0L&eiTM^{B)n2;-QK+INsN_H{`&R zxwaHjNou9}nB;t{kJF~ar!i`6hr_kyb?ru4p8f57aT}49IS#^WGOm$_Q_SDl|m9yKGC-$Vh|8$cq zw$EhW-&=duY6r>>T*-2NyE5hJN736A+z7kBB$|e^GxO|r8*D@M6w3mkONiu|Fa{g~ zh)69!B*{;IvyausYxgr)U=Ty-{iuc6Xah%3i$1>s%=Z+wwwda#I-AXdUKsdn31W>^ zw;c+MWeTFyfhre@?dBF^Sph9af#KZ|Q2kCMokf_X$j-vD6C@jd5hXEzTtU}-9@2FZ zkdH}h1J{NjID=8BfeBNkCw@fKHaC=fPD-cQO1Yh!ijRc3P2p86$d06*nBQaPkQF(R zZJZ$z(>Y-&E#5!w%M<=_K6TNTr?9=v=Q90|*yldE5Yx_R-JyTw>Diy<03+IiXwBuy z!|CA1ZY$j>NIXkPuSzl`7hD*Tf1|mk|HgRbX6}>L_w?T=LIS!~V3cgr-yz?e@*#jk zpS_hKB;X46wH5!fCu>zX2W8FXsq4WR^#@GHmp$GIXk5wtrgbG;4;sp zh#H2CP!O~Cb0_`jO&&Xg<{EOMtgbt-+TSWYO@3{)IYe!!Pv4iFa^z{*mT$A3mF=-R zoaicIY_JrjERiR%2zSCB|5Z5*yWB!89Wnh++oBsf*u-R zu?)mNXq^JH!a}3P1U>vd3_Perj5TuXX|;#UA%Ux;1(~Rcf~W4OhCD;2uoPXzuAVJ& z0I!K+2jGyUlKe1)yzOCpe(Y5WC*ix$Y*h?Qo7bd6Ao=N5=pV=jkzu^Dg znsbY;8&aEpc$g4sylpLBl*QhD;C6Zj&ghWDX_(W)r8?y624Wu$%vJ33wP~VrzbxKv z^T*5GcJ$oIi;-O&9n6c7T9vNo^AEN`?*&Q~qK&lg)SJ|p)Y{OJ>-h-7U6>GJgnpbd zy?_!ga!)AQBDUr8Gwd!x%UnCurT*c+)t*I7Zq_Mx{8cq_W|zYaX1?wZ3a7>XKizEd z|52(sZ6*24-qm=)lrN}`QU%$6UN+46>BF}d7iwR7yWehXq&HyYtGD{zeCy5l={=%P z>Ss>q{`mcqcYA#P&n)R$K)PVbZ^-=z;~58qW%;nSNP#TGf02aD!aaSmRxN-pM4YkTw~^T_XOMMpq4qC3~^H z`%@=NKFrzO?UKFUN${V$16{WR9(xCQ#(&Q~eSML>kdx`eSQ&Vy>+67D-q^RWpf29~ zsjQ-SZ)msUO3@sg`A@WGoxjgca}N04@k)2Ma^!LkU0wKxzR1qR-oTn?y?YlIE-Bvr zwDp(5eG9S=3Op7{J{z{Z#ZWcxcU<|?jkKV$9XY%2&aeF3pPfcukl%dg{@lH}uh^59 zdk#JQ%JKEAfqq_o*)~EOZ9%OATEvuo^MYheYWBVBDQxNSH-bIoY0_GO0ZiCXM%0j0>V}r=vFJpiAnL? z;-P;1f*3hD^iz?{s-aRqQGgSpC9^_{5BqCo3Qb4J>4I}@*n{x1MF|{YVC}yaSHb_Y zlMkkiqlh+yglTnh7;3oBYp)AaevwB%_qt}hR&b@kCYC3mX2m`#d350|jp0n(+JMBf ziT?h6TaU)&c!tP`EM)PvI5 z>8(4q?rWEWWZD^TpX(_-<-*vIrs37vP@T9kU``Q>W1;f%%)=`{+RqiV-dL+~>fbk_ zKbPoeoKLTx11{e*^(E)Ogcs*uEmi&$^t;rpgEz{fD(%=!+awPrBDa;CKEjKr@ZPa? z8xB3+Ea$j4?Z=L?z6t2K5>{-dkJ`%&=8dc8H?CZiZ%TCGM%qqnjkKDu`C*;4J5ITl zgN}RKZ)`r!dQ<<6v-yXuv8up|(W;c`zxCN$mC;j)R+Z`fMGz6&b zg11$RS0Hmzu6iz9>aYUCy)C8J*`+Qg{G3cP1jveTnm9o^pkbubcx|-SQD%I%>F^Vm%*l~))xUq3{UUw46|QKN~+)4`$zGXwP#_J`MR zzvb{=)$j>XCbxSe_lMhmv!4C}Kk0ik^+n*<&J#b9x{JewoA}9F^IHd}zvP^B+F2fT z)M-5ej;%YQUbXuRbNb7$HxPns4Mjq7VBX1%dr0OX?>rjo!hL!F`}&s(I&S8ZV$JS? zjuraDYhRrZ{~ixEuE>9C`GcjDWBKVFJfd7d#B>MK9wOfGc)@s~1@8dnaZnxjXdXhi-K3PXLkZ@9?IrvG8ce{T7`-h-Z*BU=pZ zy;56zDea!DxF+!J`7J*zEg6GWns0W0t^9DZ^~2hn8}y$e-{_R?Ul4UlKGUea>HeC3 zZ-Miw?4K)rbH-lhu^HJvpO*dn&YxvzmAwP{JLP5euEK?xHg$1~E(yhcF$YKkiVB37 zMeA|JLf4|nf-v!)^>gP_Lrs|yu@GMq z3W25Ahi9lry-5FNt(MvXln(QJE!i01=#&+L;;9CR1C9r53Yk19_X;i17D^4K;sn#9 z$5+pCkHwPhbwYS)bU@^2>*(V|b#g<~EcIdOcwRfBTuD*Hw>OuMaQzQ~$3Mb*>Qr(o zPE#>Q5W&27HptJ0f*K$X5W3dQ)$iw~Z<9iHM+IdoA1QbWc{69@GM$?ud67<%9}5%F zC+pWX?qCwrA>oEDZFi1m2kShgiij)y7{F1~tQ=W!&YY)C_`2bBEOv38)Wc83=R)+W zo^w%A&pMPNRMAsLCXESp~ov2EAnuzdkP*M*hf`B!W&nQl&DO(Ja)s+aC2x}{>8fvfe z6>bSFlf7*Vr;ez-*zJtc)0E|S_v0;rV4Q;~UOIErd*k$UQfR4?y3A^$eI#{5O}%vH z78gUeOC`CYJGsrCIlij*laDspJ5fd)u>adW^dqt;y{ zcWrhS74m|^cL2sHza(i4h}1T=1@B9Fxti_AH)P78WhdhKu@uL1o8ls!Qog9=G*sKF z_K0eQ59YUW7N#Rl;5j_Ndu=&H`(VO(P(5>Aj4Gwf zW<2fg!hijo|5Ie2_ZH5&fg{-t6ZU%Q3e!cF8PuOfs5l^B4o4x=v}CkE;D0^+|Mxq0 zde|mVs(#(H7J&mST@g04K$&%)bWD?1L3eZWPD=_)IxsDUKqvrY$w^pRxIg7aZb)m0 zeI}D&`p8j18yB85m|KaQg@`4h1`r4r;Q|Uakgy^734Q1=|6zWh5*9(adms}>q^b-H zZM7t1zJBK4e~5Ep_nnH{+H~`V?5D^BKSd7DbYC&uiZKqG?k6tpTgdBS_p-E=WU;GNrCnQrxTVA-xc zy2>dwa_ccSQ@hy9u%!*j?$Zk^SwCyGpWP{)eHQ*-!b|g8o3Mr`>!>L)CWB6H-;njT_;4{rh7F9uF%ik%qdbH z(&*mn`=g;E_%7$XEdCAY`h`11x%~bX52#Hkv*tn4tM(n#`ZWeEgcP&v3a3qxj}6&Z@5^( zAjpZ8@{7v8tXZ?yd-@|)iWQfP`8~G#H6;ogbS6+eb4x%7Vr3EFzu?xX{ry(opLTS( z;%2XT=eQu)D3WKj z%Ha`;ToX<@Qw$ghIsJo#gpN8Dlu_~iF^C$-M@BdjMie(uX2e%iE#PCNda4EiGP+)H z?_h47kfd*R6*_ciy;A2c1)7uz2`3JEc=*U_jt3#Hk z7TMwF7Kjq>E+*u{=Wr&uiKOJ?vg*eb&&W5BT*X0Q@%{+yEK6*uqk!{)=>Ip{Sb!uC zd2@gq?MdZVw+3&WB(+`mbpkl(Hxqz+3Wa_F(fh|jSfV>D8~|=i!@vt2(zg>kurOQl z$&v=jXq!zo;hqA6+1SfzDILm_z!r-OlP2e(e<*|Y)N$XHDsY7jDVUbUSII$COJ6&+ zw&LN*WUYCSY_beVzr<9LK&P2vxHr$We7SZ=AV-T(b4xFw2+(!tOk$-6BtrXw9cpt_ zJPM55W&HDN7ctsSH*#p5DBwQGq?pw{-1MyT<-FbdQYTYR-62#d<5!z^e9hXQEt?n8Ak6PMr)AJuJO363LOQZ99ufDR=Wg|yCi$-%`Hz?Dg6$PYBZpTRe z&n^38d84#{%XSqwzB`k9d~N9HnK)bjGwJkH{hdVd+!=`G;f~134FYC}B}B~>JI=zU zB6Pb|%AMuFC2dr5+NSzoG)=9Lt#p{o*MbR>!x^)0;8F=Lj2Ev)ZRAY0q-!urqI!ja zXz0d)r9fN`n-%3)7=z1WSkv=93=GOp)7$O?0L2yN{537-NYqG966jTp!=Dm%83O^&sLk z#8^A!ij@n|`fr4v!8zWv|1_zQZ}ztdx{-oLA<)navystWmQJ==K2ZuHiePR>(*ts} zuFYk^v_Xf0PW0OZgA@er2|1>c2={VSZv}Zt(h3#H6!~{>x@sOil%2P0wmsB)Q+l7) z{ihT1C!?|8maB)TH7FY&pz zr4s?6n8#Uj9aJfcw#5CvXAi$nr==DLjZS0bUm!>%^-_TH}%pc*`t>WC`$e~r|fJO0GXqyKBjk?4JNF`Z_ zsdr&?`S5;7_L29YNP%ch)zNg=#M5pwXj--Lg*Nyc26Hb!XF~GBkTDFpM^TmmTvQF| z2+TG>#4kErfI~;#QmR!`s&)RLfcKb~0bhB4%K859-{1V@4;;?2aqVYeuHR-a_CNL= zOKA+NwO3(~OoIXRG6$++0oc5<$*kA=z%z@<9dZM4jNDI)LC@TsMbQObo9qNwH-g{v z_yOYSA({!T%w}YWw=2>_Zk$|mU)n$IbL6tdf7QB*J+%$u95%N}k{w!D;j4?*d4y&= z*AogG+;TB-iP$BxF1W>HYC3K#xbT+kxVj+N%y>t#r@@0^SoSs$6sUd`6ksf)kj0j( zDXlam5&OgroLvpF$&=v(a5g^QzLsmDI)pupYv8@eJ|tWB`4^l4i-?U(EQi%WEBKZ? zHwJgLpsj9t{TewFzwc^b_WVU;!{ZSmssy+Y4`SoqO{Js}J0Z6%kn4^Nyf*1dv0{8~ zPn^uV?xM4AtuY;!6q?EY#E|A<$V7pQ(O{YtEI4h1~9GE0L% zF0;8MB#Y&TS}y}k7Eo9;ITPC9L&96o(S zX_bW-#tPlPXiRgY<)U9%V01)TF%!O(NCQ~@!4Z{xJ}who=kdPn9_6RbJU|GJyveT^ zjxx#b$noA6=||-KpLI0*1+}POH+PpeNd&j0P(F~u;K322OCEZBbBG359XE0eys*hy zOqe(fBy48$yFGQtO6mYTNYCsIYjg~;4P6e!L-QRq9uTIMdX8_07E08Kt;{HqrBx*8C~dKERlbBnW$=)#QMV_?01y&M4qy3u-ReOe0~J&O8aD>)VUf_L{p({B631&3=y9 z`s>{C`XIiX#s~dVXkKA>}b6)QhlGd@k-gS^cfh9H$Th?T;%V_m3>qo zV0yH;^L*!x?&*qDfgh!qm@PO?iM}4?=@V5MDL0w15fFnQ8;I29v4O^L19F58VuT?= z5EVDI6JW}+Z?G1Y`)2uqSFGOo)eL9#8Gy2|5eVRgTTQ|*_%|Gimb?f5Sh`(F{DH9TKra6;a`{8l(ioq8I&yX5VXi;A=CdzNKMjUwp*)C2%Tg-p^SP?rKpV& zNrY$DG&kfY#RTRL(B1_bgG@Q(N47{Gaqv+VWqB#PxaL7VguHzR>5Qy6QJ_>Gu2ILa zRdKwh7pvL25q%ilqv2}MGI?`SYeaH*;Zu07!E;zRkN+!lyb^&qS(A6e) z-0}yfU0wShbg`sdWoOTglvafi4LX_I8tl^T z-1Fx4@T%TP^Yybk9pjr)E6PmSB}@!rr!$`!UA2Sad_>S>?S8d=OOOR0H(QaWAugvA zi&9qT0@^<8Y(BL^+ri6^QAScNTYw_NeKU7D(ti@4+rs=NCVq%yM5|i~zG>Ap zlF>{=bDT#GA-vz}g01e1-}W3FBAA|J*3Peqjb&*NwM>Y?(}m&u?<*}k4d#aP(c)1p zdx4Z$u{z|Y(w{FRnvoITSbjP$ZXcAxHIV~)^->B#?j+SR6K2=vSB`i&&(zt>$!s1I zHUAcs51vsX@|2c?nO_HO(HAXaGx6^OWWA@eZmCsWJmpo`8TJ z%_Tb94U<^N_0jR9Fvy4!p&&Ck{OF)p1T^F>8meT%g+u-O86~RMH*cG1(S?)_Y);*$ zGRxUk(WR+*bC2*g1?9{MhOO-%jvP$tBr9Q2s z1wJ4w&<%GDJBYp-AX%RF{N|E7{Oe{UI1owhEw=`d?3a=v4qgtfZ-oQfCnmxThZ%`T ze>?VYQNY2LOA!61)vbqLk+EtltBImz4Y4+pSUr*~6cay-SE04X#IhIyb|O|%M8>~h z6k?mHlxHm{46UvaZie}Eve+uLb}DfgsxAgw1`9nuYjI(1JUP!V9MmspFBcrcXdJ|Q z2+;zJP<_@AjR3yPs5s*;rs)#o4F7sEbOWhDq!h!OPTiM!?_;HDa(~Hkrkz;u<%^NBP@L8*IMv7_PVYb}t zdmk>0gy!07I_1)N9IaF|LBiPsIzTwWylfohYvZqFby|nd$)KbRfp5#X@|!8b!H|1M z(nE54h*QMovUp6gn^tWWOAHTcdCIR}2z8bOFeJcO7y@gAoNTzym`h31P0B6z*kT9d z%Wp&m%es@r!(PQ};Yi6b3nJSDtc0rer^jX`v8lq0<@5h$3X)b?g*V z=NxrrguxV*?Ac4lI!%KqTeQfMBwMo0#F0`$jAgP;W0?NeXY`!s`+L3q^I{A>%l)~Q z_qE*DeccESfRrE$TM|x#<%1FYw-yhc)IoR89eaz-4$#$74cXqy-?$Jq1L#1ux5~#u z;j%hI?M`tj9Sj=W8Sf66=OP#I?RdlRXNTIcx4rr&L}!O*TL1T1#k4{eitVasgTI|m%V^n#EvXIn(lc4cE(&gu{fYpUr@~YUI#ix2NZh_*I z|8-|W3hn=u%C%4T3Vcr94X2-gjm{<2cEPF)aMvBcbA@(VMK25s?M!c#8f4v5v9pn` z+6HYtPTL$ztUk}WmZzGip{=~ZVIM`jxvLkp9_{_bpwhbX*65*oKid~vTmL&H!Ps7E z8wSv7&#r6>-(yhb6Igf@I`ade5TFRS>i^mnT1@q*yt+}CWqIV*@wdN0*E=fyRM%K0 zGy_#TL$iMl!j4p+!vrKWg^Bw0qFB5JCC_dn(m(b=P2LmhRR~_bN8+O4_I-$a_}W zsf!o?UD$TR|3RVY7CUK+Un5accWnVw_t~=)ik})3Y0dm}0THotcvudTo_|l(!7RYe zI@Wd{>>~Mgr)kC2ba|L2Jg>25ASM6&?^V{AbEy#Ly14R&@>|aL#(Fuge|F7%{Ocsz!@Wn#b zwt}PaL)G9eKRzK~2THfb#WmSSe0?}tpp^eA+>djAL;}Fw-%lID7MiTTH4wD+LC(3i z?%yFG#t}Ic_Wcd3Y2=K6@GHt z6HswQreI5?n&6rgJNrmOx$ezyi@W=8-8XX3O@(Vg^#;rXYaf1LZ(6aY$%ex&5 za~sgQ2ii9|SXaxys>K-S_0_Q(fDB4i9~B{P(^fkvqzAgT5Tuv&UE7GQ5Jk1kLtMCAl}hxm~Q@zx?; z!BGNF8vBDT;Nc@)zJP*2oV723D*-4WP)`7(HEtpFTyt6l4$Z%}K->}J?*HEMr&qU% zu7FrTuYmrT*fsZ4st^)!$mIps77Pu-3*f`4EU3#X#OG(+DYkht(J=+rwd7&W9~ z059)7Lei=T4~zYw0W34!H2~H!=i7f$+GoA!XC1o(Z>d$1=DO!lm&SjT4gV;7Y#j}a zn~E(TY$O~$D!t9ID*?Q(UW?!CB?V6m>6}0CZ3w!+B~z;Z2u(+3mtxlb;Co>MfunE+ z=AI@FpLByn>nVHK>j`>_;?3#eXrPg!}a!OE>0Lw>VO%`IbfKLbEMj$u5Utlt#8MPn0=T|RO3 z2DRze)Sa;Jv4+MOXe6J#8J-ES28m_CeFDP%5I0DU7eUtB0&(nnZM0*K9Rtk6YZ4LKch?uf&T&{R;<0fP81!GZk5e*-mO)DMjO;amc}A=HJSZi6f0 zG9ZQ!!3fpm{6mL(4cnRY7HMJEe-VY@Zgc_DIYcaU%X2Guz zNW$1z0v~J56@)t>b@0UV62NU~(0#-ULM$J0L2x0#J-0ZoQMh4e(L`$G&k6#F>_wcF zZLk&<;9kc;i}IrQOmvZ*`ETpZHpV?T?y&hdL=Ga>Wj83276?qG|FofaE~chuQ8#1V z&C{QBjwPmA!lGI*%CHlJAlD5~0RAMPw7?gIzNWDK&+P|VRA~H{zuEY<6eP`v$Fl@z zlLoyjp)d+)1cQZUbXrZ8GEqQc5L0aB+ZRY)9DeKahvC~@eQ3vZ1T&AI3_|7pclMtI zG0+c6BLDhe-Iub@qwB5aoZH- zV-O)hE8Ri_BcbpLR#v|RqYa(Z#U&+f#M+*Or(aVkU>w2}1bokH0DdF(624$x!XwD1 zweU}X7}~$Pz%ngpQ~?=yU<~|7Kyr+y;h7M@I*f?T{}nR9R1jdEdZUuu(R|6xj(!>H z3r7$Ef(I<1IUGtJCB|3~CH+}Tq{r@2m^sguil zVZnd(NT3HAFr@>g6}k40>$3#t4?^&N-4Z0BA;=7$5=BaR;8cXX_W1NoYVj7z#I@7G z-hw^6S*UD`WS6(*2{u_U1PrEtSP2*h^Cp5D38W9=gPp!d^iI8c>SqfrOJCg^$?w<; z*0^YNJL^Hw{cc%fhWx#+yKcr)p!vpb>vW5+aq$*ruWoI1kb7agQ?$@+vja@k#}r2G zMKsIecNgoJcoyt|2ul#L;DkfGW%2uQ3sr4!(IHNBG3-Teu{`2noY-{(wxP6zkOroW z4tB%AFu8PFV2AAC8AXZ6vnkx$d=y#I_keDYUTgy)JVa(`Ue0Zg)*gKge+2*r%1Bg$ z1_@vh5bRBW^`Qel!=CX1!vY8=09epKLJ*R{1s_tt;{V6i{~wb4AnkwY1*Qf;RPg2x z3S2e>JnStD3!rpkt02aLJRS&W!J2U^5a>DZ?T4!mS$Sx0xyJY({}P~1jRe9wM2=1g z~+`W&>=s0sT9x%wP%) zuXN+qOGZb46u!R|wy$PEpajWaXyyb_xnZ%i-L7mjfDp1fpS`>(P&eSZ1xy!o+h#dJ zIWVj@gA}J=r-g!jFr^R6-@xZU>@t`DWS0*w0LUKS7AR23mD=VR1Tiz%>QG3&bAb4I;mYYmoq`fba)62q1yb2_)}d zd;(ZRbQ^gCtgnoM$uorL;HN0H-at+f_??Km@#82sRB*kua|C~`w7u)1mQ*oWPQ+1xg?VHzo zbD|=5+kQRxpFR70_8-qS?dorP0Xys7DU5mb_g+cST`4Ks=qEH!gl^rs%M+P^ho!U- z?J|n)f`*zV)BR!ZImf$r_YHPNFDy1e<@uHo{=v!3l$d z;mMFtpd$bn35hHPqz@!W6MVC}5!%6lMa*+M)2}HYBv=bPe~|ot2^~ZfL4QCqe}<|d zVWd<639o;sB&aR;AZwL>2&EDJ2uKM2PLH_1@eyPN0t^H(Mo_V9yC^6(6u{ov%mYF$ z*mW4p)Q#f*JV00ss->{DEF2I2L{vfm4r>Ss$tak^fd>ON!*OtJ_YJOaUj#l10KM(u z0-#@TxmZVNA+UA~3sam(l-v@+#TDMMU-G=p^ z6{K}eb_2N)C5e^19JlcXzoeRo@rdx6`k@Wv+~|M43uQ=~z{_%hK=>|1Zkv)wP`d74 z!ko5R+`>N}gbS@_^?t~GBW(0tNPVfKo^>cU+Dhz~C)3R{j-{QNPdqSBaJSG=x+a6P<;w3u#omtf zbJ})%2xzHuY}n+R{ZbUuZ&GqEYLxeAvh{$1#BQ!*zC z!xH^2bc8&lwbx@&8aEOuQO_PId4Ts}D*)eJt={XUw9w{-xx@O4B&QkK(UnfjqOVrz za%}zFo(Vp$nK$2FSXF#Yt;p&9c42K#rz&~AfB4roAh}s(r0%aXZsa?96Z}HU=v5;R zF2-c}rmV+efajtgbGMcBY(#iWw|IBnuw!1yNO)654!Wmv5En=;S~vZLzd|X&%{7|v z*t)y5@eWcRw};KGDob`>Q4g{+n)96KUu}ZDS!Om`o|@oP`Kpn4>kNqZWV30&|-eT_uJIs%OvTvW{nyM`InB@akLjaFn=4wX`dp z-*EltXuj8C5Qd=bo8heKzAz&-xzYevmzt5Ek>@q9+HKFBJM8onWa~)vQ>mU%SBYY6 z-0P5~PyeVp&189vFu&--7@m<0r7DL}Y@Wn>;E0B+f3~o%U zC!RXc7vo#@Dr!c(l9}|>5jV7jAH0NN-C|mC3U?^?!%kVU1w)y14f7?7h9gWRrGI^^Q#fMW>}c(mxTjkR&;vZ)p_k3KUs5OkYE>gqKiAenEV*16IR9P9 zhSfYMUX!)!>YqS z^9yLcUx9CmxpN1ZQN4UB1!a=@nWH;N!*lfBJkU6C_qzXN|E2ArMp83`fTi2oN@cbp z%Q;4O$t+<`l4s}g^#@{b`mXvpO{W=kfViX8SZsKQN47`iUmTN-B0l{G7VUQK81ErW zRWw=`?o2k$KH0~1;>Jb|mUB=WZVqEu>v4Z$Iw=m@z!;>|U##9nTP`R`OtM@gDyKBR z$AtAnKhfMfW}Z}?W#Sj8?!T>ZrqwK|rb1Po?{iDxTR|>MsDot3O#J#)AYW2MPSY!S=jCzb!<1Y`<0H2zmfQTyVp|J?eY4o%lGl6?{%f@yP8q8 zdxcHV7|bFXgp(z-LN~UgT-J{8n0xgmJISvMteAbTqdD_R|58(TQoTn_zWi*p+Q)-xzg9(Y~aCbFEE#F+79SncbJijj5{T~7?fq{VhqC>RKBxr?*u1@G>#G5OS z>Cx}S!y7pwgH~HYP5b!A0k~R9zqmQJGS9A_Zt8thA2^P02vab}U93%T+f0%np{f<^ znJ?d>b~r!r70H^8VT|N5J}p802fDTPA0R~`PG!D{uX zl^0*4h(DyO>x(a;qrGSsb@IYn*9XKtQ{HgwCu==Md4Kq#it2=!_uPb8S%5Z%9e+tL z`n|&H^Oc@v#YLb1Sdx}oRu&}8V(_#v3Ew=Z*VE3-u+WKJDv4hm2-3g>a!i@(K})f3 zZ*Epe@7n1Sq#o!bBRP)?P7}$JXIz!ELF1UZq~2BI+!{0yHIM560sN&!HMN(|72AH! zfXGXqieNp9BwZjT+>R2TPjGi8w zv*)SJHB}E*502s4#~v-j-koqS<$4nNzS*V|rhu3XAEV|OhYU<+Xpr9JX(y-VvQPbb zE?%?Ku7?M;GRKO`0L3;eTXYS}Vzi%M#Xp8UVN&bD>{D*K>1K(42 z%rd=uuNBwx+{TljG55pWN5etCm&uG|nPb;?zq~K9tC7y6nuS=OX(;85&+#Z#WThGEAUOE?rqlbcaz)f z8MHCJ-CEk%QPpva)PG#n)Q-0A$z@kwqGLMxJUG)t3OE`93&Zm@bxI?T(8eSh= z!~Y_|oY4tnH)U)fDK^h+B#AU+hzznGpwV(u60v$W*WoQZG>UbN6ZX`m-Ai^XIEPs2 z9sElghrCj)xESfi)kPT{e};m~u$oK6DZL1`Y-1Hh#G1uoeOsdFhQ%p__i|97c%x5) z*=p|h@rz#3jZZXp|5jyNC8h4)xn9g1d=GHWBG&P}8augnGb&cvoQ-ZDCdu*3?Gh{sm1zG%jGUv_TMmUOn$_4&kK z+1fn6_gd)n`VJ;DiHl|sRw{6soVSYimkPsomq)mUx0MfE>n)ou{audjWL6U}kPVQ= ztp;fMc5omuKD_Z=>#(zEiSmSTDu2a9*1d&)@qvhO4u+kQJGZy6eiva+J-a3wy|4dJ z;|}Yx&OjDP>9DbmjFne^$9DwFCIBJY()CY($h)g}i@_m+B9Ej?p_rC_ z5SRTj2B!N+qBMjE;R{+}XcuMHxLC7{Ic*RXwIPQg5ijGvQaC=r(I%XoiKY$GmAe!4 zC-k1I4oo^3ED(v)j$*7(&c+0#uEQLqrD%mouna5xeq1gsGRp;;Pgx|B(FzyQDn6=*OD)P+P^LYPQdWtiNYY)t^RV zQg>2w7I-xt5{qFv5>3}dyf`VA`WIQdhlxK~7E@h=zYFP}G~Y;Bol~mP`Yxp7k}{nTTzgRu9{8HuS9)C$=*8_v~a+rwfAm!dN%*E^| z3Z=I`Bf+(Dq1<+oLRToKh5TkxQTY>l*FbnqJL45@NCl6jJkFgj>bhU4nxoVmMO%rZ z#aex!@uu~PSt|`wq{wzul=@+g3JW9R@vNcGgfiSYFXeNkuS{=()2@58{9V+1-y>Y$ zrJdPw%t&F0`<3HOYH$6e@Sn9b!gfzlyUnr9rG9joV2T2{&WlnK1Bz6*uejZ6T{Hg# z6+3RV{5M%&Y)^fsL#&=cPTHQ5dcxr*uEY2Of%ezX{+CM>+7KpC@l$d?cCtT*>ABR( zVzm#CFGP5}mdQvSTtdhG6W|lsZe*9=^0r87!%eCJ{wg4PlF}?Mh94{g?r2LTEpt8{ z^x;@HH3#%uqNF6RqRE9#>18W^F8ASf>JQX9PeULfKcmCIR_Jr5>Ef66#|PbXSSaf; zN*y?Ha_N|p&0ZzCF~KiJO2L9WWboO&q%A0 zN*1ORquCBo1My4fLUXQ$-fkfBILx-N!lDaQXyfY>R{9~Z6 zcY`+gagJ9t-O$ISaMYTI=B6SRO(HNCYw7)ojWgE|<+C$=6%M~u$?A!h>b_``LVf5M z8>!7>@+hmOR%3HhR<^-Qr%g~_Y8b@r`$|`0)ySEvJiAAw?w@jmM2;`1UsBMvVfNLh z;w~npoxOY}@xDyNc=zX$biIjz+_35;A$>GNDQubGsKKQ|7tG9}zk%(ar${2XZ&r&{qd)OcTLZ`d; zsX~z6q_KU&l+NUb__A*Y7I{~z!@rVbbYxtJ5bCQaW=9W7R7(emJlTBqIEyNz;3y=z z|HQ}y)nd1!(dwdbe^3cEX;HykG~^s46w=?luEI|6S6R)XTRPFCz-bc`CtBzs9j7uS zg{&ajhs|n~!Wpqr??z_U>U;N++`rng3*XcemS7w+Zf?$A%sy^$_eeo&nAJ(bYt^GQ zp%$WEfK8Z1f8xY6*cF!NIsWnl|A8Gk4QxJHu50~-BWeA&3T^dE5{XyFTY9UH*(r!` zI&)=wTxV;4Ab~)ndG+h*FgUdLH1ZyF#794Dj$UymSqUh;F82uuQ=M*;>dto$NgV(+ zsrxj=F+nYA5WrhY7R@DOAvWj@hJaP%TRF;28ygVOm0qm59kWv+o(n!@(*_e9F?0+O zxXbXVb&oD5=oLL67EU?)F_WG|Ohq8fjp#v`&)rryv`TQ(XMq^6($W>Prg@w z@xE@Q^8q|wVm%JN@%qhk+FtQ z=GPhhPmGWZ9SJ5e%)BJKxXZuOv%4Vkgr4yBZBsy)EuB^Tdvq%ljx+BTd`UUApWJep zs0YSw@IsM9X%e2fP>VMCL+^SKgp(Ai{uhZ)89M7RTP>M{(8E?^0Uv6gR5atm=SZ7P za=m-|tM$=Ip;-S#ufF=UFe!h4d=A9x--Yb*>fr|5B=64UOk?tp_eo!DP$|B8Nw`-3 z)0AS8iDi^WtywjmI3BWW(UyO@0X&;n9KV>b{UfnO}5PeJ>t>(3OS5o_f9OTZ{qE(09WXFl?>0CEg z*O#bm?5T~~7_a(N5$PRmaY^SO;`t;qxNHd#xh;JTL% z>MHmzh>yFGn3*Hr^mJsLr!q$^2hr}=u9NRHO}Ob#ln#oAcOL97TR1zD=>&l7-jLOO z{bAm2(aK~#cY(aMuN6f)en-AaJR(!40hi_v~JqL+Hn^UmC_ z(~Rn>aWTh)iJU;ykigmNY5X`z*XHHnBlt->=6vh)p2UWCkeE^ZneGFOAZzghiiz1# z`MhRsN+`is>jJM5lDbEy$l0cBZaw2mwMXVnh;kNrwDRG`c^vyqVtiR==y#!CQisP| zrcxTjv(Irc*xn{PALODMOltUHlYcc9?u+)LT0c;T@UT-H|sUgFH7?oyu@TpIGT8U$PLY& z?7zTHyuyrSCOsSZ22P=_Zk&Zmo-lp0$m+L53~RZf8M7F|V?hefsdpxkLGZe{K;)lI z8bWshV*CQBj)8dp1q~y~1%miQf1uKmaQSqhF8P*~Vq-bYYcQxgxxf4!X3Q+x&AG2i z$9onRksa-`(nUC1mt-`E+tH3{udsSg`+#HL*2N7;d*t#y>ss+XQE2by*hto%*UT14 zec(pU(kq^DvH;Xm*+j)pzDx4-Z@aCIt>1%pp7y9Y%I&(O&QU-2;p!3%UqRX%`wVif zIvbCLR%2^!&*)g|>3`z;SPlf~JxN5hJ6S$2YnP~;NmDsM&Vqb!SbowXc(7sQIwSrA zxNqsuJg@Fl*+M9}NW_~Z*FB35@YNG8oRafSyaIX9Pebjk*GUq4XL=vsk*E)xg`A0q zeK~V!JQSc-)+c7$r$Cd~4lwQX2JMEZfv99J;8=^5 zdp0rUX=YwVKVuV{|Xm|%MOWkIig(h7$30>-~ z5~f{%)R>WhdKc&8q2gU1=lfPI^+$2p;uy|z9f`(OSLd^<$1E8H9)o|@gre6B$>gu% z9P|yJ)#LJdNf+g3Ffwj(Q##)6NoEl1wT+9-+tEU?)r^qC#-#PFj3tF7O5POOoQ@04 zPV)S=gmR*NkhmY-Gz*Mh{w{RTm3&0O|3&%M$OT{RuiJOy>Bhz2#yJ4Tz@2l%EUNl& ztCw(rV2;GZ%Q?Yd9dV^z93v-*#};sTAjw#A7R7fpjr?{aus5FK5`_dGm^&kE{eGbbTTiuM?5KHp{|`drb!=+J>} zvD#|>rM!>B6CZMe^+OkNQLQRR zmzVg7``mSvs+c@gYxFio`Q|vx>vYb0Rwr?#qV&=^`#6obHxkdk3mu5~1li{cKIOc0 z&xh}vV*K`2c!)SPWS~6#nGl|LzIk9DVb=RpIl);eyiH;NyF@{CvO_NEEzIcHW*7D2 z-)=a`ZhT5)yALX6FZevp^=!V-(Coy0OXE7z88TYDhTc_6-0db7YqEa{6S$9mJb!St zw<&kg8tzD|(!qQgZ`3ux+hl0_B|Uw~%8=Kd?rBF?5kiCdYMnzL(oq`1+K_z028mRW zgyiYC*Viqxg0zB}U%hfZvhs~UFVMAjw@5Cq$4GWF+)POCk|e&A2dERJZ_Z5&zly)G zZk9+HZt?0VRb=>*i=yXG^i~02L^gncf8nO`za=df9_(N3Y5pCO%EK$2Tl7bb(5C?4 zE_XW9A~XyVpI4uIug(!KYkVMd*1dsljP_7m(aHH-u4(nuq^Y-lqYmb2ewhB~ZxJ2# zoxkf`ILj7se;M957$m;>a#qvKt|HV!wkhKTlBYxC7{O(4ayzplMs?P^k(oPlVNy+a zm499T(~PskK1C#ViwRS)E>#(%*H~^*TCF-IF^>@R?CLFtO4# z5r1^%5bdIm45)n>aTE6@72lrCE}tF`zsWTD5k#QZ8Ap{M2ZMtzJ_%~%nm$o zcv;r0xW5)G_H%6;9^lZ`362ODr308droNJ$fz6waj$}lP;%;>pN-Rc@gI62hIZ7Qn zm9C)JN?_beivB%f785+VGEBmJK?SlpheRQ_j^RpMU2Md$M3*ct&(7fkJQLe!e`rd| z!|rRh{G#&pqMST+LTad+{@MFgA|Ic z&ieKTcE_J?kW)X@%0JuAjp8_bh}Rk(U&J%Vk8fPj>LZ2t_KG)&G!Uuxv(2D^M zI`+}lCgxOm^?TVY9efkX8Z)NS5^)?;eYAWij)JmU)R5a>&%RHtA8yo(0-{*kJr=Q0 zfV^{QR8eRdDcVtnG1-q_@X8SvaTFV#uzW5XBat1AFKeU}`SwAVv1vp3X~z>Jn;hm; zM(I9JlEgzO1x~#1pq#LYIL74Cwhui>0 zq-lIyaVOsutEa=7(@gTbM^bXg*0IUZam+4u>Ep&?Z6Z4>I_KAQGs<+fx%2T-T6)pm z;0g}*mYto+kehLyQMkyfBG~xsXr$-!8fWaPbYwP1Obh2>UNdKTm7!#9kFoXjLyNef zV-OJ7a6@XH+o~%H8b!*NVqLth^fSNuIe3ctV@#FoDhxCtx_q(r)-`U5*(*1^uVmH4 ziK<`zrU#&?SE&C*X0u%Wxlco#WxT6p4W&N)B4c=Y7x7vhC+_pk`4`m7Bvu1qdSsbo zXJ%KJ@=dq3c5mw?td}-5_an5caK5|i9#^k*?qXMCd9J?U{^1v#jDEoag`({`y zv`84qsjk%W139pF=J+}_3;AgiqkcVStuwLfP*>(CeKStnS`Q4T$;tiG@dM}hs{r2+ z15;hRa`q=?e=D(Spi(Tj+@%ZCySrbvBHrKXbn8PaN$s^88Q=2iSOUHTqf^PV)p2@b*$mP9I? zjME*WkG)F@EXJlN+RH0GXBJAO{N$x(N7sw~0~c}s9OoynS%p-=&5@s$@8eHJ~9#S=Xb zQ^c9q6(&|*2&vCvgHXmQud+Ldm*xq5SL{@JAEyM57BPFWi^;b|W)v5voIi0Eyz?hb zD4&N6n+H+0leXH@@ENT?O;D&qnOH9J7;J%0V~b-L=3gVX&^$OV9_Tn(GMIO@{Tt_~ z7V-qIY?A21hU9nH*yt{2p9awrKi0p>Ki4O-Q&yJv06(X zJLJ^24#Zcc$TL5-8B9Cn6PQ_bXu6|`Cm|$-A0L@kz2$i^fo<6z*=E2?i})jI#Go!6XZ z);U=%N~3ioz{{~*;1l#SNCEFm493xDTvqR@6PUv|N>6S_Pj;i7xD4@}soiK=jfH;; zMXNB(P0V!vHP#{_4DDWOu)etOrEJPA(Qva3D9N^ykQwq@_s*3y;gmPC@ku7tw69$u z$JJ;PW1@eF<~{DViJ%^&%vyo@Z7of3vPc^H=(avyqJ+?guc*B{%8hfMHhtyQxQL^r zEnEA_(Mjivb2clFFNABFP`V3BO}ZAs)jwNc%zJ7z^W!lmLNVrYdKmNWg}Th1WjD$3 zjP-Iq5zGtRBxjMg@yz4ZBJf1Qc?5~bG>I!+SojV8rY!g%`16%6J;lTyuPE+vwcr)) zos@yWsJ?!Fl??HVL}lWSc2lQ4-Bin?Jbf?0z@;y3RV z79Pba4Cl=TAN+Z1)>oMLa5HMr%`vE%7|6b%-90S!DzVg9OKpFAq@0GSj|oMU!lXH( zU4|9h)LM2jXNkUt8zNb63T^Ha&wUgtBu4ZE{G->f^UFYto$AhR2KJ$`9G&(%dZUK$ z-V^H$v!%G_&isn8v!r4b9FG5|!p1`0KvPK029-r~UG3b@;7258gA2?pf?_E>^4}jB z6&gWOi7GrMy<2OT(yFL`IKXUB69I53!`#Vx=8ze4+-7ov_jyKwkw3>jPd@0uX+#R{thnf-RU0dZdsdnt zG2xneuOuPFE9PVuXb#CCUC+JJv+}-3huN?y^O{Y%eKJj!B3ES{?D( z&8HwOR+^brsV^L}X07 z$D{FGf~@vX9rCX@6>5jTl&j~3a_ZJcWGa@WX^g30iP91si9?!U_8lTgOceF6E#7EipY!q$Wce6_6xEmp@0TwZgyk4v*qzv z$%0A8s~H96Hk(2mm^mrI%xghy(fS|lsm_rF7T~G7pBVAz={+=W_qsYm%8jTLeP_&O zUeWJbmFf*;8;-n?*(tvZ=Uj9)I!jcBILvxwJ2xzF)Gz4rznN| zzgkVITwzA{-RrXE`LkSP5UQrckefYb*R0cuT%_ten-7^SVyi3gE6{Z*w2j|zP`m5p29wG0K0YJZ`ZY!Ci`&TJ6VOLfynXqt0#No(!whmE`)? z*@UI*9}A1*s)eQFE@|HMP@ek$XdIi3x)|}vr;OJs9zJjh!@2+^6GT%}In0xzMat@v z7(bP+3!1jGzBb$-Cev?K#m`(laxs94(-4E*)R zWe8JLJvWjR7if50DUUtIJM2cHul^~eYu@yyY^>XsWwiUBS@SMrGxtL^_Os@9|0u6X z;U?YkI8f&ml9?>{daaq|p;hP8y1$TYJn{4BE03L;?{7c0JE)qb+Lbi6jibt^;Rxh* zw5W87edK@4!k*lYJz{;6VgJx<{rH5VYNlZ}8olXkg4|Sc5#g*mbE4#Afl=v2Er}#) z?9I6l-E3!VwtEA^y5~v`gT7T`E{z*Nh}VrOZg29N`>-i|=ZL%2-djY%>3o9G@-I#< z9`FGrRTsVRM2LeNxvdeMdR5J!iM(?X$+G`!V!{^9l{E=OkdpPPQqB z;g5F})t*wB^~+GK!dA#hhl-e$Q}$NrC^m>{rqJ39V4&|EY3(jyne9dss2$vxu;$mA z_iJRhe`Y{dA)kr*<+bJ^DBZH1{E(PkJPGMm>R28(Hl!~4Wcz4zo1Ka^uPQ`F$vUiW z0UuTvH*(;bh|%C-J{3ne*bjaHf}Sos+Ul{f3aK{ehSH$5VOHOa`V-|P7Mp(9^0WJj z8`{puSk)n@kQlBPGb1dn{K+ZKno>jz&1|>ZlG!T0YscKW5jQChRjahj<07}j+qk95 zvW%DK)D4`k?OYJaa{u#|yWa@ZN5E^6gJ1hOP*PG-f3c*tc=AK3w#c33Ltl+XRnrQ? z+iG%7Tq~N7`y%4p{_;@dEK-kV2jkSo-KMQ1&}KH08GB`w737FSuU)YUH@|AF4E+@~ zAEsy#c8(+band+-$bdB`w1N4h#L5Air|%!OsE_|(dti_HiQQX8dgPtY82f4#)`t^rf`Nq|+5ZSN9 zn9SrZZrWQRKPyz$2EA2z>mbFi6F8h`k?ocXIANyM2!F?Ud-O6c)Jr?EslDMAH zaFcaY{yi%Uw7dfLKwq&-5rHt{AxnEt-|q0z;R+4rLb|&$b|uGM?76DUMBP&KTMWwc z+_;WFJQ-3^%2~8}21?cCzAkMad*nrXvH8Ks*B7HxQeHFwn@^InD72NB&rnzCfEr6$ zzRJ_kgruIXeTl`>fX9zD7lQW=<_o_+KUD2n|JS34iSw&(-}7ov>F0a5qtbsqVCeDe z`Rahk%o00Uc*Kx-Ze7IWAIURIo$3A)=O@muXZp%#h`f3EbNXSmc6r$p;V*T*u>r^1 z0xs8V$-L?J=Uxx)R{UJJa+=IV)Gv3&^nJ$a&V7+(i-s{Wl*G|6wQI5x*T{~Bd7HvZ zIM!d3qNyAvk^cB``U#m9^9l1Y<8RgBue;`t@tEkhk&oL`3c9sNjlUW_$v7(cG>zia zFgK!^@#vNeS4{mh^+ulfeyzH59V3dzn3_s8DRbtnC?(yld7}=LmTtBV32^BqL*5Yj} z4Qgb-TO;eD10)W$)DAjpSnl&5=A~gS7KQ9(&jwSAPkxS=|9OmhYKLr_`3QAR@{4vd zRjKl-60h+~(Y*PXZu{$+xR&Zn_XX6JS(MP%|DP|BsD_VUgLM^A>t{73XEmjD?Z;^+ z>K-p7O`v^4PR=EN**$iW8yD78=RKtTXV&A6(_=;R_T1tyrapd1+bZkvYcT8cQry_E z9xfH`nAs85_a>~bBdm*C6vn9Y_Be=;WzM`sH}~MY}x)Uyu=(i;;;#S zpr^b{YVg5Q;sF+`xAAeV z*MK`_Za0QAf@8KO{Ip%s=uz!rXhm6O$FXTc0m&s7P&)x>Sue7UY{wZFHeI@Srt-J{ z+%-$Ty;JHq{xl~<-YgY-w;wO^h{Y-VX_@H@7hmiW^T_q#h)6*7-v}gVcO9I9Sv@ET z7q##E<68Y+m$kgFj?H=2#1#eSccb>+8I!#oqROa->bky}RAa?smMq)ddrH(rf4yqD zOHJd(-8Ng}I59aow^$Ib)Z|!})U!VTsv@uYV0^Erfu&Tcw2J?@_1mhb?DT=bE4C9Hz`r>sd_ z)9}PQ6AGPP=;x^?KN->cBswc90PE3fe4>|=mc0J*KAZKY(mKctB9REj7UuR!-_z-I zpgjCR@Zz6A{f>tvQldNUq(xpEu1`-EjTrkaW1iZRaC}7Dd`>~g^~nzU;eQ-Xd63BsA2e#(45{$aD|822D9voAvU+=(;lj2wOIF7 zoN@K2si;X>h8!!r!000sVI}A|FtF7%Ie+d=g%QzVL4fkdQxo{xCz#ZIr!D?w`M=B3 z(CC?4H`ZqT=-E+w<{6^{-mkr54DPM_C>QUPp}6kyEMAItnOt{}|1AzTx2L;1tFG?7 zCKU-sjooCLj0%b9igf@o>AI#~S|Qr|;{#V&lePzijeV5o+MCq#_n+LImw5mAv4iTD z=rG~E3h`xKrors~Xf{%z|Kl|R!~I~1aISR6Tu9A_KYzK?%3L#1gEFiZHO0?1F7CFOKw_RsQfZRWh91^<%h;3ZFEJX!Zu--15vy+Its`jLWp_=< zarou`zDN!~hTF%@l8qsR?=K1wx$_!)-oy0#;LNC|;pWhdB3_PYXLXgD;$^Ws-v zcje&)WLVL6Ue~l2@$u0!{x70l>-GTBDbGuS>%^$@{{eLuJ+w7%rObsjXXPF|7Sqm& zY-$K&Wabl~dTZ9)t=hh$ILiKz$QOPI1;Z|~YUdwM8%a(MQDy#7OtfpYq^3oFmAJ+l z_o!Ihe{Fa+Sl3;>@@_?4fQ@%H|K_101;>D!A-c004PRev1rPDTZF}xDKwZFPeec|Z zR@%DlEj4ka%eer2hvq|S8e27$s;Nu}i_9m8M#YLJL#ShzgY#lzs$;ZdBFyEClrC;8 zbt?5Vr&F4`ySsZU9E~J1Q^Xk6MI*H2km|Y&>qxF@Sb9ZD?xMC94IhMILxpJz-2|r6 z4FSDQ21uW~*<$bCqfn4+cEIeV7%JuQ%t9FTu;i_n^99B}$v4t4Ds{)gqFW$L*lG2? zeMEImiYp57gITV!RT{0$Zd7%S!Nmuw4%1?SGY!YdL+N6`@|a-c&t_p%ecN1>I@7Qe zuJWAuNM@U6nuo?*+^8&kpha!XY}XV)&YBIQ#R&K%E|U(Hs(ZuS(Ee=J~7hvg7KNvX~Gox04{f&B*L}jYqXK4IsLQ(FjD!PdEelBrkd^z`Y zx4>Y7@BtGPQ3CTA_P9Olaa&jc*D8(sXA0(7-N~?L(6>fqWVVLE7Yzp0juPS? z`-qm0+uMo~MHX4OnHOoMrEmkxr6r2N4zk?MLS#-|$ts%bnWeP;seMo_YAn-UX1)8) zh3e}e_vMSFSTC{);;RhYMKK*pLAyP0oYAr15ENI^e;i)3FqactZIgaL$j|(AN77h$ z@(L4;Mk{K{#=WeTHL>jOs@UKUxiDI2P?LkRgLbmk>6bO`8-R2oH$a3@Ti|~$b=@_= z`H+o61g*P!cos4;E=)KiGY)*ASb|+ck(ad zY#$@s+w_Nr{^YH#!CmR=AcykA=!C1dN%r005gFToEkBz8!H4sQPCRp}L zu>R@iHSYV9WN#2cH{R7a^VeU(4`#L$v#f87Zj74QkeTjb3abKa-dPURO1<86E}m8v z0M%Q^a&U9ING;w3eZrsFKYw|(`f2korkWpiN6h}rxiM6eKhNM!20V?J%VeuYe9dI9 zyIo-a_qQSaU)VoI%w7M3$hlVeo=9rL)a3JCU}~tvtmqH6tD~C8n}(C-2btB8DE7&S zg-o_-#KLv@&DBpRxb$2!TLu3F^+Bqmjm}mLTR5zRzt}9Ec;ME5lJ7a}IezyqO4#^6 z*_t0PHL{&XUyNTT>wY@tYA^a(QziG%-oH<^MR%HiQJ&uw@^U`r%QpKK_<8Niwl^|< z@e-utA9K!Y-Vq)%oWFM48+Au)>;<>1?)+vu6HEVq+uCD>+_J-368@iW6>C*?AKJ7l ztl#5^%o!oxb8kZCo0!`>?AqQx2%22yxFA{;*5ZK`>5SpMxi_3}IDdXPVs;x4q;^-# zQ`3lf_?TPs3VG0s)$wvdG~1>=)SQ#{@Z##U=8$*9LNt5RnTt$9S4zc8*@ zNPvylg`d2gYL$5dnA-f3i}(Hzb=@yyRVTuZiCDf)AMekXl87IkGk;y@opXMF(%$2l zv5KMu9Qz{<$KCoXJ&alNc0T#G41(4VcZR5r&}?5>6)|d^ZKGJ>RzRL~#@Q@) zzuDZ||Btf&0Ba)a9)R(iRJE_O4`QjW~OIf!ogl8HTaZDJoMA&v5o}bi9H_J`` zE^Mk9Cz`qh-rzG87E7aX2UA3|v_W*2yOmOT*)LwerY>F{3>4uh|d`hXD9PSX-I@ z=H8u@bG zKE4_jdl!u`opeAak3?10ah54r!sHBC@?DZW78VBky%$7Zhh;wB+$%$le_c|fVrVkZ zJT-FJImwN`v($kK_}!b@dAePX!a68u?I*Z})>G`3eSx z*)|6}wby1FRSq;k^KP-O9?_H#%Y!6N{liu&_s=JJt`G{0SJe%m!KT=-9hpaNg0<($ zIhOf4#|FIbI6TN;->i+>IIuPN-!iZ^Mc{K*}$h^maG ztp2huQgyar4OdO2d%2pz(fUwxCEG)>wGTgUG5VVQG@o@b0xEtRe<*PMt2ftUC;w7T zbv+w)EHhOt*KwziA;6HFhX;3ph^H~Fo zp(L3UD!!u#as@YN!@$9eiUNHd1v4zX2>e~ax&Y-Qvqa12@538HUp+3LLSf_ocQ zS8lo9_f;_DQ=3xq9+MES!J-*gkZ2a>$MRUbrMRsH@T|pQid}uk@PZ%?Zf<9pJvN!U zVYH5;VC>tYFQ2wZYn}Z;>ynfwhuPOG;=gAHr$!#&upD$@sfJlMcR|$XIj+V#7Wikq zLv55SW-}Ojq7;pYNtoLN>u}klO}C5>5OQ?pXiu(b5C!9Hx}F_^UIZ20{pC(&T_!B_ zoMMV3xI6!ds}Ih7^6S4+~y6h0H^*4hO>ryZ*Yk2U8R`z#6pl3pFTChfTgH^Ysrvg~6 zk-N2rR^I(w5&)upl_-}-kZe}b$SKIwUuOLFl6!dGZH$Dpnb7C}DGb2-Vx;kcciDXKKw^%#N^>q0dX}@~an-gOlfSQoS;lF&y_NgE#>Ga9P*M+}jV+EUI9?!(p{Q zS~X#b(z5+^B>;-J#St}a+qwYFru(oeXaIw?~1ATukw#v&t?WZ zvfEQT4l|x#cjcGt@-um6TGXp-d@l0kKoHAiyeN3rvm=gL{E33fP9?cQD|rK`raJPam-Z}zotpd}2r9g^uY|=v zzkh@&TdL73+f^0X(4fa{{hu050Ul6v(K>Gd(Kthg@!HMyMKGlSe#PB+e`YRoC_5bI z^*tIqvMpbVvVBc^^khfwj}z-2ILU?Y|D3?}qcT@P<}Jv~^M8L9 zlS6E0a*KOllH=zivZtWJ|NHshx2${gEPp=BEcVzX2XS}eBgM!WJ9Zu42d+??>}Wh4 zm(#G|_@YqU5%_W7E(-y|c_ei%L!H|yb6=Eaw2$F;69vw=Hv>Qa=a}uWu08ao?T`Pc z>f~Q{{XV9D$eyLHe!=)MQU1Ee(MqPjl9JA8F#O@874;GfgH^YFP2&Hq&F;R&a&V7Q zL$}L9a1$<~30Q~&$f`~yMJy+|G0JN%1}_XOxWS+FOo)l4mYH&JvmT%FrhNl9pVb1| ziuYWlZuHf!ovZbG)^Yv1Z=WVVP5)-v9a={(|L9uguKEInNEN8~rPCZ2nM+ z8EAaSa90Rq^3HQ$HBn{f0HqhMJuucQ7Y6#pf>Z&^x`4qSbL3AB>-E68h484Vlpldb z7mq1iw*nDsqnq*Z!sqZu{3keGLT%bNZTHT%HabgSBLS$=H;-%nT-6CAKF|!%kgKfN zb$r-HPUdaHZ}6ei0u20A@MU7KQexiN2<1aOv)Pma={ifw$8}Dm}7GA7)*& zcU>37+2awyBws4$JNufIVT#4GlrQlNu&o8fyC5gob61OvH?U4crULl*p#5tn zb!}M|HT}#QA;T0mRdM4|Tcr|Ca`BVNt>*XJOb0#UGGf7WbTJ(SA8jr~`Q9wDB+lG* zY~1uW>pbU~*$_~^_hOSrLVNnXmL;}xeWJ6MjX#D5d{T2y&_mbsd>w;gvq>5Tx9Br7 zq4wFzi*~K`P;XDaPoM3ua7|A_XJXl&N$mq=dulRsY-am>AgLM@KYsdFYxUk=9p#qW zZqYmOwr~w>{xjc26!FRwO468mwY(A1wu)T4_l6zh=`0K;eFC#jnl6my=AB-fdxHE2 zLt{hn1Haief4bbLS~TY09&pp@$h{7^FD+FqT^Gw&r>1&ZWx`ZDXUUd;;(e6NfQUrT zChf-L`129*Wwku@;~@>fAHjE-@0phOe55UVuT|dzaGeZtAEbI( z^Pf|*4+*MreOM8j=2}s;Ey{v;t6iZ<0>@XS7cPTwjvB?8EB=s#e#J`TzaS z-z1>?@!jYRpzOJ(V4!j}qF=wjoDR%!e4_c`>>kw*aeqEvcJzNuCCrW=cByD;NLK^XDJ=d8gLJB7^fB=4}2W6Ziq7Jy`)~6H1@GSfZr7sy+0c z&P8(fPcvY*=Wi86!HQ$B z9r|xl*{`o6esi=vv@xK>g7@PY7|GVvlWGSNyUY)c$^rdcGKEbDkAFwMw#uzfkX!xu zj+prX>Atc{k=+Ih&n>DY5YL8vK$Zp&xBq2~wkpWB^34r_ud`rfxoC_f>X=!n>XSUK z0n6L8!OR9T<;9+E{M1DF5)iA3RnAz8zf2 zHpd;eMPuZdyOs#-KzZPb2Ui>2xkrfo`J0S-wnM6LW_t`LW_(*2v-R% z_@wArDgWlNsuO%!6&IrcFI^=Y_`tNbEa2glqZ|?6m=iBkMV#6Q)F1S+QleXXcBE zQwy%|YBd;@?`8;;eefr{kIz|k_xm3o<{2qQC?RcAYqM>RC_8VN^a#EAV!?IT#?URl zUtUblg|$eJFK@iP;nC)+pOYZo3OnI5o{U8ec&_UmWZuQ+8weAw3)m$KmO#`bgb)ey zYQI_KFIK->C3viGOu^u;iw1-93PQc)m%H*y58q0>DRBN>>5OOMD>TA@C^)G)jC7x^ z(JV<}d#asSo0tKf7bsRB>%%po6rA`*VCo5Tpf4KpkgrhQ)6M*DJBJ)HMtrGv@|?+_ zHzX*^)JBWt)KYNKUaL1~IqKW)`TOTU`w+wD=G7X{qq_qtY*iozl!u$DHgj>2>;uAi zj#&*J&MpQoz^Aa(=OaX^Kf}zV5yXVEs{}WC@encj8N`8IVc!FPw`jtpb`R5UCEDX> za{soR3V`zhf5wY$chDxJL2Z0Otfi21BXU7lEM0Kw%ni8z2W`Bbo}uM3Y|y3@xY8o; z_73`_9U7q&z;ikE0`z)_qRn#Hj=MIXmzD8HyJ9ZaJ4V|9RJGimpdvpizeI0LbR*_3 zr03o;50YS!K9A~f@2?P@jen`u=m-{Qag2>t)nzE2fy4nz(P9M)5?|;|@0H{EdQ4Fk z6y8)6T_6AM&-~yztb8BMy(TUXCvm?JmmzJtv6Fabo7b4R$&b!@a~zhW#C7V_()lI6 z?F%BFsEWnn0CCdId=lp+8t+HC9I&6B`L0Oq%IbGdbthd$AC1qOgXS(2f7}0JUzEQV z4`e~e7M<%j4HICO3lU%JNc_-a?!AGdQd^fu8k|GR$6^jt?pmTnnFEoYGcW9kWeiTg zq`Lkuy}^j%YKoF?4j05h{J@vz6A{k42fL7-C$m6)(((n6=H?XNNPFr){4b0vCmh9j zBv*v<8pvd$g&dYZ@<8?U1{7!Dg^mq1tiQi$bjj4b&3q%>DujUyrp&gcsA#BGiRz*08?vVQ@UoQEX$d40yv#@zp z>}_J3#Vn-F$^#N19f4`XouhKFfq_qHUTLY8SlC9H!Cz8L;mHS_MgKy==fSF;&H0$6 zNro00A1f|0VqPt8<_^;{ZM=M{DW`ECNKHD<#FR zNjlG&VLnrDQiquPhLNog8w>G0`@4KO3}Kia!Ywu*c$MLa#u@O|j4rbj6=5fn_%(ls z3U3NoR=XPUP{7B;^kImyHqq$i93^8ls~b?Mh2`%d}gfoP=dIV`HVl?ddnR(pa_ z#TeWxE^N^aXkC*r>=lH*95!z?zp zjIwkJR@zM|!A1vBbOR{JL*QiOt znj9@)q4jzsZaGEqLir57{=DSsCcnxSsNC#QU0y7R1dh>E6UvWm zk`nyk|6C9oIoVMDxn`XtlBQWro${X)twS5M zi8j^rp}EN^t;uPqEVvCFV>=JnrnOz5*x4IvC1|$UNz|jKdIWkk>8I*gYC}#bMo4r)bZV-*CW0p6;KN{fjF%yjB@F`_CfU2OT5oSmFSS>BOg2 z{l4Lowx<`HsT@7EN|5_yd;J&DU$hM5q&vN+nI^~2Ixom=>!n^&-_s)BtPC51Ub>fa z(PzWYCuxj9(Tpkm5&4sDSyuLM!v6P20sJOMMAj=MkV-`nRqmvsHi=S4%QC;zdeS+S zfle$$rT?ba+6g;dwV<1n)D5Q`v1QSJeKuO@G9qsAhGHLKews zDnA8{R?pet7T{=rkCuZy92-?MJ$U`_pQ2T|e^CrcRRI%8;W9 z@13oapRMDE|CEP|KxOqfq2-YKt)0?E>vXIgqEDM?C8Ud%YAUaAN-Mj$_T}H6?!+QK>2hud?URU}!Ew(DcQ#y5F-%M+6-{*s z^qSEPP4sTj6>-Gl|2r6$kf4!5E-eG2;Ke#cS77Ub5jw>O6Vyw#=G%>F|A;Om?Y_j&}m&Ft+>aPO+P<;lNCvbZH#+!$oy zKxyWVYH&HU!e$_s7E)0LQtVrJ5!v1(zIWx(=x<-YT94#5{X9ZXxhXVhd>YoM8c`vQ zMNc~$4l^B78%L^d`|Wu-vY=v|NmQ)1^0a@mP+aBfpB2H$z(MtQ%P8MFV>>oA6X5?k zE21dKDLvuU-`j{G7F|=bL6C$37X&z_=;#}3F^NPX$-Jp5NJ6@dQ8GBcA%_8}-lKY5 z_>ns_n#<$p4UNisxX+Rj&Hz<7IyEbG@a0AWT4~+AwC)~ST?;;xtd`O#>Xk>sfcNz1 ztSt3Vu}YJ(k#q7yT5-AS8|v-=$9+A`)XTZDYW2EuT9t6pNv!uk%b8@dMkmJ|zxAUz zXpuLX+fVz*Sun;~&_3$EZG}@}Jsdz-89IJIXQT4X#0-)vpX$kHzyT+&e9{%Z(r9mS zaT%k}tg|z_uqWOXNu<1qo)uPAl1udk~BmujxI&>5l78+cDuZWc?D$f>`XNp*GE`zvx z=#`6UwzjrZUb@7Z-aAU~CtZ1PHCf_>RL@U+3-oRQePIh?^Wdm*$I@OCN^bc0c{Plx3>UoRnZ3Y zCV-I!XjRZ>qtVb-ayHc?cY@(X$=QGx0LtTvTY8$UB@h2c&NvQKTQzx^0`?R2a5_tU z!|vE`c`?n}ITOmgP}b_`nZb(6;!6H`(dcMs&kP6%z^SdmP4<03Qz^1hDe`5-1x>~I zv;6kp=henqO~!7f)PB4j_4J0rbth@*O>R0?AWP;Oa6QE@&<%_t7)qAWz8-u;)DN*b zM*AAA$bxVr=sefc)6=(a6pjNyeB|1HA9g)G$qg5?v(v2ABjzoXw|B$OLPN^Co4k&a z)q%P}GIew_6kHuXB{_6iBN38}Wa z+D6k+qNfdm?EG{+{R%v)9s<=&q&1Dms&h`8OcRka|E)Ytq%OORH<3e5ZW>O*9=J7K z(5BMT+U%xULq+@2MThHvL+iwwyoGhxlo?G2%8trf?n{e!S5qkAgbUrEzBas$|38kK zpt)s&_NT=4%g~7^O&bLoci#rY9#PhJ85hGp?}oeU;N*`t@b6PO@Kbictvya4x<`jb z(nJ(dYicy;{%Y#js~wWS2Rl5fU});V8vp~qpQlZ2i?LfZ(BFR_HZ?zg+C2TA>v$Qh zXrN}e4CYH)^$!h^)&zaL1YN_&+X7hv8;^{^XBSmUseyLBEP8#&J~d&Jp*^d91Rw`} z`FY3$A;`ji4-i!#;(y4j|31M;&MYNp~LPH z`NM`55Eee{7>quML~%f2_D8Kfa$54PVr0Qb(W$9?7zr_rbWyLKmiRwE-N10!SFd#A zza_xS@ri`ACg-Vd#Mw_5Kbc($LXREAQD|0&5q(b5Va;?k}?p!MrCr0{*K6F*FQ|10DUZplM+tJDqNZ#scm|= zwX*5p#q<`Z*L+KmoC98MkOF~X8`6ZPS$nmKRbD<=5Ynhc+8>emJ^R8Dy$4`*wQ42+ z6O5!u36%iPIa6UZ=?PjB1HgUjfQoS{uXMwS-VwK+fvD}jgif%Mp|ob5SWUepHNl>0 z^~5zLGHpWUTU_FU2yVMDnxmfnljCJl30U9yr$~b9|4JoXO|vI-(NBy7r}@6+#_3JQ zS`#Db*K(R0JUv1AJRm2Bjo@PA|BmVhz(w|bMj+}x$h3ADBsl7@wVI=;dqaNE97GQY zpc-Dnl(jQiU30A7DVfWyHkQAgj~k^@LZo_{NaamnF;w}X^pTUsm8cAWafHMi+| z*!L!9iLS9$1E}L4k^mix8#1Xr_}k1^)l{og&|ql|o>gHUb+~E-`rs$yuma=V2&Z{w zj#GL_7U+!c(B+oK%VT3<`d+4~Oh;T!?8S!x^&9>7fd1dkg-`3wlga8shlRAl7Fu01 z?W5=hSY9&Q=dxbAdw|Ct5G!O5tl3$CcFCg}oef}xSgVJWg@A+SW$iR@ez&c)v(YHt zJ9g{$uBqS9PtTG^krY#_&)kYbHqoz&ej^#;U6&o~DqJ4_IO=LP+rNo;0KfiRuXw$h zIBmW(Vd^HwsROjVtO(WQKhK4MnZSyb3BrFLuQvA0Gu9lBPO~Pf(>%~m5ebfIx&B%S zLpFIiv(lPq6aQ%l6vhPJx(q(}<1|(H`NKVOG#mxOo_hF4HDDk%!P$_gc-;9t4)y#~ ziS-w^t(XrdcnJsSD@b*CT&8_xMC*%V`2P+p&nCnM1jQ9F6q{GzaC4H(`Q9X4I?2M3 z;e6N{S50Y6c4@_S$qnubSKOBs+cn!1{Co8Ld-9(v1wX1!mNhbE?Udf;B-!LVK&^Z? zgv($Uv+9R(+4jJT ztsG~p;sQOskd_rODkbo99S2Q~HR~$gq$JqC0k8G{vtHTFCqI=gNE4Z6v;qX zsJ%^IwxQAeI=CS-k+UE)21hX9iIfaT26VOA;orb4MEg0ti9L`2n5pM(Bb^l4Jydd*2G^^psa#x+XU@RnN8KwOEHo^_0 zMj5VB@Du7-eEIOU#vfvQQ#$tS)d+aKZaM~H^?N^5PbzC-Qqv~7UUaGOq}y$F$2H(| zFv6e=w7v9j)rdwRcpP#mxIr)q;zK`CM=q&eJC*`b&IgpqON@yQ z7l>)bvANZ8LvU}3%`G2*$1SOX6;M`IaIzxl1xJ#8-u<4{v7j6d|Q6Rp1;TF^jJpYGsrRHBqvt!mGy0IRi~_-6|6nb$^5G zKejn|7e*=9umlz_6~fN|tdMtTZKfL-Fy_#O)$5R+DEl@@pF(V_9BO3yGD_^lrTQ$% z4u}*l!Mxxl9sPJuXHxqnI7}X-6xnR0t3(lvlDI__k)#?jY?Pw^MAb}so@8k7FZ%K%s#*vSKnq|YoBm|!_(K%IrDLLq!+xU3U1k`kx$ZW2YSTLKbCE{w#dOxG|=s=rtBU*fs>7>`o;6MuQ&G@=G zA-g9H(t_f(nX*Kq)q+M{ygZe?GMu9gUilS|Q_ z2zyLC2{;&Fin^;g1AJEzn2g1aOkO6ufMuX)6DCZ-ylp2%PdU^+6&RWyRq#8W+Cp^* z!vKovLDb$9FA31ty8&?vT4N<80G&cYWk_a!jVd^E3|41oa^kCV8Jud$#FXQzi}b`hHc@&DMMHIv zGEJDHQ#T5WF9i0rK%8r+eW@-0Nd~tjRxE+n$V{=X*)|d<^%$IcIfJ$j+1xd-owC*o zrkQl%%@AnYtao$+ra0A*hACx8N_FZaTzh~BjKjc!#ml4#r=aK9-*ZsdmYK^g8Q|yG zvIm`zHXF1j&R+bTQ3^LBym73l-(&^wj*T!y+&Un*p0`)9GZuUDC6MQBY@qdB&LCdo zsy`H$AU$ywX$(#rdk|co;w!NA(hH*bz+_M86v1MgT=p{tt#pxREb*PN9HfUiIDxst zM8Nt801UcBX#%j*MG;;sCkfF!LzUxS0*T~LMo;YP^aG63r|`-k{( zN@&kZ`zC7dCO&YR35>7+sb!|+#K16KRSO>IP2H!BcvrYZV(!#QHx^~`lw2uny~%Ru z2XCU-1h|x44x7SFEz%%oYTqP=+BWM9KqJcR5_^6s2Lypel*ESG3jl!X`!VFQF4_}s zA-D%j9+!jEhPg$$jDTIJu0Y9&0yx4Oaq1*Ivmx;mqx`}otaC-U6)vv2DWXR09AAvV z`NZ~k*oejtwMI$C904$KY+pc}Jr<=D%g>~U-zT79I5q$-=c)W6NHfTr1U|CBtVZeh z7$<>u`&>nlg8(U?rCzi_DM9yeBf6C;IP1cR5j};n_TpNiFc2uXi3+K~F?Y)3AEFvp zfFy%DX@=H>O~r^ZA^jh0$FPN$Rseq?kT%X?TGE2FEx;*CR~|^HcqfZz2J`5!+I0eQ z{Ba!lLye59j51S+AyL#0GLTlP%;uKF@>4jlNP2*;2{Sp20MTH<5C-``>p1pp@#zY+ zO-!IU7~~~JxqTbFn5PNvdl;|{s}*RcP7vW;4b%ccQz-?~(KhP(PTKhP(&;qioJkJT z0VuR`0E>@>Q9>G__zKAGR0FGKpQBnwA(ar!luJ;2ZkvrBZp_rx8)cL^OpNG@q1e#@ z{t!P_3~AKAu(=;yU0p;%0Mni(V{iL5TcNgO#FpV3m5S83jJ#v{lMVtT!!iR{4z$w! z@7P>*z{=`gSiA&zgdN3@cLdT6LLOe8qI>5gc8*bAqieH6o1Oz%(P0Yl0_2z)v?T|2 zPUrr=e(7f9lIgzWKp@@KZ6tmPo-&0KUnh8g)|`M9mnxD-AQNRrn+1h!fP-7&+xspebp(<=X3|EK+2t0M^Z;Jw)PR>nLRE z#>5qR6j?jw70^8)dKHi!74>o?)CuW%;mCJ&8UAg=Ok1AXJ2?`siN9_p*eeHl4r|St zA=63`(I8SOrM4o`yWhYIw1i6I!-&A@bOqaHNHXU;`YsxDNC?}F1#4_n+71^fXC~hg zPBB|+L0>>cqD(hd%MzD%39~SlZec2}E61$0AOT~=R8b|MaqHm;*NI9A(B!0O0`lm` z-C022I}YiWsX6h0{*ZzGX|plrNd}}%ddjJpa2?sxw0cl!SDR@J$+0-m71Z5IxdfF) z`tSW0d<&oCV(gMJA01Dc-47$zfNPPLUj38g-4mQx z?7YdenLs{vB(EQ-wF<~j3lJpOM)ps@ENzMsAWe{|JOTaKPU(#Wt?gH55bMj@(*`O) zF$Zlkj~w$-i0TlRMWuo5^F3l>4g^Wgx4+Q0N=;i7zl5&Y+ER`{R}qCNjbV7Y5UId!+ABn7LcS?O@+57fijC~7t(nv zbsW&{ziV`fJgeMbT`k&P2KU>fC@j-$(C3)&8u@{8X6}ss>ym%}9tn5*e)W2doP@u# zKmN4sZJXu9tfKzwHjA!Hlq2GdN%63CA_gqP(0Ev+ZNSqGhy>a&rLwJ#=(_sS?}(bI zq+XB8icJP)i>CF6x7k-y0M4;QGq(!#Yg7zv-536_r5XGOiY;MuSQJT?FAG7J~eZG8xm>pit>1LK#)KBn+HJ5q_ z6yDqqI5xEg+jmbpq5luLBr~c75Abh5>Vxe&{)==@hvto~nv{=2nhtwgzj8rhWAlOG6-mRpC@!1AjTV*#j!U8&-W$Q zr5~9&5LImv11WTlO9?@Boc`$?-`yZbbYngz>>^oW2PoOy|U9egdN3F7sU z_-cbUtN?Mcy3v$hwcsJ~hA8;YT`u`G+V<1PhJrEj4N=NTls$@YLj5|LfcK6}>k?bq zCp2l=nbq)0A(k-QiXHWCP2kHI(F@k!xk-FSNUq8$hDOl19bjC!`aq&-ivn;6aza1R z=}u_RLwj*qs>i;g`tdJk)>$0ML|PR_LgR`n_lfX7N~0(nU!zy!*xMwgovrGo3ylkj6%$xL%U-?Y-i*aGsYQ*|ycW z98_z7uWZOl_^79BpOTA)Kt*Wlk;=xrXe~Nkz)i{w6v*Q#?QdOghl$FG!D+O3MMZ#V zpo+x-mn9$!aPUSKB|fw{i@zo4DQta(W~%4)07BZle^Ng)^&IWkRb#)ryE|j@SIOujrDcnRyUB)nai|tc7{+>KQ4^Ofjxb?bDp1)41LA@u z6XZJVI7yz>QpdUM#Rs%T4(AdBd|wojvtEjFbW=q+YepxgVD$a^JYs)#+vdM-4zxv& z0rK1Ystt9GI;W>9L5zZU9~o*3?40nC?-crVPV=j6#Z-kVam3Q5z)neya#&?w$P30b zWBqoL#U*AQtZS;&&QXly^y z?Jcuvx&V$pts7maBNx~WQtZh%?mu7u;X$~fbO+uYr8l#J$ZwG;+5dbgH%iKw7O|q- z2vAx+w$bAw1p`eMQLj=!63(RZ^YS1I9%+FX*MVS>WlmK~_A$W{oYo^<3%EootK*o0 z4f7>pl4CMB;Uy9B<7+z$#fdFw+hK+~rPl-f`W~!H*E85Z&K9b-Z94M9D?m zBN+;4sg{5@lro5JF930}dV>Pf$yZ8{&mSG*fQOc#%;xl?F(BOJy*91aT6b>SrD`=$>39+5;ki*f{TXsLcLo^m5y&h+O!uzl->}-p$EXlfO$| zN|+;h9m%A*NO69Zro9t-*zT1ho$r9qr)dpUYOZDHI2Oa0jEsmcjk2 zW(wjt#@-O3VL)(%M!~}-#fNGKb;i9w?^c7+?|8yCawrO-KS~g_0UhoJ9^`y)dp_=~ z6f-YHHtAy$@B<)JKy)&2B8o=q()>mt*dlON%rO8*RpZ9k2*gU((}9_eOuNY_9Bi;j z+@J!FEb1OnvSKw`v{;)hnz>l|nXvWu83Z}PFZf3eUJFb3W$uiHgjJjO96fdA#^cDO zys^sPJI4R~Yxj|pm#;s1nfS4y{b!tN2D0h^+ObMX_~X>xZ%z(Kx0FyOCG@4b9-fn{ zA|;Tf4Q-D9mJ8jU_6}1u?!tEMqoJSO4WxvR$S^t`1^=VdbSYtWaP-O?Dd9`5nsEn9 zVlO3h1r$pOHAzy!0C=E78>Ivu5o$XIRi1>4cN;9xk5(tt$jeaiiZf6!=-gXq;@meW zL2!8plUIfKf#$^g*H44dPPK_Px}Z?p-Lxdy2Q7;18~}MSEs+vFCoPo{0%|*uu~RHD z(k~^9c}odTLgCGYMTppW+f`Avl#nMS{8uxl_#|K%H`gr+-E0=Jh9tjO)d z3W^z1sKfxlEJ@h0X0%93NHg4$k?eo*dxn(oUXvwOt(Fq<2cI>(_kz`EvlEop-|2De z@P2Or^D{#jiN^rJG5sp85D>fQ7|O{?h`O4NU(1K1;9@g{g!z%M6zGE?bvk?#GKLDF zo9*EUwj7ov4;H~!_oEC;M0{o2FRW<`Y~w}#nNtn6Cw1G*x0M`iqS(cZv*gHgme@D^yj}r z%MkHzQbL4(=X*_0j+9W2NNl!UY=E}=G*!6u%%WVYIhH}QZS*g@Ma&Y{CjoLkWx&wM zSL@Ls&v!lRp0LNS#>A@@icbwP?F`YX=$#BHVLd7)f#hLAt4miuHV@qHuL=EKC?#|r zLkl*09!lPY3bIjQ4HRA3@culAh}8xDeptB#6|19Sdmx~T4e&s@WCQjDNah-psGSQ> z2QDd;5+0(R=4iXIABIdq8Bx*+i~VM2k`9fvs&rh7J7oD{P#J_IMLZZ-u!AMObRx=; zDzq@SC1JlBNaogR#e~M8pTmyJI(J(Qt;Rrx`*-KGN(o}h5`grs8UXxqDd8Wq5Q|(V z_XJNA)kDJ4psOYZQo>C&V}C$)3`I)#6&0R>h)Cm9c*>yHvf$ac)jk_?e6O&YI^L)95q_>N$g17C=RKAaV9Q zlc&%%F;!4Xhr3J+ngnVFjCF*eg@J8A4QBpy0ci^5`~&!ees)|fWkA=UKamtk;4(xE z{pGGN4c~@}(*dsg@IIdh&PAu^B3x9w8Mv_$Iw>R2b46$0pDPWM5`;h>bGY|=jyrz4 zn_5!ibgMVNbVpdJQ5I?Z(l*gC2pCmE3R#=p*XXn%X<8);`gs4XDMNs8w@V2HB5%$Un+qAnOZ3k@tZ(FqIOZPv63~UBqbxBLZp!EHidq=`Ns?J*2&Mnt^Tfjdb9IkWvj{Vu3 z)={#%|KM}WH#PKW^ANX(mSG;y)~@5DU!Zc}lK{y?E7FMyO34;+aD_5`f`v>51O`VR zVoC`&KtE0QVH<#?)d7ySr3?`OSF#%II>NvXZ9~M1ftIFCf;9oE?wB!vRib*a7BJOC z2duwE1<>)ZD`$}2r;}$3owf2m@u-?B(M}I0^nAgleVt{k7i~F;zZ}moNqL&Esr1j> zQOn%nCExj{|3L*xXweG?K>RphO&w`(*$KGG2PAd(JO|xxy?ndLMhk3aCy10^0R*P! za73FMh@s)X;UDnWL4XdNK^MF4qe3RyeLM-w1HUtvf#ffl+$aYTI`%vh{rKgXop-}S zhsR5z(Dnw#^mwo>JOh#xXg`D_xr&w;4y3ICncG>~``I!?Z0qqdxDQZ=POrf!@cugn zCLdl0l!S_{&tnKk*68!%Axz~}q_dQu547|U!5Ad$IfBhrs|nfW2*?D{ydHpwV1Gat z0F>3Ba-sjgV#1XT%y0�y={tCE#2$;{^IS>6}8+p@nX%-?he6zE5*|T*`s=pP+fs z9Tr;+oC_|(SUV1y2ZpV)pZ8YB*YPwWmX{K4Bi#*ZfbDTR0D~f0P8Y(`X=pD4{uzjS zAabx0!MqUhc`4x~Fs5+x*<=_Jj5`b!o(06e0Y;b$oDu}IN|8YKGy}xNc4}$nR3Qkf zUKn~L8(Vk}N{j&h6+tI_G&Kybr3zP&m-Y(mOK&@BIT0o3-~gzRudz?v%9^7+0XEOR z6r3)7+A#RJ%jFwX-H!I5+%?#Ymhx!^*BRjfXXmlReZZa2Kt74{r36)#zLQb{U94ItCA>Kh zoe$MA!CV1=EQIQd98fV7dUVWaiGgH?v(6W^qH^=zp2rV7-vblJXb`7?M8^MVSn}F< zTZk(9>E%d>nhR8iOdl%^2#>2uM#d7*VPILhs1zDp4p>0NCD6|es94TQuN+p@0oZ^( zg<*KVpx|Y|^hbsW#Q($<=zaUGV|0N!B2mO^X+^{a61o7vZrW~7t<`6WSCo}PhxPxo zSfR^Oz2bS|%|E@9)ah4A-#(#bZ1E=O;_au=(C%youj|whrnLvCru|+YD2P|3UDy>M zViL9hh1s2?GeBPe!7*bs^fL*pkV?2vGqYF$9ypJ|7$;}Lg(`URD``3$m`pNDp|L&` z`{nAp%;*(c0BEO@*4KLihV#}0UjQmvjLk&EdyjtuM(MOPDnP{>cY^9M0}Ceq+Y|QE zj1k?6?G#bV17M2m>U$O#30w3*N`M~tdotnLB@i7k&>aOkAoI?GKoa&hz~}`!00LyU z0$l+A9hhxTgy?;tEG3kfZ2#EI1+N6YK9Mw7y;7yCAgjq=Y)Azll0NIZq$5$jqNeNcy^v=#V z4S_{C?Thcq=(=MP09gz39*ePkK8WNNT?A4u2nZD50grqHqh>3h6W0hruUV=W3kX|+ z6E;p-IJWF)jHrTfDOzBm78`TEr=aak-*_t5fECQxaGe|G8CfpgLU+9|EhRkY)AvIZ*(*aOfJ88mJdyjIoGwqOTSaw{GPGib-0g%1i9LdBav((R0z(usO^g<&vNtY5pr;p1?2}H@pH%HkD)fCcr?EG$~L<7e)W9-s#4p;(f3MH!% ztm7!q3DCnH9G(-yCWfExeQXoZ)Jo~9(Y3}}MKJ48n|cLIgd|70c%So%qs=(RI>J{! zobPM~6t8j?{R$8T=<%*$+2FhaRq$MrJ2LIvAsbr^OS}t=sZ6jAa1l#FFnNZ6gdH@} zV@o1$18n2hZ>&dQ#N72I=;vA%*gw0LZ0>#iX3c?~6DF07Dd(4lb=fVahRd1%y%;k( zX784deY{sz_}C;ItZrls6w*ZJeXU-w9T5rPYEqr}A-E;}5%0}=_J`d&2rUZQ#D(_; zD1e=5>%{=~Fti#v@>ilaTM~R5H|N&tW3$-UZ%L;M4gdvcrlJjC6H85nP|GxBOU0~YrvU)$H>*Rl0byA6A=3hTD6{Qm zP^E;uXRwtGFD_KFwpo{JQm41>2g7E%O$3~K?Or~HU>a<(ykrRE%0l4EISZ-KO~Dg3 z7Xc2x9FJg8pk8OZ2WJ2v90tsBb-qKaL(?-$K!E`Xcw3n5)@e3o>r%d?P<*6xwB)hx^MdnILj47xq5@!@PH-B4F+dww!v}yx}F96>%TFoQH>t!jDO7EpwqOuDGRleL{SeMNyTQUJRQI|U@zYlDhbse_;@4|~6N z{G9)sK2T%5(2Ypw-b;$uI2@UHI=ox$S>j!Q8T4^o(&E#@Ct^Q^_l6usI?S%3pAUww zcirQ6aA^-qtf5G&RDn#XBrY)E9u_!D=yVy<4Rt>HEs18jDWzO6?j3)CBB&F)n|&ES z16KYS;BgCp1n7t(UH>_q(F50ffO>2&LRx8WfS0&}k#-*D+l^km*nEf*OTf-syJJC#B)jaO(&sKYIFf-e-QOBcP0`}IdP;nRT zQH`yeO9F`eQ-=jVVPKUDEdV)hNm9bTuryTs%cCJ|FI})U5W#LNfoy_;4+bt_WvWL= zj}KkhKeF{eO6ayV*n;fl)x5tO{(AdxG``DV#a^RxZJ*5)E_5;%ny@kaU8Hm|P+fcX zD{ee8KAIa>dUq?Pfwoe4StYEwTT44jf}s})Wb;{{Y;hH4_!3a{0FkW6$!wRo(j)8g z(J%*Kr+IVUesf9MubO!6v_iQ29F_1l??Mh*q!nCfAo0{p!~Cz=mn1G+wrS1<8w1MT z=l3fYZ>-#PJoXIm?Tuxs2)2kl#sttartieP`L2AyFuC&;S|!?KxY(pU8xd2)VQU)_ z(a&~$z*xTvU_G4NGBp&Z3?ZNtmzk>fnwDwBnb}gY+oc;V>B0rWGdHgfNERG(wcS$e z`eFaZ*ZYq?dD+sN{3*NQ$*~cgHhnv}_^%;q8D{TZ*GFM9!nRO=14-ELFvc?&9`hJ0 z2lGRt;q|)X?}8D|dGS$0lE@Wg1;)mfGuZ-VHB(H@7(_K+egmKp_X$nS^Ul;NB{8r& zVLLs@v(QrWhb+6_C~g%G$;b^|Ju4sC>l*{q*V8chzdbeyc54RZ;gyI8#bP z^9&<*FRHM*xq@UCeC*;|k0jmWc2_??J_i)}Wzfv9_1EMrymQBJumtIxzleU#CQX8< z?6{}wc&1Ja4g88uPefhGX04-wS&4S}dw`7qvJC@C)V=I?VTgoTDX_DjsR z&nrOFEImKVes^^EEsOaFcUu0F-y7Ew9dW}tiY_4W;}=F@{PWnA(otN{cdXAiaK)rj zkHkmBPfE~F>Fer2?&2i;!XBs`z)`Tx{!W&p@(C)Tg&^X)9{_4V|6G}Ad7=T%0GkEt zV0?ZN$*6Gu)zUZHZVGMQ&!=zx+gN_n;ADEp+iS;jL#rufZpFsQ&&>9OZhHFeip$8o zaKC%cLl|8BgKm#K9h%wetGh^JEb$=Mc@Z#h$J&-kavVsoPk{@LzwfXJvNr5@ zJl%2L5U|hP2vqRb)wepP^y&RC)Nb>PYi6E0=TH;mcmM?H=b&>Js&UXNCd-vNrcV7RQ zsDTWk0-vhV-p4JAfg;CWfSgil9R@HG1NgN1ufB}{ z#*Z6QrG$fy1){Lm zr><_gS9vxO#yh;|JkV)DIKm4Dh8qNcPrMl9fW*4cZtEH~Si1DIA{z^hDK|$vMMF)hF_?;~A z?O-5?#cTmGcJ&{V?8@v|Q>=(C+1P#brFr@PL(;VeGTndwr;9F1lH21_p7gkG(S_lm z_Nd%juG`EtGFJ@lw>O&_u49vyM;|E3`I+5NKI74aw#FR)TY_@ch>I@WIkJ; z_j#Z5I_JF3Iq#1=&@Lcs@3pjj$PyE1p!grKeV*u7JA7Ghb`y6%a)koIl^zpXGTjwB z&HO?cEa6>g*}2i5w#SCNu7R*{qXP8NAfj2d6KcXi=Qh+})E(X$QhvJlq0$Gt-t1h( z(GqIDA#2HuYv$bW_}!-#?)S0mS)|KQ;C7?iZrNFAbD=9upOf8A>5vynb7?855C&VUZ>gQ#!uH6qGl1XREtph3O!W}Xf0bY4CU zYk=f5V_T#f%5L@Rg=$Px-@{yu1K9BqV~YUWD`h7Cpw3pNxvCb3W4i?b!&DacW`pzT z8}Aek#CMS8ZcRxv2a^8@Yg9<&J>RecfjidJzI1K7{Qhzfy|Zx4tU6*6(`L^}wy4=X z9Ihn|Ohh+9<_yPK^9o4Ft|#~lA1ok3gTfCaV|GTf5$@g9OCa5)m^x?|-{%4BV0y|w5KYfC1))@FzvB2&NoMQ?$ygP3KQ0f|{L2>?!) z<+>iQf}ie5f4#l*UN+Qqw)_9@w>^mXmQr$a6^M2E$V^}qk^P&nH9o#=P$nYwVkJ*- zc}ncX2SbSME|gneql_p43*3HlI3_?{<_%`|QC3ZTSDDzH8WFz4qcGdq?r3$EjSqTl zD7QL@;I^Hll)vHGJ?wl8pCmi)t+?KB%azDK&_stv6gGSgufiji68T zM={(b7f-q72< zK|q+3hk$xh05m{<8&SU)2m%_PcihF91s^RfAx#v;XA%TpS`TrE_?*Y3ex*@^^+b@i zyF%x&9L4C1Nkn4v+bi1)B6W$Lg}dk;vI84m<9DCgpcRSntOQMy{8hK9-@~i3K14(c zGCX_t6~VlA&`IEo@V6@cNXXsH%Jz@n+PBNisgOq{S55!h$WHCMTUQ_quly}NLS8Os zHgFK4bNpkrEjem@ zzFC*`IyQ%K{G!4SqUm3kyT}{2(bZ^cNKd9D=fIu{;ohMlh$RF`0F^I+rfhfk|3UI7SQ;nJ9uJPmoG>?-$*(5RyBy*MG86wj!dFVHu_O1c`K|AZ7tChIm7TUceqaO4r}F)ZH9bn_15u`FxH-jIAT`A zWU5$5=eq=UPO0n<`RVhDwp!N7l-Yl|x5(CV_dz?4IYGeR$=3g;b$0H-?8E& zh!Fho=oHAlK*Z4%fHzyTqhL99T9RY4E0F#Qv@@L6l|sS5okF1mx*W4wC3nDJ^|kmN zCay={USM)Fc{e9%E1!bnQIsb(*JO3?8aje@zls*p?-;B)p%&n-6#Nmh*K0rR#>@KM zsS?G4`F-zi-2sn~LK}T$bT!PeUkJN`Sv%==*5ZFnNx&LA1@VTBA)a&DF{k*J^e zn)uJxrcSIN3FNw5AZq2a2H_6YV6b9`ASoeiTgnkZ#svttHe#s1fo8RjXo{6MU?Q## zY3o0&@t0Id`R7M)_exwaMKEE%8B5>_?_kC9($dO?W9u3;yi&TNzADWEpUdsMyLrTZ z?os4;agF%5w{=N)ZQJ$&aZi!KJzj@n68@c@iNejk&tept>`#@uoEZ9Uw$b&(WgF}wtd#Ox_DcdMu<*+X~@6-`z;3tGTjL9yQT#ZTYyfm zfpUdisFPN-u@e8&5%>NeGVe-Bsy`#WcW`5opYBkO=#Z?RASxrv`&6)_$g|v<D&iLKmU27`q#PokGeETNtGt=p0&=Wf7ZO?v^FyHekcjN z!wRe0HARv1f!}38-ar(o$OCz4;Ule?LQ2c$1LydQtB{h=0?X<=ufNHHM6lWvumxFg z=+pMU7}&)bSf^mgkQ2ILFF4Fqp%M@Ulw@D)E}<^_xwOnF?>9WdLLKv6`H}p+L!!JL zbGSNqz-ztEMZ%uPA0?~mQwhI!B}c6+^{4U^ecdSi84kWa-slh^O_P9nm09b%d)r4wMIT^T!v{Q)sd z2fJWt>VLo8!15<~!2GXo)PtlT?^rn)b$KAW8|d?Mt&-&FSVOp|Kuek=TEGUaej{me z^bolHUVO+yt}MkQsZLA9VUo(KF!du=ZRz5)%c#|2o^Uo84hoJ9P_)C!RBUh?{V9sd zH%Z-QPKxY{G)vF=Gj5JOCiHSYOW&8P9>j+qiGAQ_Tz{UFsd~tj?=U`axhIAY{^Nk+ zKVg4VTs|FRtR-2q8N{3hj`6Fw|LKSp+y?B?{BgK5D9obg8UWo}7vaV)#neC+(MO-0 zVQvrD4GgJ5ki-TG{jdp(-@p{U8BBPzN{%7IRZ{E%)y#y=#frC^!X}v**PBrkI!*SP zZ7g-wZX)8{Rhu*(n&9(<`bg)2`}fT3#12Za8}H!e3)Fh=C!wdx60IF0;uE>=o9uk z+9Pj}G{^wnA1k1Ma4%!Uu0awUWEp(Huaala&Wb^BSefpY$IxfB5V4NyQOA>YXRyng zqQIPrrM0LYC@@Qx2Pbw#EI_=&LB!W#HET+M;$QexkBV@+1FZ$W59PX_%SoiP@so<< z2kpec^}0hL6Mvdi5yvum$FL$J?uQbencZqP@fW!M&w}I`;WckFnwski) zk!~A=p!+I)?oN_}oOV0wP>$B5WRik+-I;q9r1ICSEJSo$Cj_fPJ)tBjY*jD9U%pnC z^h?6hZkbW3?!evZzkxl&MERjdjIolS+*1!kv+h_SQOD_;GlU#w;nAVuH3fnXP}%AI z-*44=KrN<+$F@qGwZ7exe!?#*WlDZ29}!2Jw7l`KB3jZH?o1r0gA+zHNJb{q;BzYH z(CFAlIH&xpEI2)s@$ilKAhpe7`~7X7PKV$;kWr^IQ6Hn`v13m?m8g57M0fOS?>rs2 zkf?B`yB8WKRrBdmgOsEDK!DI`Q3 z0vm{datst#aMAEEH%5?zogw0Y6bzWa60Ph69LdM5XS)|HVCPSV#CQ~qNJz(`fAZ&SF_5A(*eWD zm%9V)?`+U5ZLs}m*0kS{vygZ8-dFL#tXUeH`?%v2YEPlb^saWths3Vt_hz#I)_-^2AQmZQdjyb*(;es_k_ z;Gv<(9}t8Av#wsSfPCKR&AGz#p~0S}wn8{LSS!{1(Zn(+vU8r9M3WL7&Nh{|Jk<6| zcbCZDlQl&J#YOHlZHX^$+Ixuk!o|Flns?FamC*uen65-lCk(kg3}i-5B;Mt|jh1U* z{^>&wsq+`oC(_>-k&1q}Oypher3$d~hWK)t0wG!njN)czu7Hs*S@j_v_i5lqsu&JM z(|zjcFA~I-B_N-(Ucn`>F7sc&9SEyTMk6CpaJ$qv|DPu-LxF=vK39=lzT{MEEr2qH z)qr-Z(p0?@>)b|84AR|zbpvO;fc9+$KhcZ`7YYP5K&od;e7dhPZru4u+1`l>Uq+r| z$9>(-jj#qc-fcRQcU|mz#Ahy=z^Quul-Y*YVhpoGE4XkeQl8@U(z5SM9fJ zxHP9gWLg{h!Ar@zPR`751v^8>t9zGHJLS{c6J4$R*&t1v0_uGD){*|Gys*b^>+vn8 zeRKc&w|bvH1RmKAxhwRRKb;PYLVDYrmkKO+PZdD;JV=wG7TvJoc1e$F+Sy;#$+4Rh ztH=`725yE>$}iS+GjI=Sxlrpfj@M8E2A-01=PP&-Da*boiYVbHNEQ$3N{^?Uz!faz zSli!ZPB-J^&7ykF)D2(jHpuZYrB%Mkwe;$;FUMVoO^sNQSsPA&SH=N$yJLVWf1#_jLA7}A7w0d2rL!+@3(axKb%cbV~3f?MKu-&JnJSeVK$j+ z<|z0ZCSu1{-TrFTqH#$?FS)|E@u|OXu&v_7@$m}3yaSpl#(+mp`X5y#qE4K6B}Mu1 z5#(!!lpK{;f~|aFW(e(B^ww`%p-KnqTzP>R+=T~fIdh{s{0aqy|3DqmmD`LFDF=~m z%t@XiHdnz7QLu|mfwN{393+$gW{&}UQ>LXwMHaaK^(XpmatD>nD-S2#^Ky0CWP;08 z$uV^{!B}yraSywMXpt|*pSt(JS-SF!UyX?!)l_d|U0Q8pU-#!Cye@Sh(NOfL)_nz= z#L90pieLoHJ;k8ixt+Nl@plbR4MOvMac8M6KVN&vNImmB(oxil+v2_Hgu10szMeRe z8)Lc`TCC(;I>h#}_7S8}Qy~^S^6-c&VKh=AE6@%A#Md9!``(8ot3-Ras zTI9tv=QrT5P!RJhlrWL8YNPGOYsGfCS7~zgO|i2$^g|0XiNcz`g<9gxayxST<%1!= zGif`~HoAsISe~xAIgssriG^#{tU`gx8X|MdcEu z1xn#xcSD}KT|B@}Zk^X{l{P1SM8vpN(7s|(iCUwv!-BaRcO~v@%4(cU>vuK6O}Jp& zx}sb~^!j?U%k+mn?+vxfS_~5c97D{KJGH)}@lbHV&Wugx(otux?H+bJ}BR$@HYAWr@kv?sADp{ zv|52(o;%ki9~F!f?sPSPE+C05PV@*oN2``pVY8t>LH8r<(i14gh)(F7!G1};P_Sr? zmB0d?#~6m7*Vpa63m7o%DUIyvfJog9DKxu4;sFXx0oayLFqVaZZShETpGV!VY^Am>$xT6ZMvR4A|V5+pIlY5R0yX?njp=Is+^|3Ezgwd zFpV%l279sdf}~8MMDs16QA!fyfijV8Gt(;D5qa<8Kl+RZeyE)vx1`x#o_JT1+wnnM z1P_ntzqHrE;y?o4>EQ+3gz^^K4r=EO#>xvam%T*IxRL%H*s=P_7p~i)j;GUAP)=Ik zY5=gM%|lCy9`7OT{2i?Lw|nsIUqrOl%y7(a132r~uCT{C6bsBTu+=`56(vx*6>4`Z zn^)OMj{kofZ?6;~Y|us(Jp+0MRn>T3#{RG)MhQoc+xMB*jxydXuUF0MmYI-qdo~eM zl1rLo>tzOO7-K@KQl_Wk_hKiD@m$Ty!K^EEe=Rr9PLewlRXJ23;0~~aZG?iKS3-+H`Z)c+N(Y&1bBjm*~1~UFr_wfQ% z`(xAA0q?E2%&hJ-YB%{FuS~pa=x%osGxCJ`kxyD?sbbsbeoivl3?&iLchf!T(M_f7 z0F*7hRwiKbiNXOo^IDzDtcT51bfp$wa5VNZdl1m|JfTWk$*=k_go3JpLcEfUUCL0Q z-6MwYDyr_?JkK?oL4(AJU`zgN~z_z|uh*lBG-$j|}pG zUk?1baBEavBCDi!51hPel88aA6}!f4FFfFQS5NsiI#g^wS*C`D z>#rkhE-j8ptPw{Zc3DiIoC+CAtZKV)NT1@KTqRiy46`Qc|7m6kG1Db#V^O#6^c6EU zRQ=OKNq#l)vGOy&`JjQ;S zgys1*05haivuubAKzkvu@307fU6F%V4MDzt`o&%6rI`Qy20OS=sy|rz)P`%fqZRrm zvEPl?_%a~<(Io#hl<8yo+)Wh=2^=33MZ8vwNJ+BM0?`NP1&0dl(&o)~*+d-Z$lse8 zO(#9F=3DYtljiGq-zJVu9OPBIQ27rIk*?mTW)5WWI;b`-D6bedl7iR!@Lfa3hWm)Y zs<|q8aS~1LSRx7615U+C)>SddnP9S^(0gOf+#vdZAd7BtZpgEnk>sPIVA5=HOd*DS z4C$$`H@uDRv#9lq+?A)NXq{M`Wc-#j#LLi~IIpbjss!fh-1<}NM>fP=(0!W-OE-!9 z1;fWmUxu)s4a*|JP^@_Dt?4e@e+81NTv;Jpg8hYG0t@{81j(iYJ)r%$T8m{+(}x^F z+DrZIj}q&)?*7rb29LXA?y73|x#FL9ZrzD8>LM0e=X@onIX--C^SfPt`G0Uum}!h#|_qi=F7T}(mU@;f8ZGB>6>E$r)PV@ zAF(QYC(DWT&LU%z9`xS(M*7Vzis5;<*a|RNa}53AvSC=UU&K?{_+s=Q44udj+W&rw zy{831vCzpB@DVHa{?$DExW88nYN|&mcb6PgiiaI!A)@g)gD@~*G9rEi%dv-T9yN!5 z6n>E$uJkg2a{nvnIe_K1s(a_b>kw;WxJVa7UWlxE3Cm~=p^@&+YkEV`oGys?6wVOaR(VXF-!`FTudYr z2&#?SIhJ_YGx~%mCHu=a^R|>6twla>k`2_ocphh5pI^Dl^>!6ICFxQ4F`5aZ$vUyY zVUCkyGt~cl{08q`~UeW-0K46bmHjo*w4fjp%bQ^RG zs~m~ASuHr5+}>oC^_2Df@=cC+?zJ#pTT7IvnfQb^BQG4EdyFSv&|B`t@{w@}9=ovO z7tp^b-VYfftj?!`eG8VWb`}!&JKg=Dwh*A71Nne~zmYN)$yjk)jYnO8KOY_K3dxtW z_of?6?)h}91j>`%5awO*an~=HSFDlw{i7gG)4NXWmUi0T=B3;dFii03tDQJgk^0mm zb4FV93e#Pm{hwV^W5p%fcq;|z|FC;@1$;x+WNBN2`>>iPPQUx_d4d%wYl5(G*9 zL^}-yOfcD2bVreIgvODsoX^fWgIz=9iFd=U%{KxKeMwfYZm?>aRu;}(kXeVREh!MS z=^szRb#JSz8!g`UIhXsKR`4CRseSI3A@iUrx33NsKKWHkssM2g8`|j5Is#AxDE-+M z8Aq1Z|L-^ZIcQD26W9rqNB|`VVcXJMrBTdF^QxWeVXr4_dV%fqAW8+4Zcp{{yyJ@6 z=T_p5I$gE0gkxsvS=;Yn(_vcpV5?2LM%N>P|2G%FgH+S)0O5GL`)R^#pz-Er{1@vx zF}0IFKdd6m@}|VtDU&$U&<*Vm^Hnk4k4m>2(lq2uos%4R4eWQr3fi646U=`bPd2~d zj-4E79e1ZWPCS}qm>Hxs=S&){VfzSWabw~DPxeu$J5 zO#4Ofba35|03ZiTU6{}YE4~VU5$H+Be8ynwVeNtsKU;yeR`f-9T~%Pv!J7sY3nbTI zqjlarhyqVnG;f@I2lH6%5XS2SJS(z#|^C0cT6S7Xlp;AM~g$|@15S>YO)w?K~a_4G&!Hg2e+7CWun`D;O)lQh`^iw z!v0*AJ&UjHi!){{M>g0K$sc zt?e6C>rzpdCNZ%ruW^C`U~u6y`_GPu#X$8CxTve%u_sBvIKT&*ipP zsu=Y8lY6LSe^mbsh-$9s5A@mrodVmfwpY?C|EswDY1PJ|ci8WVpbEblc-?RXLJ%VK z>g&_WH6^kDVc2s2I@xV< z*@*qry|vDP^U=tnrYgrMsZ@JOdGhk{%epr`Z_nL!swoifZJjrh1BsX#VK}mw(N6Ya zEu)6Az9l6%DBw{-RMK=gj{H&y>EyF&rU4Wyy4);zDv zaAcvCuY%W5*1e<*nz-6W#fmgu{1+?m=6mDg8*Up?m7EQ|wwlSgPSRtUf>U<{iTYcE zv!XgFRjuU2VbL>28`8!Wyk_3r*RFLM2-?0WH^fn7a~gI*Ywx0#hT`m=qV; zynLs0Bd2F(gPC^N&YCJS4ciL?KAqhYuLmJQ77udd$$sK+2MP$$jW$6VZc%8dUaVnJr$XQWYxNC95% z&k$z_p)nZgEW>`c1Zk=0Vc}^e?!bR=l~`{iJ)oai>Sd@hb0|fb*GZz*v1&{rn%z7H z4`kz5zW2ykO%HV=e8NnN={6mdUU{(|$JBFoQKggP3ql37jk(FL`|Kmg-G=a@x;=`% zl)O9W1M0U456&-b_?2pr@Pu#Tz5Pm?kSCP%f#e1=@}+*&XttimG5Y+VVpw|;L)9zI zj_q~M2;9j!Erka#xIlq--bmyiBl@u9z)cme$NQg^=~k}D(^q_;%l-p9w$I~wiwbs%gSQeV!WK{G z{?TJtrtB2l_Oz)$aLcc9`tb;%Fzd$g?X3d%AKU)){A~Ea+gPl~&O_t#!Yk4TS)QMr z#m1G2t}%&`O^)jrho={+8<-Pk&?YRuftIR$#v2JMZ@N8wsj`Qmq)bV3eW!PN6WydK zgpeOX%n-WGS`@$Ox;&dMd#F9o^9?8Dalt$)gNpIk?r!RXCj~$}sv@1h?LemVfT#f@ za9&11sq{0$S&03SAyPIVq6ff8ze!Q%h@Cjs6R4sHn0fs*gz#Y53h$53hN>9s!@O#ksEEi-&h<&8I9N zlXPYKs@L0$l@#SJCU?AzRLD(TUca^Lwh_99>%Tn^R~bsZN14mjP8FA`2ui*)UU*Lz^$d9>Dqy6$;vU_*TCLhKVY16-kmahotJH|b|>ci90)!89y(RMGaI#9sZAq= ziVHC`(S#0f6_q&l7b^1Hpmap`w}$5*Zpg<2 zQ8QA~;HJqz*!sR5k~DvzA6BgC`T-^iL0S-&fcf<9Xxa)^LyhxM3Mpa(e)k`m?geMZ zYXCPd|5IMtyKTS-C?35174=(%Q_ki$rUe=;?Q;5KeElQ;pJO1qvoF$Zcm}=L4T`^+)K#ZoEgwEfF>hZ*JHh6Q4Q!*>9&0qD9K2g~lzk{oR1FZvWnT*2!ro}WQ!Nz_=MLx1Ha9&q7jpdzGwkHXvN z31Ff`Wy=LWwcvy1wZ1V=pjE7VYiQ3@h57yWs1q)MnT~p=FROpbIr6#z2khb65{2MW1^kKt-WLhkYd6RB zgJ`!6*NpmvJzq><)uWgJ9lT zBoZOgPldN4Ld+MaoK8us3;q2CMZIF`9&Sj~NCnOZ`w>?H1BLJ|broo5kBh4W{782a z++$Tb9P#bm()v9#L>37bsW)H(wXHxrb{*~s$U9+E%m?8vIDJITr0zmq{y6u=hLK-rUOQl*1K`tdNqV4ddRNw&ld8$Bc|k563Uml&oRvI4_Ohr(imD^x+tDvOBJ6Kl&ar=Ex7s!-{35aA=}rb+p*)RTGph zPJIwrs5b3kc}8t z-_QuUgP7-ZmF1GjxmYm2JJ^x$I;`2%H)u0NdG^X*SZfiSt#WSGDsRL#DGZ z)2As&VwD$u5&H#K%d;rA{0EFm(Wp-qz43iG+Z!W~*KRSwj=hI!MSt|ZJ9=e=gc1?Z z&N}P$>(9TDl!F`Ek1WgO=7R*jmkTR4~@nO z$jz}CQ<|I~($4J?qpk&XIr2GiRl&R;YOd_~TH^CMd)+z{eTodB#_Wl06aK5g(`zT(bhCIjh=kb6ziW{L}(*E7$<&(~*VX95+goy6d zRk*Xhij&OACv1-&J2jqp%Ie+#nfMtbF~HgANTC7-r~ZzDiD?lgG6W4KBa5An2T8q- ztqTc1+dNdJjEH~0Zdbtd*e1@ECcNA1vktKZqkw;RvR5o5Ri+~&_+|n}qg|rJ0&dXC zufp4;rw58#G*H0QAfw@C?h)w+uVEpN|9p?MvpbEI57Aks*=zh<{7z39nYVPUA;o#z z@iebH(w)3iZIFH0%;N;mX4XR=M9FO1_3V>m@|oy3-(nkU-hFpt{EoeBcZ&859|-!1 zz(>cu59RI2HYEo=Y^X8WGtUXmPMVwu%IbOj!%c)d!9=DUnC`n8%Ux4oW(!KB$I-rx z?sxNnTR+Z@ZN1p!N226~j?$1}AzVuUDkqYAevjDLj0{#&I!t*t z&30I_mQ@cuNS`JkZz@ri<)6VvTrD#_S&)F=3zfqfx)nz6 zbp5AjqlA;O3wqTnK6Z3&&{(|M?<+yUY3_@cD`BDJdUT^H59D43AzeF}E9Wm9 zNLi{;uB*?Mtc8;XOn*gMushejNSbPv(_dfb6C&#mMk;9}vaL(qBCc&($cuI>qa*vO;)A46T}qjB^8WuSQ)~I&S(v!6t_|W{b1Qbm49bbToMqOyr*B zs33DGf9BoWrBC6eJwlDwMR{Pq>%VRGg*4rqe%edO%)dNT%*HU3{-iu74V6;gU5jev z%k_#daT~9aHfucplZ5{1>fMu1a_X+3QC{^%BBW(-MhS!;Am-ox)svQ z@%9h_hu6-5*T2EqUtq;I>cDicVjM&O-5_dpsOc>yffa&c$_ovgj0|?iwl02@WCO^d zQot@U@Vo}c2I$CX+C=vQ@an*W4qv0f+ns-v2S1Slcrqx26=-RpWN>jSEht0z8YledmmB`Ot0j z)*&?%kl~m;ypo7Njn$HB1u@z;4oH;Bs`T;begl8u+yxN}-0xaylFwXPu*=o7`z!03 zR&|Ii6RX&_XeKsk?@V}+s#B7+WTLULKfjCPQOP{P$;xD2!ynzn&=}5VhAB;=)}PDM zb86S|?G;w@{o<7sq0(zFU z2+>KjVYK*Q66qdrl0>v=z>%+FZH@TYr-z3Q2|}$OD9DS|PrNSNReISX(c}HgkVK>_ zKhl5{>Bw)4aWG*Pd~@Nd)fm-~BJr8zQ1758hcgq2A0-;UDoAH zYVbQvqRfr>|YwIhEM+5M=S3q(mNKfmh7)S_v+cSu&YhdCCtk@aa{y@95BztKp zky4{xR${gC9A_6H10UPtXO?STgO!Bp>#N25IoU};d${~VFVinbNbSB);F!>rnmh!4 zu_>A%Ch`4OtgD%{Ah0brRGWaQpRL-Trdt+LTU#FMwzUjn8`XHvCPM}D%(E`2PIB4~ zmr{j{btuJWP(}e!EIe>--%J#%b|!*sv@Z>wJ+aa-Io&FD;kMpfApwx zC??I?Pl{$5{S2t~?-YCw+{ne`L`M1bOjq3H;C$hrM%R%N+)XLiMP6`O3>a z`rES*9s4eup7J+gT_q@B8L6+&RzR-zMRI7cMb(N_#dT|XrzP`lth#q=pR*5_bmE?ot?3n@Q3HJIgsF<@D1%F3_!ac(WLGyGLB2)wl zK^$V-xQ}a!rxIat&n%*w_DmEv9oEYir-+y>XH)DO68HSq^Tul6n3C!SS=&$(J(I%s zug0kztE}4t;Lg&9+FFHPRp>|OTwjz6LM`n2zeHr-?`9K%xD=Tyb$JnQcSx*U-}Nz( z9*a`0n*H2ILx=}d3^_V;FGyL}-U{9KN7Sk^cu$M2Wt=|XSf@u>V=H2nru)*wNPE5| z7)j`Ul`g$-X-3u<9G~Q2lb5=cy{Sg@#0qX%hJ+3(;QJT32^W;qu zYcGvNy<}LPYH{2FBD<@a52h+jt$|wvrwJqU1(KWC`6a~m!v1jG#{zIa2Odw{jn$NpkcAw znaUUa8%$;imZXgpL`clnX`=RrAeWD=Jh|}0>|!q={^Q%C-JGvsN05)x-%M+#J1SAzqWtK& zo8LSX8-DJEqKl%c59yI{KCKRS{5*8fCP;!xX!~B4HB!pNDu4?z-|o7FRZ2(5m!#82 z8eo-(_*DUz@*AM}Qoc$GBvikdp&=U{sLD4zT8dk4p*M}R*b%AglpLm$PM>s?KdvU5 z{6ZHydS*DHN|N-(*3i!El`lffbFYud5C)lgZNjk8BQ9^YeJ!~pI92h)^FNxbKBaX* zHHYgt5~+4Z<@X53GP1ME@hePPp-&|~=1xonH%NQYEIT$v!3C~)z zJ2+2;xBOUbrYm*|gPoa}s@is9sJh>2_(#an36v6mVWr3)k)FNt%|1N>-UDXQSM;m6 zIck_8WW8yX{F`x_`!&J|#Q^}Wly#>z^nhTm|Lx_ys zWug*)D=$m;PPSioa4cJmwn-yXXWvU5O_W-p=X(pZ6}>c>AG*t`udUKnf5eL~t$b+z zCrB(0LeHiqsGC$f%3=9T>{#&#G7Te;6G!8M(k2%&G0H!V@h<2rx}8Te-Wn);p6f-w zP8xoL>1=4xD)Ud!h-F9w@GP*f?VEK9ahCO^*!@b8?#0SIQ!+C9t6tV0VDF8_oE!PV>?h zGM09|wDKSi-+dLrnz$Yh2Rs}>mW)lLyV&{-6wd-s?RxnekmUD0KLtq_gX)tP?mB7B zt543ZAcc+MS6H^ZA)WvBf<)z-uI-j=pEgkNlQYl~%x~|%VE%4>uIFjb{rXAXTOP4P zF2%*0Y>Pr06EcyfBv!x83_8u)SkO*vN%fi;Q|qIc|dtc8mTMZb>Dn?3zXLBXSG+A?;x$2~s3Y@5+GNngeTXpH6#3Sr_pzJCC&JTVNh3;lE=-#UYcIKe#zvNlo8D~#f)-( zI4Ud6kF@e!yD?D{fxXl%!pelV1jUoD7fZnXoZt-FT;Mm8o8UYp1T5n7EECiPz_e3#m|_gc_3o4<-ZtMxL3 zYPX&#`*|t}Q_Okwxm2 zZ;|1eh44%67d2l~s@T8NAM9Yg4?gJZ@qPB@AnJ=GN5y6yJgAoH<+uQKg9hajX+;`* z{DiYm=f}w!O6Or?dtt3j^v>c%AAi#^vLYuvpT1>&pU?Vkl?{mZ91M{`9Eo(wvY#(4wTu-5NmEUOWwvS8UY-Dpa zPJXN0)v~5DU{bW3$n1T9>c9l16<#m8qUhO=%5}FlOUbS>Lj9ya=n#8PKF)GeJ>`IYb@jPjH)C!TV8`V-_vQP%jD>M8;Ttwio2Nm4O;mhLy5C zdogg{MGPBWxP?4p1KF^pb>0A)33MnbXitcFVfqkqU}!;FpCJ@6Jga?Ll|4bmpSLT9 zJkK>{94huCzvwG(j87x4oQp;m&OR!|e3tw33t41CL2bo#U5(@>T`lQdJ2{gdyXeYJ zFeZR`qO5A1e`UmvUuxwL?&08t@gSvC4pF|LGW5py4Tl>ZzI3%PT5|&lnI$I z?ZAodO**EZUC6lAXPL&z-p{at;h4;Ox+=$v+7|V2~7S-yK%?POWX3C#pKu0pF1RfG{EQ-z^~@e^G5&UBw#Y*V0JAXdf_?~ z4d25l5Tn`|_3QF^EJwe@!hqoR!zRuC3;U+6Sugk59LXbmE)WLT>iDm$H?%eAWv@|m zIAVl<8QWK^v8jqiI#XLa>apd^*~0S!NwpfM>{M1M(;*AErT_U@TvC?;Z%Wi9keiTg z&%J0zRKIP5MJXt?Q`f=?7OPZR&>U1!sr%8`%=W({{yX_<^Zxh%gEQ~Ob4|)VN@TkX z?QN|5qea>Xc2V}V<>3b1+TeEbUQUNPHGo4_J7-AWbIB$&x42=#i61m{D8x7a3=S%1z)DJ?bogu_iyp1ch*+-)*I$rlr`AIE}_@rUY+(|o9p z#YHz(qo3u6l}VKUs#U+`L)=<7^t1Q~p@XK%tNSOcj1=DJ`70K(nM>G6f79-Sa{KspeOF2XKP|hi zu9gtw9$#&1cBQ(}@u?>oChOLEC26{8vly1v2lVq6NgnD$FY=QjW3=X-tTjI0uVu7d zQsLWlPnH~BQGK*`@!Y?Qo*LH-gAYxKwZG)frHtiXa5q%nH&T;i+ksLQZ&#viA{adL z`6vm!%uft^4tYY}D_>Ho*1W$)Sy9?-Y=Fqv#2{oa<;qMNPtOwo&!XGBO(c2IpLC=jJz6Z!dY&m+%Nt@^4{Z0vF`axzdejETP!{3A0?Z;LvY zzdwBNLoy>k^8?D7R}tD|aBtuHTF+VnTI-uNtis*t>y_TRuJp_P>*V68(o22F4TOd~Z8;Sa~e;8LTy4QTZiIX|Zwc>0pdA%4Uu1B30Xr8W?%R(Zz$ia(kU=`^h^pE4Y^?-Z{Gg z?x-X+E9ILSgqCtnpC3$!P1b$={mG$;DVnBD?!loW7KYDkMxXFqrum#hL>S?kev!lb zUdR0+i8&bMK?Op8R|UeJ&Qhp9~GHJ!?H3O1W5$!6cMe`mmVAd&SK~;zU(Q(&R@rl8LLjH^kw&;$t~ZV6g=_US%w`d#}kFMW_7iPs(l)!jYxnO{|p|!RboVU zb&-dv6iO=0bgcAv;C!sa3H}Dnl;Gxm?MD=BM}Y_ur$XFrpd=IBf{hJz$U5aMo2nc0 zLf47zWvCn!63*pPV~t}MieJmodY;53WuDdOt3F$=E9CumF`ueFzjZJ-WW&1T>#`&M zRobzfXEV5{S=LSW!{q&54EhC%->VMHR4|SIZO_d5jq*}DR z+aGVIEHgIX`hP4v2_RH!+bT*ZDN9IGD$AJ6twbeFSt62kFonq|*&=NyIub1?ktL)F z+4rTCZG?&JD#b0^2!$B?4C8$NbKKwUGG>l*-uHQ*eU*9>9^bTTno2g6cb|0SyeMyc z;VYx_DNVpQy6&=9qGd8ynR&><&yJR`C31t`x|Z2Ca%4kbU_WJQXYRCxY7tl3*-!NP zP4D32C57UNqc7iV5`Ivn$V(PUpRrDwSBY6Zl{R_$eM~fkGTinu|Kiz?GVHL-lR^iM zFGZ;a{<#3|NWfgaPpP3&!771qC-zLDQKUgCSK15ehB^`6I+QVw@UUFJ-24`x7>4R49k_DGq%_Idf`Mp3Tzm6Yc^ z-(R3WKPaV8LJ3(LqwAZmlP#iQHu^e~Ly1(Hl4nJ#?~eZ%)A&~-x9{cc*H#~5WeR7! zEcJx03jg593a>fnlyXdYefc@d{aPB%y0bQ|cg6(|FG#M}QwS(cSJ%Dp^|#UHDid|g6UME4V${~S4_{UlvmE{@&T;>z zb3=$zLGm?wH;R(K^7A><%a1;pD*c{w5Kt^j-t|W};TKUPGcBLEOG5M3m-nuDeS5;J zQ;&ZfrP%p05-nEWP9m`d+Bp5b$)ROKlF|^*)K+P=bF@J9dAeQUTyd4yOdHaWlnRKv z?V!#am**fgn2#d(bUJRKYzA!3DA+z^@5A=b0lUMYex^yV+|S@hQ(6HGfQQouC<8s--Pi=s zx^HxV$TXd^(pUeLUZ^;jDHvEtC^)JYZ$m->=F1Ty{bt7Z^4lB;jC-_{zLQO61*{hO z!n6F-$>(FnZ+?7m< zndhwDXMQ+8bAcIW`H?l+-=ti`dO=ip=5U7?mo>LkH5wPw3;6oN#`g}f&}8YoVFDFB zyKeJh<$4~~&RD0BG3;(x*qCcjmbx90xZ`gA3^fT{oRp;fk#rxTFEQ9NXRiSxk2eE{ zgE){xr9ISWK^cU}GHHOyIaq9oF=i+NL3kNS7?a6tgOogA?VjLuD9PukfbV9jaJW8$ zTVfs;`B*pX`TH%gixu6Y@1&l%K2pDCARnvQm|`p2B2aY5IYYAXYDI9xr&n^e*S5K_ zcpB|C)ATIPgBFt_!X|&xtlN~2nAyco7STf*)v2s{b*5Tp+QkAj%UwRo8?COQI!mC%a$ItNNv|4OT^+4*RoFl3o;^Ju`; z5aouH0i7$MX^EnA^~6?z!LAVaJG1Eo!bK9Ez5WU+RqPFauvWX?YW{ ztbRb_OVyj%$!vdwLwj~@&$ECk{FLx-nQ(k97>g}Mq+!crGN!RRV2f20>vR{jO6#W- zq(wCp$Bt5i*bQ!m0W-KpQ2G>7vocx${3Te&;JrXiHNnET75_1y$a84J0KFoS<>P=T zaLwYX`*hd9t^QUCbN0LaluX1%E58kf+>TefdyC2{5qYQPTw$5&q9A#2gEuO^ImsbC zsQp#KCWpbSx_!%8d!0v^|74#1hY6&-Ci#g(&PShuzUfujOy4wRT*5}74ZlXcg{H$p zhZq`%R?jNDj8+oPOsW>}n@hfhFY)=<)(T91&rz%jadjvJiTyeYkT;J$_#K?-zFiF7 z$edF8;vc45;tx$g@j%iz*W#03_E3S3Eb%#ydYJH&D6oFf!>)Ky3)h`6W|L5D99S=RHpt?_-g$N$}fU zRXK|+3Le(G<-On&*)?@A-qI&wYITJ6aWZ*)fQ6o@*chWo_#V#LUDEVV8^`eA+IJG? z%^t^}I{(?$0#E31H@=(yFV|T~;E~1$ADeMC`_B7~q|d#%8v;8Zp#)ghgdbZCDq-SJXVb#6)tV;SsF62C#(>xOwL`j2loP$=U~L zwX9r8Yk*8q9E5E^QCt=>MHa;oj?fp357(nBf%|xCD4qt9yPtvKlhMlHjA{~k{h79& z2c3Oe1O+SkbWOPo}ZUbo^;IGF6DGq-^y(8f@xyG7Q4?< zR5m50A<1mG)W!ZnnWx^jE|$^@!tkJ!%~xHk#Mr!;>J53F;=IRe0@5=Iu6DWNdkNR6 z^1R3P5$Dl-4)a}8llr+*gm(?t_zWzoX2lx%__jrKgKSWr+A#v7&us7xej|Kdt43pQ z@Q8g~IcK7_F`d=&3@s_7G1$c>pat~eP#dHd%ViWaEreU`0me85zHG>;3akL+#&8_r zG_K&m=MdQ8AP@xh37|{M8aTm+%3)DrJ_4(?dT*aMmv8j468E}cuKY%hyi=7>8~N)D z)jL7VD|)+xUW-#_WQ<*gjCzsFGpfX}&Tw0#QlYxaK#|u)tJ|ItmD2W)aP#Zx@o(S+ zv4mz89r9!#cb9~!As_!7XC?l)^oo-WQ{{0MQ?_8vpM0FKGp*A9*S+CUok5>Rpt1o{ zeuOfC%(&5Obxk-*yQc2Hm&HD1T|*^fV2 z<#+Fv61O7$<(y7PbXqFx7i7kAdYzPNziE|E+z^0D_>qLVAA4XM zQbmKbLz&mMXHBnK$mN|jUYA?5(X}QxmE{pLXxepUqy5xPD{GtTqO7?64K1X?9AxUmS#U=fV6HHxN<) z#3=zG9QqPjGy<$%1214D51=y=X)YBc`*-vx@*NNilpq{{3xL!>-*EVqOUM(EwBjS?M*74asPd z6qXbgmR$K#S0_zB4*u^;FF3H~|Gz$9j_jt3{@;K2FZAfVe|xKN&x46qtf_{hJ;(R% zZF+pNW*V3Fszd)*Z&LEK$3Lc7l_@ji-YUGh4HpSf#e$T-YOtn!}kW6gJ-`5j3j zXWe4Ko&{*=_f&*@b7tKygyV5t4I8w%p8whmMa*gAxIPjsRhDEH132Dv@IiU1zx}@J zHf0CUwpC+I&RS5+g)(XgB;1OVTYJ!WtwD}@S=nt|5y{wxj?q?`WRUy zyk{8jkyD*STiFN`41Jgb1wMhrec%CTMWEj##@x1J=O^Q}Lj9|NdGE>snr|UtV-NwC zTM)Qp;L*;}%qC*)MIYE%c@Hwc)Nj($t{UGv1=dh?MMbXEeP$>TCYy26u<}H{2J6n{ zZTZ(@`Qff51Lm|B12-w#?-(5nx^a<*m)qb7&&S$Z{o@FSyai|qe*BIAFAumVFn3%o zP?MBgn+Es? zhQg7AzzK2rLN)}$Yn@PLLMi;-1z2DV>(!0?WCmDu1$>RaEj24LavMomjXNi^RteC> z&INP6jA&;I%j{Mxmz6zy-re18wL9`_x9i49eL%P*6F|fpCLCedQ7f<mN-8Z zgB{N=wOoKu4T-lVq)#q^JgQX%wOZR>uU@?$E`oeJ%M zEAZ`19Q^}2%aAdQu) ztpek(o%4~n|0db$`#_u8mimdin{nkE5hlzMbU6cD;VF&{xJ<(o=@|@%flgXUm>(U^ z`Y9kViknNFM6aad*eebmdLK-9TninOs)VKXB#@@m-QRk2Ef`5z9etY|U0{hc9&=aP zm^MkrKi~3deBr{Z_CT}=r?1Ch4aoV;b6#mC*JmS3kqRqH3T-$Cs)ioc(Z4A`nOLkCr4&XR9{>7*|}J8t&?)N!Zb3Wuh5O3BI%-XY<@&4KLm-Y&IMb^S@yn)ByO?K^~bHLp> z^Fyi~xNKZ(XH(>CmUCxx-#*&9_$c#T8xmBG0hj&^NzdzxiVMff<}Sc8k{B400AYvC zj*l}SuXG)bqdkZg7Xi=grT}lcyTagG5*V}ln!I}05FJ}mU+KUQ&`%6fzG-4>ap>{p zyNxy_CQNUU@^++0jk$gJF_ZCCTz|&mmkBfIS2_{A%|zH)O*qiV9$5=ySsdZ+1D@r2 z(KsK>uNWXfPCWSTBsH)I=&f8|U;*azsr==vZnr65G)x#ip#^`h;c|jQS!sEn0@Ei2 zmQ;?^PC0ei&=Cno@hf89PPYgIf{Gz9$6!@(corgW@LE{*)&@TTPEHe8d0)^i zD}L1+EB`*d$eMuyALO?ugz7M+(0>$YHcD_sqTK{CcI^lWiS-faAI^+f9E+somAf0&*af=O)5x!fJwcf4@C_$makZfdDhXLkY;LibV7 ztrf73dkXsWmpz^8Z*jXOgR0VbeS7CAD}M<6aQ?^lUjMxghz6`Sb71SnMW0Modf-c$ zTh|>sHWi{09xL1UfJQo1>9_W}Bq)3vA||xH zYZKzQbx5QOv#^BbXJF~f)2v_&(z)@fK5iS6)2BB_g*D!zUZ!h~GY-yb$b?i1%;MM! zRR35Ifk9G2Gaw;DaWiA+-+zsjk{>0ro(*VE2_J~de+;YqbkaW2YMf_dOW;qA7#3#R zWC}~sivJ)fgm;G*%&x@qwgQTKG$z+ebo!)V>L|43An{584y|+cp4M5&BCtuUvAG5V zYczEa$Yd1&T4_b#x3zIw0t#vM_fURlu9H+qYeL1mD90HRvm{OV!})U#`paL#e-%F( zlZxP9EZWvGEy|2HHMSNc4Ia(2W+x2wTAyVYa|wT@s8n#Zt1VR}j8!H4smlISl`vWr zx*;5T;p#XPbRoY$F}42UukOK^prfn5z~a{HOe1ZB>Aan%jmd($RH$&g2kZxtsOGmWVDp)Ea+g)byO{vosGcfO;a7HtUOrjka;F0A64nvW3pEvH;PTk}9sPNy) z%q{UgXKvF{@`uCSj(u%DNuS85#`n#>yR#wZ&k|#A_^q+sw+jaxK(L2N`PvN478g$N zJYQ3~0e?In{f`d`4({=za{LHKZ8$|Y&a`4-PWBS_W~KmXo(X{pp)t$~#$$HYBWFjM5U=Ze*g# zO^@dZkK0tXNbYZoI6qOD&x|OMzq(kt7PBCneNW6>7;sU&#CmbiU+9)NA>q3}Q;ao# z_KAwlcEdUR%exct2FD2Z4Yy}pA_VwJ95!&G=32Z%DL{Z)?qU>N*d4J~n)LA7y{T|a}sd4nQGWN2#pFpU9R{p>BZ9J#N z-33Fu??ufwqq9f5=DWBAcXtB8YCB;&A<>3PW=1ogomr)>8JRQc;Zr2(S@Vg-)j@YG z@@>j z%|OFGmkfd{f`O`)0@wbUBLHW<2_XGUMB(9R_~TTq=oq1hv8jsP&Nr9d?Z%1AA2eKJ zK6OWpLb^_;&c+>F4~pzdP!RyNV7Go-bQN}?cM{eK%z`R?IXW>`s>fnUn;E)!k5}_y zPQIbto=fa4=l354ahMa%eM#(Gh}Pc1&D@_Z^2;s_iFe$Y;hSFsf_?udN5@I@;^v_x zh=K_*J;e#3For5qL zk$2k8`5ZpdOaE$9MxP2v;<`OPO58Z3tSXBrdDGH6R;u5OQzc)eIaD+b?0B>Z-R0mf zDRG8!;mj>R?_>9$Sy9f1YdtbV)Mi@sr;%uBEr17b&P-CD_He*;+ujJh z1KZv8q{2Y~8bk(}pp1Ul4?^}2xa6g==Ee({vxH|aWp7=zlwZVge=1MnN$mV^=9ST? z>65>+R6VF4irbD`@U)w=MBefwc8swW^RpHcMzb~n54W4`v(vxdcf#S!F?f@hS`|V= zcIodSCcT>TC(G`vDhk^oMFWno>9N4QNKpw(i6J#Vqt1VZp8OalX4QS{rc{e+q^8u8 z8@^=S+v}Du?)(Z_stR7PcwxgBbh|>Ig_#=r?<*{kMjy**NnCt>{%9BkpZ1JbE!7^2 z5&l~h%ey!3-y`o0lsg;S=XYH-MmL{t|CG3k`=Og2g^9`t;W4>jFm{SMBpt zU~igs)Nd62Y(M|uJnP<$Bct;IceN_07Pls@NwCCS0;A@r>+Pk*$Cp*cc=kU_My)^h z$z6*5;W*$Ibw>Di&QHH7Uzrn#gr@Ud!2_3GFRAQn8p<&|Q(cs#b;i_pZ7R6aJMQ*c z?YyoS`>Dr6F?3DnxM96iJn4qOyv?DiBePLdPG;A)TlZfKDB0}Q$zDfA-%Xl7!(W^P zB53<>lJALcZ)B2PrPLVAOQ+O%dd`d+8?xrD?8kb~Rg=%=ZoS$Uz3bojIM0$ zAomlf8$Jfk_{Z#$@y#AC{`-2#WnBVEquW2mt`w3@+JikqOfCnezP?=u=ETW}HtzO= z?blAdEBoBb;GF0L%-So;Y0PjvT$ZV{GtJMP$a#P|Pv5vObK)bRLNt4A!&lGuS$=&2 zirL`@PSkww;@R9e**diAJ7Bso1Uv8HFT>DCU?V2*Kfo4JtNDws26HkZ13`uTSbtA1 zb+1dIsqJXG+s+GR5Z6jp$X#DyY1{6TUc@8VOC3%tce>kUdDJ}Qp5@hu+poQCiZUmf zP+4&Tr2N^0NF-YcXurDL0o?iFS3jG=lPj5nZccw=S{!u0p#MxnH-dB|ND5GVz!0;s z+C^+JA#!H(T-G!7pT*sKC2T4t@WW z{Z?2YM90?L;KPtk?jvoRycj2-+?^Bkyb~Ik{RvILhl2{N=?9C>m zq^kSAFH9{aI#oidAv+FyG++CKoA$(|*;=|wM#V_Q z6x{$%k;L#>{Tq7XvDc5ey0A5DhmJ-`Ty-auG2;r@#(rW}yX!;xwge@RB=qWC<$k^? zb2xa^`d*pZu>9MXhE*SD29~lfQIyuZ7uKF}lCdX#vB{e5r7n>Zo*+X`->knS<)&Y> z5&l}GrTdqv*6(>;Pr&Hfzh|V$9cOo_zkfNNlUX|)RPp8dzD>sG)|w8OG6!P3AG{Qf zeW^Om16{?u`s?!zqGDt}m|q;uMO}S<4(=MR2vutB1#5M-7+g@S(A3b!`7k&45}@$` z)HNCDm+-N=L>m5Fm}b>FW9p2#=cZqU3R$a!N?Mnv%@mr`*}?#pbWu(ye#8A*J#SRt zeyuq(M3i$6ZR)g^^i(TJ-FlgxwBjIu4`rR;In~0^djyI{BPsFW;(9r9j45NO?c*B( zd;b_dbo;EKIyRB?H{*6;k(T}WV{n?c3uQ@D57fO{VsBdi`-zn+Q{uLT87pAQ?u)FM zp|BR5c;?GCRZ$;1=+k-+vM$n9rojxYmU^3ZI6JGI(lPPK0mlw(?J2*Qpk}ye75?Cz zNMRCpA_P%CqI17hu->}ee&;$U0W&_jI=wn{iW;&sTG_E$Z<;v#!O{kQmOOMefwT;* z$#=ImiIZjkI=U{f(gR@pG7T-z{l&SyF@|5Tu6h3eu_0Z!8R30SEs=ySr1l(Ws%)Hf zR?7j@IGiIX`9?X(97+dea(6nO4SrzV#}n9OUaXJu+fd$pQ?*TDu8lun_*RF$w7TO| zW70|TR@_oRQ)I0J*jHO$S6?kWIT0oSdUL(CVS4BqXru2=hzdSR=d7>iRc8;pqdmTK z$66}-0dGIyayZAdh?36Wn78aI$Fbwvma2%!Ji@Jt8y9bwP3f`f4pn89T3I-5RIyLu z^%3!FK#2-#ZrVU@KU9mcK5QnZ$KzG=D{VdX*T$)+tylz|H<)g@=?HAzZxW&&l zjgmlSW8oL)aj1Bj-c3pHQ}Ciz{}8<~k1!{zeoR!2{jFM>sQR`fJK@>n|JbjEOKiJi zG(&yKo5dX*jps;OZZw*<{;~2_{ulqta~fa!jQlsu+HgPv>aAjiKR`ESe05cO=}JNc zwu~mE=G%c@+`sC`C41K`C?Ump`etUZFJWo8N^`~h2dvKShS`6}Y<0D9GpZ(QM9<;n@?6$%3 z9?CZ?6Py)h(CC+8>Aq*zMzRT*C?vGV7~G2gjwWW}P&cqylQpYC@wM?=E|K+ldoE&n z@cNFOuiRaAZs+H7r%g384i)Q8wDCr2(XF6BzWZviPJmKuV)VPfr+CoD=RJW#B@1!L z#TRU%^jZacG8N=glAl;X(<}R6i<8zzDLzHFvN%QC%o@@o?nr?;r|j<2#mStyBWaI! z)|Rhl`n&TeM|&lu@e&iNi?4pibUAxvxaf$zmRg;hf^N1voWA^Bj`l-SBlqmmMB%CZ zEwa->!%!zAum{2gTa@^}etoI;EZ*d7PEu5ggTVD>N(1sM^x!V#ntWkDNv+I*6K0?4 zd3plhbyHPUe~=obj#`|0R?lk>g3b=D}{=K2lUSB z?M!|4!IT(~o2u!ju6*q@KD4P6MA!fLY)_d*SrfS+jK%`-O$mw`!J6caWMbgO^Fz-s z_9G1^5#E|u9&S*}>*L^bl)}y)pj-i?LVZp8@+dpy5O8^6=3YGYq z_J7tna|uVIbk$yy`q#MoCXD0BtDhYNOW(_!Q?VfO^SZ^ilCAgCszcY)mjq0>`>C91 zl@?mf^sC{dLe|IB#JL`af>%|0B7|{li+4Is8J}_%--ifhymQU@CTZJjO9tNOJ2YVbht#cubg1=w$3DT8KjE-E8NjU%B{H$Eo8Hz6h zFKI!@gNv283{BiYpp^h6m0j^!_}ezz!b#NNP?61wGkLpb>)m2na?w{F={18rYr_Uku-07j zcj2JJoS#PR3BZB=qj0-vPl(_Lscru+4$d0 z?s&HH#=gO~$<~$U6neu0CtK@VxYR_GgmLG@w`V5v3TV@h7)O@#;i|@_7H(hS4%M?h z&TB$vFmL{L&qiK;V?Fw^Npx}ZtG}6mlTXYvM(*6)es0q96Z99-6c**^w1o3M^1crj zAe@s`ZZp&vUg@u@v56{p?`Pj_|G)F4!;FJmf(Y_SP?}Oyj~@S(mhsL8sj(0wtOGEL z^0wI6^ki>2?j0(2hnNOB?%c=?g$i)2@Zf*oWOqSB0R5^C&MyG_0fTcOzm%X(ogyyk zg)i!b(C0}k0`djbYZ6}>=;8~}yl@WY#OK`7hPMIlewi*BuPpdoendket1_aRgrVIjld@x@KO zmE90rRFqBnbG4)r`M3bRv-U;-GO7QK!W*_e(>>XDdEwi`j<<{WhYzb*Jc<0gN~1q; z?>e2xFiHi!PV&w-)w~yG{Kz5)6iCc}4M0E|Dv`zOBp$z|$`wgf*y+;@@KP}w3Y~_U zVoAKiFtT$65YH~bVlYoXZe<*MH{s|1_{uM}03Vtb{5Sa$L{nvNGHau7oUTIMsZ%=u zc+D)iaY-PaHBA?<>o(gcQZO?bLv`hbDC4j3t;s;uHh|bjuY+{l_z>{Sy zRLZ2Z=;yC=I)sVe{JsU|{Ru(>2I?QmSPZsRBLx|rxfR1R&4@oD*XJs{*oWTLvH)LL zkPoq_sU_M17MuUR1Is+Y0`~(@M-4x|&`5)7W~xYwE5+lDzCdC&dZpJHs*u4{>BlOx z{(b3rq6N4;D+q6qu%_$_ngM(`@;y?AbThuhv3Bdq$qpJtrhST%)XD{_jyz3 z1m<9Xzz0xzL&5EVI7QYwSkeT#plh{qV2X^EaDVRT}-19st82u4ika7zFh&at zNL%{;tuL}Ilro?5sGn$g+3~sSEmw|n50yM?W}29#Hcy|K?FQ$Kk7w>{&S|qUw}HYr zf6Ew|E<54hRUr?o-zo(KFXZRuhb&ST?=Tlg5^SnnP=4Z>&qO$Y?Sps7>zGFxD{dd8 zOFMLQU+6C|bJ!Zn=h&k1jH=$&hpsD|SOoxip9g_MU=)C{dm-LST?C%>qHdeb{Aw#~OHyy;H6dT-k0Apub_xGu!^{qv~wUW0N6-D=r(| z=g;D}O6(X>PIb(P%T|d3gjPwJ@oBOMUZkk-h7!|RFZO@0@{MM*!$G=sNPdjK< z8cD^3JoKFTL*YC5m;XUbXQ%KWe{Hvz?kV-$D ziR@}>_|zQ|bEck^&SclKc%;z3B&pszN>{_{d1pS|Au_dap;IU!kshjnWTQ0DKpDS? zTwy^tf>R5=|ALsLvSW+=h0zn{Da!4P!Lv7}?ROJ-f^m;vmn{qVZy-WRyXp((EPc6g zo$Hk?Q~nT<3A#(ip?pdUImy@eGp58BqK6V?`8oMQ^boUSM&nf1rPv?-O@FI?>`K`3 zW-6=TIcMXK<38=;gbL6U%c zzHSNJ-q}U(4gA76#u&#O2x7Q}@RdC5t^@&KZZ&tZ`;+!doHCLkLr4f9P(y%nxq9HZ z$)Y#-<9DiAUAq3%t+uOp^*epgV>Vf`!Z|~Z$Yj31${5u!brlJ1fBnr0xW;}2Siqlr zU_}^@+sq8@!}>Y$+Z38WKo#!W22204zvRi;9}Psg>prhqZotP1*u$8i!WZ>TmUgWe zred@Yu=$2?4y1Y+H<6%GIB@^z6d+~T19zP~WLOZe4y5oIC|k=VA7Z9XdFbJu{&ZPVxS4L2>n|m-aiVm;&Ev3HvB0Z`h8oW zWCVyW1{w(zV_=S-BxOVg|bsvPl=M=AZ+N%1Zjmdf3T>?ZiX0^tZx92zL??SP-in=D%zq97jbI5lu9+_KF)Jw8Il?5NmD&=LbFmp7(z%ZXfaSGj{Jtr zoWr5c|9Cu)KX|ch;)T#**lA3aH>hOxo@GlUo_tjkq;J8$@v0{HH&Hxu#~MC71|Kqb zN&VrS{x_kzkmt@ZML0i$u^Pi;grgMr&&H>C!0Gjr1FJ0?$_+UQ>gV?UC`ATn^qIfp z7q%{;4e97b{ZX#cwvykbPn3HfL5%pv1Ku8QFf40P<3S@B90g~NVM`#1X92=-3XZh8 z{mQp}=3b=f)$6ZOxlMq8dt;igblCMsjnz$r4Zi>&7D1=s6azaQLDyV5KUAUK&88f7 zH%;fsjjahbZ&7E8E7ieOWyo^KslqGfaPv{nr}BBq@DzAHWP=Gzf?ftXHjvy()b&Av z-f&!ja^$|5^V9KJF{E*zDK?haBabW#Av^%%lnGp0el(f8#fF`=;9z!0nUx;N%U%lQqZ@e z9BD{W2DOGwf^C2TBLmrlkKl7yeS1gl-4d z$+i{p0(6JH?Sm*k#?1O*SSD;T!s*4DGo`%js?eXtWO6ipIRyoFghMxBkQ5A+a#zPf zZpFo4T3;G%i#bAk#HKmNje4z1*p13Jc?{U+lVLZ|U2Q5^jT#{;C5js2}ur-oUR00S0 zK*A^M7^H|#&qCHtEMV8Pk6|!oqF)pf@2cf>dV4~PTA2ZlaAF2&WFQ%MHL=N}7zi!<3?!SfRyrTSl2+2ptIZeAA~kVmfqBxWh8Jk`sr2Q9 z9^YcCL6piV`ADCUpmTRPjXp;>djNYQhSDQn-ttC%ZYO4ONn=<7B^*nYx;^F-<^@z%;jy z-=eWU=bZp`W+lr253is`Wyna1$aTrJM}C>9fk?jxlmmG|Mi@5>hQu6z@zz&Bgjy6l zf2zrbO-L;q{}p)m=@dL59~h4)Hy5UGs$4?h{_NagCU)kc90s~EUkE6%fJ8 zkIxK*g+ea03J3g}@;WCKIs*^Kc40+;ckUkq!XW{|zsNG)R{6D(d_y1Y;~oYl3ers| z)=u!Xz_HcR($awLXPu&9=m}$L5Y>d$!V1_M3Z%5_aVS4*%d6vb2@N==F4jOJkpc$@ z)9f?BKty=3iCiHJVWR$qh=7x@9~xa;L0a3mJ5rzIh!3J`A*-wDW8qs%H`25WsNkW@ zvmo|T;=y4T;&nTJS-2iJ1S>2FFhvU<$~>K%`~XBDEGQDdR%lOxy$E4!WHrDHw0?UA zZ#9E00sC533K5?=8}arMpr&+EvB}L{V8lO8)x?8P^07AU{jFKCAGSWb{y#PZv#eIf zs6f+s-YQ45URYWmMyfA8PYy$PaD&tZn@Z#pz*O#6>M(DKfZM#ykD(a{vLNT)5Q$h997umN}O=>&iB|UxkD1(*vFrC&AxU^^LTA zw1zRevK|hUx@t3khfIawW)Y4xGVgEpSpndY7sD^J%0K{mXoC_2C4DWubo2_j*r^$~ z={gL4PR>V3T_T* z)!PLk>V&RJ@Ljk=$li#P2Cr7qgm5>UJ{lh^ngndT25JJFZ8!%2(L6*EuZ4M=KM~F# z8m$h}Hl_<)4l5doG@zgMn-M6fTnCR!HsOjR9e^SCg3LPU9JN7^vIii=R68@5G``FI zNM(1^S^(xWT%iiA4q%M~N*+NU5MlWpG$xF}^Thc690?VS1n^B6Bfq~DpT!7@t!As_ zsiG)>OdqA_dy*EcwsoDM#Jxs3^Ut;3Rj+AAct_-uN_$3O;-UqN8$n^WwbHjB1de$$ zxLTdXCh!cJBGgOF1O0*~vzIhz!tnySfo;k&7RaRE72_vMlH4sTh(s*IdlfwXY9UNO zb~{cul1nslgo=66w0^>Bpo9nEOQhM{(YED7@LF8OUb%i5?Dl-%NPux`1(BufS`>C> zNE$R!V=?B^mxs((PPsI2Hcu-zlnkaF@C4=yVO zIMHX}9Jpn^6rD4a(1wK{`9@GZ5*l_GS;|31gd9LzXTVdmVfEtOYDeL(=HS3Sv_>_; z%c9Uu(xjIG@*Pgip$b8@h;V~V@e{Bq`doU;7x4q=H6Tz}4i3l#(xBx``(Oezvi!3d z;fO@-2O12AztDyxaKN)1=5Prd5UK;09<@esoZTk8!^!Gu`M_69fT?JJ3>L-B!9g1s zYnpH+RZNz800>l2NZ=9?(DT-VZL_2e+14;Fs*W`Exn8*D53;-#Y9|Pd5$XzcRxy~< zM!FWb1%dV~n<+$lZVmo)*a|1o;kWHp3J@N!F7_KJo&i>S2R7HeVqd^XkKcS8 zPzd=etzc2iykF57TP?9QgG}j>l0e4DDz#3w>6k`2%Zj^%UNda3G=;^zKzNG4ZQCT_ zd(i<Kfxd6T(Gqt1=R*w-t+^K%75Ts zJr8)3K;ndMc)+Q<#}9!rc*Y%eF6M-3YK}e@wm8n#P^nTZs62w375_p9OnaIHPCIyV zb;|8mLcjvTR~C+m;-d_n7z2fHNNbVz73^PkF4e!WfY zZ5$F$Z|J0TLSs5FIM5kmq~BN|G2iF&{5u5-ky;lESQad9xPAqDgGC|rb`Z{XJwU4s z<5kPCBuE^Di*7(y%ezFK*Tn09A)zEJMmNcY=Wp1F6G^WO@Vmn|W1?Wu*ns z1;rcz`Cw(AFg<0ld4v}sEM7If8RUA%ju4P40Of!knAkm^4_J#s716mf09q-aP#oI^ z_(uqAeH_sHTw}#bt@u|xF+ks8077vkvV?!8O&ZQN01#1U@;PuBj@$Sqlfh0F*0e_H z^KO_{X~M&=(NEU=2tEZ5qoq#i>rs z*>!7!s)m#$D|jTUggq1qGI-d0yrDsm-yldtH|SG9bHE7^gNBEEiD3=R0R{ne0UH|x ztLyj-stx$zN0@;KV^%O|)%(O=r@cWQd6%5hJvH`9Q##{1wvg`)p9fWbICkiAn7O~7GbKb`mR=(_2;S|aL?j+4jFawYHtXJ6Ete=3^GQO* zX7xnc=%E+Y{B_lJVPSRE&8KyP1gnDr8mgNg)-?w`tge1hT`kfa_99r~L$?7zw>d1} zxajHIz7`~O)X`U<&GB~e?JSntFn$=$)^RLag3{GO{$eThT}={MFvFE);m=nG<@9`d zl~+f~(@J)W-f&!`Gc2zcx|?pSk9fz=-%!UCE9_|xCd)fsGsBX}$ z)1Q$u3uxgFYT)CO=xpwOS|_3#P{*(1PujF$;yk|r-@`gVW5MPCUBTC<`A#3NepptV zoSa4T@VMY~LVGw(el#n~dlV0KgC5?}aO~ul`$s-O5F~BFn>Fm*VpbWuob!A1qH^Rv zFLZ7ibR^of^ZvXf1if@sE)wB0`zm7FGZr~GoN&#Sa+8}T3C=?ojfs_ zbPehhn!`l8DY~DY2gzA@AJCYY@xE5Pn4wdA8e&P5EwlO>3_!=@OSh4Q^6O1W9!`qe z>z*7VqJM<)MxF9lEY^P4tn4WA+_zE318#{|`-2X6_bAMqZ>|msy&agR;C#cdz1CKK zxPgC_O#tB(ZP-7^o)+z3W_r9X?08Frgb2mXOiSV|E#hEA59Rp?>lYuOmLQW)cjN!~ zN*TqaqQsv0DOFZ>WRfEhnwaje0u#NPK;#opeW>9py*XFn%AUL*nFg+VQZkYU1r3YtxR%ryUPE z72Ojj7f0?st;8p07bn=jx}@|~oALCN%{p%dT9#sIF7l&W^vZ{sm}J^S9v-hSk(THB zFwDbOm4@7&LF8#Hmg^*9{pxtY;R(X6JV9-mu5N&K{Z5J4I-*))gn)p)S+#_OaPVe4 z|6LtF9H~j}>!-w?_e`>MgoL@*I+|0~#9Xvo&$xWvaK>)Fps=!_#$SR0aS?W65^-YP zaay=^`Mg1#w_=ycjJK+16)XL!Y95}ow@3Q9PRH%!e-%_G6tX;uRMT$D2ZRM}bjT`S zQYp?;P+*NMct^Yp;y=S>u=vi<6ozw}!)mT-j~&gq`+4;9QuJ4mZqvNTFbVnbG~#=*ovKHcs>f;FnFF^3>?FF$V%>?|WBc@n}Bghnm1`qu4$tnd9RkLss>qk)xg-;8Z-|tI_lNKX)NJq$tRR-9et`n}R<)j1UN8Yw5e0a?# zXctIY0-?N=hL<-mP`VxTu-id^9u$6FB!gljU}0p?{D`J7kte?+lk`+^N#eDt%ft%c-vJ_i^ou(sJDrln${TxgLr3=IT{(^sBFF@Vy~*AXS-NVA6KW^I*)rI{IZ znreWs!1n&#jF*So74Og(ow~~wekZV?)A zeE|jyrHw3A)zML>357flc~yqkv%&fEr#sBV?4moxwi+soY98>uwzHA{K}z6BN`zfc zZ7Ha9?LsA_1c8UlkK-LdP72BL3f|vTC$D+8pMnw%UD@b_N2c99sT~4|5_EF37|wxA zexw`skZcwaO_7ij6ML$Se;7bEvx^WTza3ST_#yq1ItNb)O zlBY0{g_p;Vj_$5e&G+_u-7OX!L6#FUi?A8a0=l?%VCj>lbRFqZBBk3bB5?yUBfE1< z)4EiLE9T71b;5gi}S!O((*>- z6$k;{HU?yhSyv*t`+Wy()XCHP$F&dEUN+?(Np@s0F+f*|QU+kxwErJqQlBv?&0H}=)^>`IJxf5Zn-#0 zPXsw(;o8ImKCD|_HA|HxkEf}o(URqzlJUv-VVcK8+C;J|i-vbn%^7vd$}`Ms4xmJY zb-d`GRCK(ujZOA}pmJnwdp+yWLol8M}cypxNd3A+4s|Q(utl7-ksh@vShM!b>%wcY#Nle>IHY(nb`u*>glxWj_BzTi@+<4 zY9(!i@Uns{tVF@3V^m5ZkL3iYiXUa+0Yq67c$O;gm^}Q+FkQiDvpBK#`!s+go~9Hd zf325KVYQfCX9pl#*BMn=ubG0o`@SzM;AU(aT|gCj4rZZB@g)m9@>PAkm)jWrFon$T z(0$G8|1j>rsg1XjL!Wa(Y#~GU<7`+tF8l3!>}+pRQeqg!y7Xx5npIiL|LJ-fqQJ=u z(3RPW<=hhlm0LK*t>!uLLWfX@*C;RAR07whH$0M4Nd)}>l0mIjE7v2%fh3hv^B-a} zvYNvkrKrySy`R1Y8+!KIq%6C?){$I+Wyj1)|4u)0?8X!yaKdum+rarr$@D%&%iCYy zTt0Dx{tCr*(iMOUiG?A^IWZHfPXKG;=?HbyYX(fk~n?T1EKrIQDqj+nw2$*uE`rnvaz|i z7;pkFQuGilGzZp<1XQ(#^{1_`n$TPC(vgTG{;8JhJ^A0=C) zqtX{;#UjjR@thNsp?2v&KJxLwne#*xT0IzY$G7UR@!DCIdy`&_!fjZ~_PJVwkS{2THCZ^rLk@bvLcyCe(j`U<8f`Y)hh1KIm>JM| zBBd2ALrG29Xa3RIDDpY9XT|~4J+IOlFn76MBmSV-D3pMJCRDEqF@;!3_ zD&qx7F#>~Qv*pV$z274jyYQqa0&pGCy5_rw{(mlB@K4tfE}IpyQH<*U?m=Jd zGBag$%M+?PMfO`V1jxD(Fog9|mMw(K;b692i=FFk-1awhW{}FwC;TZGFgYC2jFl& zzundUem#OAV?B99bW_Pm*aA*~impQPZtWj2Sj?g$`?10n>kH&3QX-LaaUn$;CdUu0 zlKyPAAWgtWW;1)#Q{Lg#6be3!aZw2Mvv-s+DY0>?;X|~FTB(*YRr=d zPCTh~7iQO@Kk#D>0&1j#(gbuH%*JAC#imT7)a3NiQW5l09Bz2{Mx;q5Hhm%(=NQQ9 zE0Ik?=e^neg}YXgEZ0!|(>FVrhXPx5c>k1&eE1PDe1y zUr0<)^PH;|y3~)o_17bv8W0QTBd;D-*@N6l1_H&BvZV9vbcAsrjW58A6!3=dd}MD@ zno_9MP1R^{oDmkI_(?h;nWjD2E0(hot@_pysN9UCKjh;LN&}7fWCyX+eduD~+6$qw z>jjv=C-qP2%O>=G4=(ws&^yDl8aA@VmwRhKiNyHrB;6!_%`_urwPv%4&qoA_m1=iW zipPtj3rI>w`Fq|*={`KsDM4qPGn>Td+7do*B6z47oigHoO~X5hA6Wc z?P~V5g5M@ty69Mdh3q7_eB<0613B4cz~_6$q0Gb?PWo*YA}CLfeXn$H407C`Z~-M+ zR(-5#vZi`mP4IW~tQ}Ix^bvH=dxQ}w!(jTIV+8=!3|6G6A85nL+r&l=)$nh~yDNMi zAMeaV%A&Ks;)P8DK3~`Ribrgybr95zBBw?LdO&?A>(DutEF?+y9nCWj*%Yr~#9w;FYa`Zk>2>_|=q0u3iHP1~ zPzShJ>m2P@u>~bO>|JE$3kY{-cxe=$$eSWvHbE|!kU{TNt@l_@ixEf$%JSqRo}}a- zb-C~YHBpJTW2BbS6Dh+GGK<-4;bzM*WsmbyD0}2XB`Sg_VQtS{)UGI=!@?1R1MdzsTBk3G;Px7{X-sXu14tNXekjgBg+h(yIsBcYT;Z5Neu>#>gO!k zqZ7%IW~4=8vK$Dpvt=*ng!k-a!r1Chv2WhL*5FH!y%%c2)s55{=qTJbi{$=NB>k~V zvwF~_{sgxb=aC(>S*3I&iNQE=X-pGs&n(ObSa*Lk4i*)*$g?#B2@1hNJDL+&{WTh% zo7SP#`N&D`E61|Nd(Rp7rZe1Q5Ecsp0iK&fme|;7VYpOk2RZrlBCVGEoguN<9${iJ zA#{;=seDxH!q_OXCA+ecu(hh#(ZtBtM_xDREq|swSGwk!5Yms2EzSp6p(Zn>hc{{k zJ7mIx1Oz=j>z~(S22S>|Z?hMx;$kU_a8pEhl(aw01)fO6zY^lzq;Q>&;?dEl)Z~_m zY$o!aWqhj{VMZd9A0P9>eP0)m?Q|tY@1>ku)=7YMuwaGU>$T$)mc4R2cQ8Fe7OPf@ z7_AcCt&2kH?vx~Ik~WDdF57>XxTb$po&~)CNuZCJJErR6CN(o_3zp6jn2<8L!g!0- zks@m3{ySk=?%toH&tc1g#yog%$xJ^?B&l}slsv+w2wjrTK+%B|{|~XUaWEXzaLd9p z(HR!hzDC+Ot6s;=evqP%9lWS|YW{Wv6RG!3&z3R~r?x|+M@pBnXoPy>{gImrBh5)) zeTJl|e7?w*K7V!rbDztI%IGcT9nfLLfyz8oSj_g|HLNjc1hPC&5Y6Y-=$Ejqb-Tu0 zLs}5;54^-9f4@qHqEX;Iqf~AJLE|a@L+BQX{BU`uwYLpk=kwh`noukz!(yCWkR7Lp zQc(=I_qU@ANr(k&kn2qu$LM#Rd*j;BO`p+~=`AK>pBVy=N0mN9s1a)Lcn4wB@R56Z zT?#a1o=%sT+Ib zGeXQlF)H%=W(H`l52fflVaO&=TZ;CsN-b2*TH$hNoJ4`rNwlRk=q#5OXwX?ZcAzzU z^@v1+@Ee`}mm$e(Wb%>xp&VRM&2&m!0j0>`30B*FW1{H{74IsKWEq2$u^3T8K`cQ= zFkVF?QKY1ww1CSL&%gBul_8}QWfqIglEB2Iu_@Y%v+P;fFO0i=ua627?l6qz$i@I} z>^czVGDv)#uTXje(*^zu&|E0#tr&6kN!lQ{(!n;#??U093FN0 znWI;;k51%RV+yTBMOy2!*5}#wL)4SwO}#aaHAE6`qnL?ANS*w{8`W%1pn46DPqLxXAFdn}SbsMbvMc)iI~fc5(*u{|zXnsSTD(qf}G5!@5+)yS)k+>Ue@iu7b! z%1oWEf3l^L%N)TJBaPP<&TVr$){q#B>TU}N8`0_YM%37J=lrDIq`@gyjs1VFCfj2? z_lmsoS2`Nmkc|qzz5GEG-?^s0)3L^6VVPMd^8?|Njy96g1!jX_0+SJQaeCZ)S zH`zzwT#O?t#DR-tD4YH_vw(F@>PGUGZ4AnAQ-pLDaSd`l_Ux%Cp5*USV$UW`56#MA zFFv{N=;5dL?~2_H!si9}OUSFJs~Q1+=v_R|=_a`amrN@)zjEyMkxbS-G|GGk8O`A$ zkrIUDSTtkFUTe(qy;(Exj#Oi*ao~tK-=bv<>4AMWSw8gSM>UKR@$O zn9g(H@y_*S#NH}aFCl3IMes3?(VyhfC^Z_vmV+~Bv$Y$L{YF&W!!bN4x!B#98 zTZHzIi=Lk?@NhocvPb0Dz6kEJvXmO0Gu{B3gUx+PVeJ#BPH-H-G$F59jNo2t90cA) z3eAhLdWI>i9Qq3$Uai@@5*%HK7pi_8hHebX+-&J9>#s%7=>1Rl7Hn;>d!ardX7Du0 zWWg@9PdnXmY`sxNf8*%n9r^Wz(Q??WzHoTyK8cjSk9j?p&|&>opQH%RrRu#tG9^o; z-+AO8v{*3dO+%TJa8aLfZ$5X zs21Y+u`-A@^TF*Osr~wD`t~K?ENG&XfD!#Waq^KijHNo-W&PT6j*80w9S>p1&a3C^zzQ`3!3!C$)6 z+u@<6kf_O&Z#`;B^J!~e%7m@-$InLUsT6Ip31Kx~VdBm)ng*EB+!ImU8&U=Yur{BLHY5+%177$&Oqh@rG~U@*41bF zrLiwHDxq52=o?S$xJnM8HKm-JDs0*@(b2T)S8d_HiytM)yCQ^1%z&3;>eDCSFZ6ea zgII{{dvt-Fcr>2MR_ZnmHKf>2YP&7k&yqHe@OCzz!1og@Dh)3x8csh<3jvs2w`U zZlEQ5DK!2A?_%0%S^HGJM|4;7_SB@^#e-Ri+9{i<0}Sbh_OyJ>yZYqX3>VV!8tIzw zqzA{x4kI5w|L7lWvIwJ~&he(O!KKl8ZY1FT{ka}-;`zp-BTffTd;1Wv@b~w^hVCI% z6Q2BfNXeGlt(Xw|a?bflYz+4)&cb$vtnP)^TIW3TO%8UiN zv$y5Xv?+JuIw-Le7^5$_+g4E$Vwa$UGdQ;`q4P<`k@-SX>K|cBflIfCX_Cs0N5hoK zZMab@<=7V%iwV{ok+NR+nny@~_9~g2EP5X;RW|wo(o$&m#sn{8-u`7bLA3wC1sxslEJiqiCsH zSXy)g8~JqymKlBG6)QdJ1b5;DAxfb>IPE0+fzWsFd5Df=Hj(dMheZ}`3i>_6*YWUI z4)0E8>+CaAUj6GW;Uzkx^#no~8hz;iqc2B-kPkM+>yv28q|XS8*!T74tzYN^F;_>; zw2{11`Kc%t3#G8-Xo9W~Cw>}F^Xigl1N0Pa9i+b9kv_Jao-Jpyo`*OjEA5-}0^t02 zDgWLJqctexypwvu{HFhf{45&DxwzUFc7a|VdVApAk5|zBlNn21%~(&|y7o=@??lPN z;6xzej3v7cVRBayAU-G~q9C+;d1gKhHi%J{KNji!w`sD;;-q|Y`Dj~?XWeyn?}k7^)7VVggeqJ|CaMMIRy_CUzzEhpMnQRK|fhc~Kb@>=Xhg zbZE=qBS%dM=SE$hLPg7TmaLG`B~!-?9rh)Hg|6pK82#x96e+v9Ab57Qrb%R!cXm4`*2a-b)9GlI zNsOdZwEA2D-|5c(d20CN>dPr_2K)Z9R)sE|R}{C4atA+Y+Nej97st8<@gN0Ks2mxJVMOQ% zSIs zco%w1c+R{ff`*{ij2rh8@%Gbs+xA>lonPr~ z8E`7_%z_}D&`eS_T?=3a&-e?sTax&j_@FiSk7;tSaAD#P_XRLg!ywKfAzc{N|A&T_Qc$IHnXq3I%%U- zUO|~L$NQ10KO1|Nz`bOt8npd-!2`TNg+v}jsAbED6UTFWA{7sMIBe+Ad$~AVi1m$d zo-nqSZkCBWISlbwi40gyOko_NTZGC)f%;Cbw4%_mYof2wWy-<5 z-n`gAW^~P|7+Cp=v+oJHXNIz?-)X{PCmY}sFbSBL(Frkhur zD_^jlT{pdW6pIj-0{5Ytu^ZWp&kZv;Av^#Kl5|m zqvuW$vpE|6;Rp3WaS_bJce%?xz=BcN@lU3=1{5HO|q4e%{voa$Z2g!NXR4 zGA(kP)l&WutM`AV? z(~HsbECI20;%IG0)`8bJJL=5z2v64LaP?v0!;e3$9x;2;FGoKAl`Ivy@!KD-YhIc4 z0%=`w)uJRdPIZo-s7-4sbbF?b?+k~EvUjY-kPJ37+rt3NVb1<~c1<*Hzo{co1onW= zlj7B;TK%fm|7;oPw4bK4AkpQ~{DVG5Wr)#q2ou7Iw(Mr?y>99kOevmhHOhAy4uwTu zz&i;Ve8MQE6mwdedf-Il-QsMlU30QPQJd^D4qUeEz^#97mft&RmK?&ev*oqtB&3H) zC1{&m0@){~(}OFmnXBKAsTr~4$)BQA8GDZNH5$^|kNvW9+Q-vC1?%;q6rGlQ&pL89 zGhhr`&S`ng0AY(nO81f(I0YL63uIQh5%u;gD)#sF85hqb57X^U5c)L&>iJ@@eY-vV2f6)di_Ke$MafIKt5=G9R{QcO!xvYc4c0eLkC zoTFE*W|_>gs|$lu)MjCy<{4RZaalR>@)NEZu{<*T?}4Ioi}7@ZSTbJYQQk)Fzpx}) zMvEr)**-6`MADtV_2AZS1i$Cl!gZt0HijnbsWU$4RqDLhmSVI-55x9c3*@{z|4wVn zne{)4^kABCH=JK~Ch@Z7)ZduOl<}g%&*Mah&&Su&$5yX;D$cq27(`@sJYtV_rX zL6f6sVu$F~^2&V{*j}r`e}38KFUu0b@&?p z{i`gOT(LMld6&&rYq4-ve_oWBxM7}C82{z1cf;xsxk5(gP$&MHHR*BeFKdSX{{DH& zl-QdGuM1Kn^~?!@QWmNHzY7o8m1GUAVFw6fu5;DsSCpWMLhtN3}o-CL8P?&eGiOq1!fBoch)$G$xEa7_wEO|K6~A z^7f@6mcLUCpNt`(xjk?sjhe)RB?J|$Q@Q73?HJai^G_x7mi)G;+CJi9e9{E!_QmNN zUWd=p>q>Svd&ys=L(>)CyioF8F)E+|F#}Wbl}{jWy-(w!j{;7-z|w1 zT;wlUuzdka@nks;<2;qWlt_EqYXp5%-!X30Wc2>^u&kKHW(|Pt^s+ z@ghV~Q7wAn`%)}qo)z*)1gFsHQflVghLH$3$wdRQN<`1LopT~h1}GG;Kr-w=+Zq(8 zLlp>P?M)-gB1LTSh&;&yM@o-9xt>%i$$HxN?es@Mzb55!LndzBSzV09-m?wHCO^+t zW8^2Y*#9)d7zU_5H@w_3&8u9hx?t~7A{>zB^E*9+KUN~n*i*94Qgb(Kr^zzu+=Q9` z_RIKQEe8Y9=Onr{4jze!qG!uZ0y3=k-0Tzdv+WOA8IdkKY_qcW9T;3b>U26EuXK8- z+!HB2EqTetswu*huK~+QcDAW>#B|1Cbsl%LQ%ax;h(mosSnfIxxeb@bi|nLJvB!VD za)7XuAh@o5MRE$G2IFVBcV86IntddVQhiR(-u=wGCOV}3@bq6S7~c73VLUHtrZVJ62)Eh7b{D3Bh-E4V<2SOto~z1-1LBBxvQu-Oi%r z#v6_NWsuoll%hSW_oX}$@^n}JKJL=&8eEMzZr?ahU$<($3tD=+FYT=o?YnRzsD#{AZn95dM%KY zi%d2#DJgt)o-0!2Ah?{aUezJB`_izu>MR^&0&;(ME;#DG@jLw*=DC{_vpSbhnB8|; z&N_0g%QwUjZqH3+AJ1_w+$ZQbKg1{C5!`>fHA;S79zUYFM0JTJ4nrEpArKZOgJr2j z2B%Kc_5#ahaS`?-IHQu>U@uLpQWKt~;J!K`jFX@+&eQ5WWNK1UAJF8!$;=fgD$>Mc z4>^Fo*ty#|jy~7zw6n3*T&@i}&PnXpFvFSV#*3rIJx)qV{wioUFJ+wTCj(oPg-xU? z@0jVBYYdtJNqq=Q1U(ZC+k{c;@S9ER-$I6ll+HqnW2F`p61AZE(0>RlHg@D(o>|S; zM6=1ASO}S``EJ!O=ba=TpJ2e$2Ii8JL^^{<50R=!)ONQ4g|$4MOR7v0xB z`ljg6q7XxB!rSZ2jNeSlXTr9nEu6+>hH+nqnF0o&QRg0E7H4n*FsB=$B-=_hjylCZ zPn;fnFIRtNRejc|GYc;8oR?gTN<#^bs$(!9YT$-NTE3HjkH6~_3RD^*j|#|99AF%{ zi-&ZVWWcs>$M}OPg1FCiv(ai>O-_92B0)5kpu!W>1b3FCFQ?8!Gh}lLe6dqShzX0+ zIfKnIdvnQ*5_TPP(AB-AD-vO|q+Z`+pk43u?P#ubj5?FOHRc+VT>5&|=bw1IA?($C z=Ql?PHapyOWQ9fty9$FWrP&v6(#cnEe;XQRx~mRi#e$CckG$ zN|Wvky?fXPrEk>hd-?P!$}2vaR=-0})nE5fyR)$pSb{{&NzuDt8F{QeWl@(L!&$cJ zL=xB)N%0zY^LNZv%~UU5)Q~PSU&Lu0C@F(v>3_+o=JD#sjAzdsH+JI-T-lw~*5p~! zw>`zN0|JUKOr+q2*>;N*Ih!dK3)6P=NlFMPf&*96#8K0okKsamoQ3^A>TdXGDpDK(ntX0)k>N95_#wi4z}#L8_U7|wC0x*2a7>4BbC^K7rkP!vRM7qpfHZIe9YDyPRR#+}p@ZqTZbl8-&Lb<0Bs?Czs&y zRKMCI8(>n5VzfjernAgOai&Wryt4J0?VUXfmd7!{_m+9=A-0;x3%i{vmwT7xLN8sK zOG$>zss+L;qVr+>4sJUti}~LU$Sbe9IO9-%lRSN$kPxpI?H06}&~&LuaZ6^2qF|Q} z5C^Vn{4HM5Zj!Mqi~tBznJjf&Em=$B#Cidf5Se^EJEZsK6#Z8HU6HqVN1}t)Y2RxR zqbwG&mda zGNcoIgT+;buPE-UTE+i<3g(T!n6?0I3c-$Ipn6gN4UtJW&!|r?FFznL8jTO;n9%O9 zvN2@{jEH$iukaeY$wLj0)uh#4r|MIt8&cXRl!&oj0xtsZlt^V37TSn_5ExyILST|+ zTujN{rlxu!-f7L|aH~04D{!0|p=KBMdtu(9g17v29^KUR_~R7ExT{pdH+uB+W+}a+ z&oN`^$)fE!w;aRI7D>eEUl;tZ=*@`#osDX-NX%wre~P8eD2A?_AD!lqUVBgIJxNp@ zi1O5=?ZH!qlBvlMu1O;3lSSSnL8Gw#D1%`_TwwGU8;xd}S%yGl+aiR3V)dvib>F$w zb}JT(vteR`i?XN%V1P3!y!r`+80vqBWNm5z-_az}Z5c7~-}t+Wu;Z!pV@H8=AqYQp z{7!Q;CoBpbZDq6RXp0!Z$n&lE`>51xG0`OqR=@~4!|TyfsbmjXl&sfEsQSCtx7W%& zJ;Ed{Uqi&Jl_Y!~tBgb-2V|b^Vw$f&;*N8ASAd2F1}Xa#5?C>FgWCL3mqXf z{&vn}MoCa8vs7xL%T_FpPSR83fU3we9}pK+Z}pNPpfig;ps>X0U)6H(c%T53N&-*l zBQ%l*uI|=C$6CE`l#}rG=G5Rgu zlRylFp&+E)A^NZfK zM7K&QBnsd4ujF!!Xr!BjU?DRP@^woW(-AUObSxVsJ>ms9kgV5J@4QMTlaTle#<5fQQnOTUjSU(62$!Wu?!e^hDIPMI z)p2AhnM|Rqks4+9A(UXCLMm3WNAygBK7{)n&9<7tS%*fBa2_QJ94^ScCL*o1i%?>O z>NBJ4;vgdf#4C~^d5x352*4;5)i}LJ?cv}$y#FD3z?miI@|1Q%u?kY1H9W`+AZc7m zH~(^M>`LijeChIGF7;TuqCqnn41>pb7mI7R4hdiTmJHI$1E7h#lw`_X70YyZsITRr z_liiTrF~;YunzaK{j{-s#J@q7fy`U;APtyQvbn5xo=uEknV3Nil}2ekT`y*cEeIJ- zjF8Hhb}Mc*hg#xrrNYh@3Spv7sdhQt?YW|2zKXAM@zf3%fq>^-=fH!_fPB3-YS{mw zOSLUm@?nM2rQ|=dVmPPkFKS_-!3Sf5x-x_dpPBT-kZiP{;vv$sk5}3qiogI&tfiu7 zo8^H}IRVMn2-lH5FOXgMNdGujsxdm;YGj6*z*~1BxWSV+Y&Nd) zm?MQ|0JwZ!72zW)FY|>7l{3Be>#~@g4~+6Z7o8&0!hD*7RT{`~{X4jUhzR6eGC2rt zl12a?$kB1q(H|GH4pm#E^y(+_EQMkpox!OFiG7!#R@lzMW(^k~uTeYIiC!KgtGWm( z*Sk~GLCeUk*;vfrevG4hx7!6PzMWRP?WxB&h1sd;ha z=VtqhU++?~r!mSgT=`J)?vXr_<1kUB7JM|L5WPvsszlh>i|0E?M4|YBu;~m@>Xb<8 z>5$!uDAkbivN5=<>S}g!1aGa~#&P$!okC^Xtk|p*QytU)LkO-k+HhP5-`Lp-PF9Fq z$|}WiV;=~eP1@-gMIB_xyVQqdQn7RC(+9t*$gbvyb+p0@Pc%eq$i&v0*fZW;r7S6= zkgnLRauM80(oqNT>I`LAAN}?YzS;@ipVLin7}Y9R^a6(8r{v+mq;>PcqDLs~I1q`k zXJ)A0;kdXH5xlE=vtw=c9MVM&4!+4+XI-~d0g1N&oq|zre@+$^#a5T82ten0>UaAY zJV~>LZaGY3JYN6TaXh|>HfJBIs?FvEg#6(en$1;UvaIp~Gh>k<#IBIz7PzxtFkXzu+9@8T0A-FEgJ8PW|J1#} z;lK-Oks6fX{D)ojhwAo@T6?QJ%T~>?tw>!e9)U!rXhjbicZG10goA{1y2{0uL6F~c zg&-usP8AYJNGEUHY#c_O9m|OoR@yn;b=r`(s`bHt2vlIu^QQ9fpYFXO-B__zBXA18 zpUdXP%H{I94*9DQ8CaYm5;hhg0-Nn?MQYj%J{=)3p&qM++2upL4P(1`#;-=;B|Ktd*ve9UG-x={(_P|SG7jc<8rKnG;7rr97#ZQ zA39)XbJ&XP{z!}>Ihi6qj@Q+yn>3myjJ22sdG@H-5FAcwvuEGKigr~pYznS|BRie$ z7HSeuOr%|64ARVW4B~PT}rK ztJ&^%7!xMKsZt43)f1;?UBBxt&k^zk6XJNMc)b?F2&BD#e7Zb*cvMqxpd#s)yvBk&#I{w( zR%7H;HPE($L?Q?h+57N`PA8A(a0(#nlo-`(co-s94xz2A(U|OPTlTYFXSRf-^x#!Z z!!#QmEF&&kJ}f?2DC{IXQS)HuW7cN$YPXOmykN3POiU9A(l?2TCxP@mVI~RUk_j(# z;R(I}Ga<;BUcGuD`XGAs4hVohdiCnp3xWB0%HY}TwPUulWbOT@z*_pQbuxYFn!m-z zF5EOet%tp~s9t?Ay=a3IbVhA06eUO-<=K$ggLBuoVapLW9=677q0&=3t*1e2DXc0j z?kL%C9*%_aS6Nav*N3%+b>Z6M2K7)kM;{whv;h z5_ziN=&7WZc$u~(0U9Z;al^}fSWHW;@i(Wd)YU@w0AOidxi+E1km1==lc0mrXzdX_ zXl)C~LNs&*RY+=JGxBB{yte~3pSHn|><~|fU*4|~_4ki*vlcEi=RnV*uK;|!!{1v% zaa}T$WtoKsFhT@CbHTqE+u>k|wIXI0QT zH{@o96~c`ZJor%D29Ky~>krVX!Ez}{gHi)u0h03_hMtU)gpx1+H#lzmS!e@VpZ()A z46y;u9f^TG?C}jVS0)tisq1N=buTg5V2Q8~?8x?a*!cz4586kqGkB3&mVV8Yw7gN+t6?Lx=b?*myXvrvU zkMq~W>XLzFHP9OdFg&QBA$ov7zAe&KLjMg3T`>v8?LEP-0Nl(TYN7*3Nz~C?|EMb_ z5n9Zd1N`vg{)XJ1d7~p6p&l9hS0)s9oQEl$zPvoVv_GY*Yz&{4KV=8D}sX$zTHHq~#qS0`BeLR$z$?ej$(jbM%19Xuq z%N^8HlX$>zaAs8x4!#Y8`V!w#f!%UMokszg&@FWmNQ%m{8~o#FG;~K)-8XbY=M!eu z^)%jreJ}w2SU;1dwxrgT0&@edG&Suz{Ji~L0mN_UK4pip88GvW^`fr4uD%dHZGo;b zOBx~m=NAMuG&m)3vFMwl0ZNQ{fuR7nIEuf`fdEeZl6qR#x)%8Hf!qwQpJs$L32>fI zfS+9!cxh6xt0_cNFR~hyr*2Ad($0jTAs|B9tZpI?OQi3WqNmhi75N+JGclL&eZP8KlGyG3aO3H9sVWA2DlyJ~V!QcmadG z;1?B_KIEjpot-8A4-)^tfPKcc(7OHfH`Y(COX8py&LAMtim*c9dDx{NM}fi+P;TPe zIhg1hAVr~wi+|ET{UCc|V&;E{nOX~UWg|mTqki#MV2Uz|eFm65e-#tJtbgh*K!*i5 zxIp;9y@sA=%^+_JE5f>_`+vp#aav^Sz9mV|TZV$bY6!bW>)sBXZib&|D?Oqh!0Y&T zz%t#yApqmNs-%Yi{SMn&=${4RMC^y@bJ`@`!xlr&Rli`VGy{@ydrBflL_kBLE`YzS zpI^dv`SIYtZ=R?R+!ItY(9n|xWgMZl6zCEd*)HHpcmpv9tNuJ8k`Z`K2 zh)R%cg@ej-QvASv?C`e?3mBA`a8G zG}gmd2hFTXf_>K}0B$(9{T1kK=zWl$){~igP}Efb*gXhe1GIHOh7o@8LrySs0WqXQ zul0s5ZA-HpC=wh}`<&eEc2gL{ODv_kzYRuA5phvqsd>2td9DR`Wp4=?}jB zb_EzB8OWomytrp3eB)=n!QNR_rZjY+nKnG-{G=RiJ(t$iRs&)fFvSLps6-u^po|jh zt0L-peo(M~W`Iaql3E+(pwQ*5Euc+*2w6$bq~f?AXX3W83#;PF;dEda;5e8HFBm2r zFtI=o?Qna2K<>er17_9TFFD)buA%Kc^sRMf-F^S}HHl{v^&q9*13nA)%sb@f()NqG zGQ9rD?uzn(&>Ri&;L`@fe$jx@V9Ipu01T}?1!ATvqAp1D6tD0&}( znc<)5Do`iD=+52kJC_^@&Gd6X)jDV_s~zU#Xze-@bl z`ZvRn34MO0Joq7-N^07dfN)Oe`lYxE$c~nAC=>Y7UyC;UM}@L)lM=M+t3rFi!1x#m zgw#dFIOhE@5*m&}=bs)zN?PuLLporcJv7WGm?oe;0QGu8vEPR2`qjC5XAZNC7ga@h z1#)1K%m`UD`twi{`HG_ zAi%#Z5!`V-p_yQ@m^S1dNsxK=090`2*ji6mS0e0$^LO!a-aAp}hulN{3;TiG{WK3( zaqS+zushUb!ToVXy0`GF z(%kN;Rb-2o7L)=qr?9H0U*ChENV}*29gH8OLs?bn!Tj4?r=NqTRz3W|t^f=zXjcx` z=^w|2oU~r&VO;30>Em`)q>i3eomXPmzbDc=oa@{@{6f{p1l{o9;NhnR9v|li?2$Jt zwPLJY+B&PM{8-nx4!}PU?*Qm~YeiUrXN>mA;`goP4_`I_li#QLpo5R1R^Zm${b07n z_!*$yujKy^q3{mKfmbkoo8JLfhJ)G#Mda72Jp+sDdH@q0#XTXqmP-#0Uh&(PAzeZB z^FQAyl5}K6ZaBZ+d4F@nka1nO4LQ*{D__4Z%qzsN-~3>GK<Hh=JFDBcQ!T4xC zKN`e_?Ff&w_R(GtHpT5L*Erv7Y4-mv_u!zRE&mj>hsnPet!+{l^bEoGTBO-Hh=P$Xk zFL20k?)RMSTMH{z9Qh;S#;U!AgB07_r)>g@^PA`APJExz$g!X1neTT5zqnF4Nd3@W zQj?|)?fX4ywJ-QXkGD45TG?4R@`G;)FblI2N>kFl#c3Ws7p?3}`I1=Bo$vowQrB4p z(k|u;Z3lR4NxrIQqFwFDol9Pd{_@q`%I_k#WH!;3b~geu{=Q)Rn`57W$o$kKfsuo7 zX>2|MqfrBzO5lA3;N0(Y0QXh=vbOVBWKQI%*K=n*ng0n8-@b5<`{z@cBLgj|H8FqO zSam;9zTw#9?!n#BdltGoSm-^@F@3J~U6Hur&kH%-Fkj01w{CXXdX9X`eOY1c{$dDy zR=2ks)zx+n_VUCn6h*yyk(r;`{Ihp*vr*8Y3$&~F=NniUFgfsk$VVzS)qr*50R?+0YRj8 z0kH4h0V$sJONca(A0J~tMjCoXT5jHNA9}z2;6X*o=90{eqOMV^S9Aa7&olO{t6%rw z$_rkv_PyE*H(i5%>MVAXwtUeRw14mW=Yc9`M{wfb`Pcfi23UKS=S~MU_C%zT8zb)& z#DDs7KIv1|=JaKSKFqiL<_#~bp6;=qBbssD?e>l${- zlxxz0uNjy&Bmw03JiXtOeI4sJQ@YRYq4^R(arA4i>lvR9a`?#w&rF-2!TfyQe&^eg z-?w}xr5ZMNR(#17q+Rw>UKX@#+e@Bx{&M5r@SMJu)Dc0_S#`k2`&+;u@B;O7H=O?f zI`g9IR@R;+&kI`W7Nl(=Rm72-=4`W-3^9K`(fZqwPgg$RIy2wQd-$zxLgfE?z^WDv zZtocd<{oub-hSbWoXJ9k*27FDU@2@M=%opop3&BSVq-rJJ13D~lgIu ze<5rwzwf)=m1iTNT)1UG#2&4`c6elQZcJ#*k{ef`$-lne>U@)$5Lx{HKXd$#Q=>PW z8XYfRw{G3~TzTli_(g4nag&`jX-gt%d;x8leLnXacv1Th4UAe>4q~gJM%0}M;_6Gu z=wj}>CV#lWSY_PZ6AV5GkozC0?yx-z_cXL_+0uTyWq9AJoTE2Zb=Gt~e0lK5CH>3d z)Fn5+`mEpoNnHz1-cQoSk<7&vpUb*LZHQju#J$=YKXsn6mDt;wb*X2+bmpE-+wMKv zOuOf;d+WR54ZWACuBuK;$k)`pN^B}=^MRNe=v$b0_m@kC=i#rypNBsSe;HobonI2S zt>lwvKx%r=eBZd#@gFjBPoMgj)tzwn_dlgK<_FaT9r^Rhl{b%zHa(*a3;5Y?ka+#P z`Z`tDIvUuwZ%b~s$LP23z)eefJG>-}+gX2T%+1Xk?L zw&8~Pb&`?y4i$8Q)x0-Kx|Dvfsh4#_ES=Q@hprwC^T z^dB|5du{jHE@Q`D%oR)OLLiF!5TYv2S#|zP$E1SI#cyhBHiH3g=-O7m_Z^QTFT&5; zANkpkcW3Jdn5d?N;;#1BJ4<+zPx5OLw7Jr*URvq%gu-^wK~eXBA7=i;nH96Zmx0{} zHG9CyYfl@cZT)b3=T0yuz+Nk?$i2~J^J<%&-T9B!efsO0;g^oJb(ypyaSP)vIiD+i z7t;pBUA83TKYR7OH0|b;j<|<+1PeOt4S zB@c7Dnwy3#?YUWU(~)i(@wNC{F9fd$;L9^xf_ zlOfkH2}(zbM!thjp-}81p%&g>@93Q@ji)&)y@-K4Mbj}_-6BK~y z#JRA-QOuQ{yY>`5Xe`af<&7-BlK&Fh+YSIG;k*LyAIiXPI0~92zP{0-ZJFVI z_^j($N$9|kV<4fqygBc?hhJX!%gUZn`+hK0j;I}U7#OT@90f!h{34_nY{Q!PR>^2| zrWVest6MH^*M2IgGGvzLMz`hbQg1JAzCb^Jyi51N(-GDg)-vz!pd)EV_)q^>-4d#- zQ~D;`4SXNPru(S-1kfiI;M-W=f3;Wst@~I~4)7;h>t8*4^=#g!?L(&6HhoI`H0V>m z_9DtR!=#SHN4?(v@-_TX+QP4O{)47~m%}U4V!qZLuY2bIIH_Y#&%&>9$7h!yM}#L2 z@UwXHvsa&zzUjWizj+An{~ys@u6ow^Y2CLwovXShrR8)F?;dVgmXK;}63Q}@B6~ErC`(~(3u3B?LY7Pm$reMX z$YdF^OjF8E7@}kwLk7pWzt@@W=ll8o9*^H2zZr~K&YAalzxLJU4`lg$IS+TO5IGiuJuL^BlA?RoCQPG79p3Qkl9Yr&CSWXt{-C8_)8eW7~ zvu$!Od$vByJ>5~<8A>o4TmAE6a1pZCl~GZ+af_XHNDsh&x?U)@w zGXD%7K}eJydDm2b5ZLN%cw=FLw;LHCAjn3{!C(N8XK=X5T75*o7yn%ZkpiG=>g;H7 zXFs-&8LAW%0ZJ2uGqt7tyrY0HVD${3@)DgFgmw&6f1IF4Ae6zyZ;@hyFjI$gaG+qB z59flEIi&=@uC6wg9ubTjl%@uZ-Ley^_+ltM=4+E-!0Vet14Yhxz^z|?*Z(Xx0+|D) z4blo&XoC#vTqPU&yK!g)3c1ipwB&~9A@sVN`6C=fw3G_AMv3!BHujO<$w(X1xk|Kf z3AT$iCTxwv5)_dYL9WsUd!T2$&}wl0A@x$$sWBO+3CGGWE+G_@-+jVs^S+0nnLGwy?>=3FXz zNue%=L zxf@fF3=FUa$j`kG=2{DMZ<6`?e{ThYzi%})gEckW3QmF^+z4dwb-!-}0`^G29^lXa zbrZQ#FH%=qTU$q*zrVAyNG(qn{ofBz_rbgW=cSsO8ccQn`wYC^ogFaRQFNgusP#5^ zv7=M%GpeA03Rlgamk3)8unvZp?FX;Nt%|>r3Na=U;Bs`0fI4URE%jR}RI6;5Lje8y z-~m!w9ONern0|_W0Q~*^Z#eY#WvwE1?sNSfnsaeVB^CTENz*b79o!9w@-w zBca%a#J~ZS75YJVMUzt%7X04BoB?Jofc}>|;I07CHc$!y8><*k#x!Cnmgg6TttsgO z1A<(dDxp9&%oaE#VJqLwwf&!aJJZqmCw2#E&$W@}+W6t&ku9}wM_NKhLMfrtHH>IZ z*iM04HE&m`D+3kN^Hd%UTLO-cxY%y8wQtdR_SS<8hBHc|6s_hjjLJOZq;(Jvo? zCt>lR`YI@jucMPJhmp=m|D{#r{KFmYVn0G@BcwU@sqzO|9%}bcjPXfTmgq?-~?;&`C1<_Yx6 z0E00<%#Xsy!Z_v0>$=hEuHMN=J@i`;L~m$={#EQ|5E?v-BLaT@ZALU#~cXv3I;-+U{!5^LV;-%l7f z)j^r?ZF8`=VZuPs)c^aA#ULyo!JEb9@#XO-+{++wSj1MiwkD(}wYDWJ zXCYWU@<`sbQ(eF0dX3;R*{NQlwnTR7I1j)naNYf)`u*yUexY71Oi{qnXG;h1h2Phf zAC`O&@-cY#U)K~+sXnVk>Ii)@b>ebwtE09w0z`6JJ zpz~hkE=a6=+m`(Ca4G0*#6jxT4(bQ?1CZ$zuqG%Bp(O?@cFe$HOQ7L_fN^4NONz{K zvE!t+z`BOP5Br z&>zy13=gVCwnvW5^QWDE_5{rN#DCp5W%z9-ugv@lUr7);yEpR3d!b6Na>8smQqq{m zVddt*nl%NOANaMDxDeT}O_Za8ZrL03V4CY?H`jBywFSF3=fd&Nl41T{P=&HkX@el$ zuu$>KN(5BoK!j-V;6Y89jkPfg87kVZ$ ze31x7BCws-6VWGRQ2kessMR$9zWr+_K(U4_^geYyvq2eV7WILa)6l<*4gnqt`7}&x ztfNgipBGH2@|7O)TS<|LQ%<}6KApAmKAblFoWa69v&%ROLz&m+SnE8 zu@1f@86v z1Kz-+&MAZOLkk6k6;0Gc={;B}_=LDv$Ua@6jR_bs>|iAv!t*dCa0Og+@9%9VCE?mn zF!h%3YYt{H)DNvt$^=9S_)<6^0dYYLo|r?P1>R(GDa-*80Tc#nr9rcP&JvDf9M+Ma zzb6mJ7#S}jLSU30xabeli z13t_?TsVCSUT(1y@idmK1D-}K)~?tKa9gMWRp?dj8V?edCu3$IFvngW6Bh+282+J7 z3a}1f^N6rNz|#V{4VTS?dUYm*i!~jwxf_O7(XXI_27`V`7cm7WNrk~j0`Lj}Z`M6= z3^cuu6-5t#-C2doV*P~nPz>Mc6d~d6JaCAxGcydp9)v09xiS{~!R1ZvHvOI3rLmCY zZ8HY&8Fz?ynBnyLB*ILmVamZ5xn52~B8=UE4KK_cg?)@dCllBgC49i2U_>ztgaFS# z(6G&6~#SO@nOrkVwl=$H{Juw zz_v2}4MP{u0k8vYP=ErPh%{cGC^kx#evso;}c)iE|>vJi}P9kAo`L3&JNLg z+ao|94jK~|h?q6N{!^&}c*{w9VqmRtzrsQT;Q774F1aik#B2A&!7BKEXb63m16?P; zU`7aGe?~!pH`h6auJbCKSX0b>Pzzs1q~QgQAg2%11@QK-VNSt=3Betk8o)sxt?lip z4q}Cuj}WgznJ_!bLM6fs_}*9=(O?dbtCb9=(Z76x&!$J+H%?wy%8KUnRA0!Wl4D^Czp9~;gaw6IUMCj*hA{Qi3sqP0L zwA623?7+lbu$fN>0|{m&L6LJ~<9`h!kjp?->pL&f-`qHI!XmymoDIX;VhD=2Ry zU|kAGVGS831w1(jsmcpfTU*3$Vxj3+d#MkIGlKV+^7IgeL-b??MDH`3Ze9V<7*YI( zP~i}tQXE7T!qE+WyEjIi!%=7Dd|rdN#>j)S;OFNV3w}fdToap%woAc-BjP+}mI`Cy zOb}OL@sRJ=ume;ZR%XB_6Bht4P{A%)ffg~)jhEqw;5Zt3P@g{TI~HO}T^rOXG@4)m zD`7Mh=5KW&W`T8B(3x65;NaK5Q_!E^41{n{no^-QB&$=0Kd| zymF}1T$KJ4b`y8c6_^-yV^HmLKV)7%1Z@0rX~@v z?GIU?Xb}-@Fiz;9wL-@Z*e)d#L}&+zVXQX5|3R2AJBQ)d8h$mfV?_$y@9RPFc095W zg!lr~Qj*7o0P1HB9|VXv28$3)fmkMJ%ZDc4J|c3vxCetg8ba+r#?vVUru$$6ZP?ia z^x+o%fhQ-ywN-^t5g9kl-9BMda1LECl?t`j_(tRaJr(Km$py=LVK^bioEI3#%>Uzb?w6g

        Ps|MbsBVU@Jg>6av|NIdlVe zLB5BCv1_lUfiE&2=J$_tt*2xIwdSR*8G@=z*>XSG`qF$ufglS3q5$$D^W|++ps}5R!Ts=ossVx^5C@bcwQGQc2SOX~ zbMfi71NaP&sPy%`8>mfm&%}`k;16LtaRKAHc1Y>Sw~bOXew87+0@?nvn>YUo`Yll)J8;}DR+iS;@T$S zJH1{P0)2t;E{4KX@|T2Fy|F5&R}6AH{t&Fm^fEF2O8IE{dU>hJ!PdqZd}L!Gj2yxS zNKHgnI@=alHR0YX?@A{tHYo%U41^Pg5+R*FiT!;-J(%nfcGW9WQz~)@th^KU%s~7n zW>gYgW5X69vM!xo`SD>}Fh!q)3ib+UhaE_mff0)_-4DZcm4q~`q${j{uSwT+Ethz# z#J?aoS0U?FzOy#bkN`>GSi>6D)$L>)z^%eSVCMmdG zXo%$MW18v=T!~>oek*T1lC-{R07NH5{25`#ckNw=~P65I`9Ac_G7HL1Dbu%?O{cN z1pAAD3WYT&g)>+x{@<`cI^zHkkKcQDW=mV}j!FFqLuPEKW(fL6=O2ulvwyM(z$=cY zylWpMbv+DT$NZwkezxIG*-8&yJ(*Bnn}U$^j($>^030AxTwSP#KJ%31tTa{a;+vDA7GplmTGA zboGoNW-5Q0fU1m#%J1wxW4K3H^3&(Fw#S%}@Ak?ThrC;V)*qG)+%KEX|Da=-5}x32FHYbfmO<#w8@#oPP;_l$s5ApLRNxMy^1N<8Vor6aLh-U z0Wjxa?hrUJ1fbAzXlR1_(gt4mO)TC*J&gQ9D#%! zx#uz_6U(#Y{op(>oGUz%0INBDlB~sCR9= z9?1Zw_H(nKrp)ibqWmGeTr4lqJu;bi9goO?8b>m~1rI{PuvaLFngf0AoWr~LKMnC4 zm!+jB)t(5&+Oj zrV;=ccCZr&n@erVbf-(Ei{Q@MH#^Q963haq3O%yiP_NJspDv;)juLt<)JsvsUqZJe zRCQ0$1DqNRT(IewT7V%l5O4-^#08OK*tOhvCeCA)rNjSXQ1x>!A7E>FLc`!}u^{A! z^Yp7@_^Lsd+GaPi@EZ#`G*jnfr6?RFs5_(fR?=Kg(tC~pG>Mo+{o*v5sXP!Ikoep% zrSV$mG||&DnvjI`z-%xq87%Uw%PkEvF16tC*4!k-&xOfM$Pe{u7CT8+3fr-~0n2cq ztcTR*7mORGPX&@g7$wB>eL=aggsyak>5&S}alC1GGyCzfFWQ^Ipcj)A@;}wSGI07 zr^=^V`;VJ3R|QTXmt^4+w}x)GhR3&=>z#G*_xaky>)F)SbBQKyezrrzj2}fdb#!_- zI`-M*7Q@$WC@q;PpR8Uk8*0!%lybpc0rOw*q0Wv22NL3&5c7e7o6CF(YrFN>6Fxb$ zU)qGPg|BCeVD#QzR||jox;H8PDw;cO)WKk?&KZ4VWHRS^{GH67G)U&&6I^!g=MM3U z(an(K{eS+znK78@VhJ21XvLkh70|DN1>#fQJ>0pag8q^UlEE^5D#R2%l>L(qXf41? zb(Gi2QhcYeMqmB~HK$H*gHD|0OXhc8mxgWWSjqRR0i=|5yz1=cym)ppj=I{Fab47^ zEAnhdoRL_$y z+ixw{euSk1z;OlOhj?uXdv|5*@zGqi!YxI0hQ1&i-LXEDY!w0oy3Pxhq0 z8VrG~ED&-y28v-&*y$B~h}?A{MC2qD@d6%rj*RWshvOV%Q`bB=P658){c(%BbHPUM z>ZiK49N2g0BM{81h;w2Kr=Rw%&&Il12obIRZ4^AC;Qr1cJ~ z1$I7zsJpkfm)8XNkUKD+aafH0aRq+&Vtesy(1g7O)ph<^*d$}%yQ94yFHk(K(d*i% znb$gDR?-Crc5^vZtQ+VLqy_wZFq;isb~-Hk=~E>xE>8EWh5j-$3Ou`6xwARsLg66; z*VIvro{;N>eXpyxOF;usOv>5cr=zK9x8yy?&raYT4q%MTsWsr{8EnW&F*+&|44JqW zgT*C=qa1GE*1nW3qFz_w@XLShc7;+5^A$+S*A=~pvy1jGhc+eFSM~5> zE%-tEA8THKA&4HJOUeL30X>}tRxl2I4``#p8(5DNdmzPt4-x(%4E=BvIQH)_wHrSW z!uB+LCG+)K2c+vzMqwBrMnC^$L^Dsz_{`DNEan!a)8$~hUqx5F>iVTc-stZ!JM0mD zii-HTSVM3^bLRA1#~juXEDf`p>kR4f0}kBN;o#7Qafi$E30>viX0%?1zMuQ5@W0PQ z&6tF*<=T5EOwU)xWC{%dKHc1v|;S>(Kb&W*Hvsxe?jI&H8;; z=|U5RSO&zij3^@6JeOe=ddx6ebT2hz?e!ftf+-oRZ7%$I?`OeKt2^9XDl6-N1;emr zSYs?)N7*iOONL6ELNYzKRE#1-*nH zFJpigugu27|J+LkmxB)I zp@EJt-KuFJm7vEoqN#Y)ayC2ucat5{jz&4q4p0(kwL@DS>#oCl4eaR}A3l-P?izo51|yu++F3MMPLr;6 z`e3I5ltOL@#T9jSuRGAb0sj)^WHX?zeT)T#Dpn+(eip!*TVR1zou;Pd=(#}0X=E8T z+g_|I@B*+x;db<|63@NP0GI6u+YD9>G)f>)i(CP5?d}FVg;H3P<3(!!k*x{31bt)7 zk;4Npe)@m^50-6*v44R2+gbD@)8eh}?iNO0bf6 zzEks1ojguUg z>p;XPG$8G2Eq{?O78Q9Fr0g?lC%UrIv@&k9EF9ajl zV0W1&+om4)+uGEP7XV!byB~8frkajAc+Ri4=WeGa0VVN?3M-%W@vI9I|+>c_PPrvWyhuC3SOyQy)v9^*5J|~`q<0R` zc0Y={PM@}eX05Q3M)P|4^0#{t{WUeki8{F0iP@tWH; zH8GuKr>E0!ns5_6p>Je5)xN+KoSQP|RNE(30iuiSgD>p#N{3InbBgupSTzu~8uX0| zx@ZD!S_6A~M&x*)f|2tn{qJ8a&AEcL*uh??Q-vV?ADBHv6?Xz@_vbw1l0SVil6|rWK$e55 z>)%C3q4ZNg>&Gz;JsOnv( z`OT7hmG@cnKREc-gzrT~{wu?$?=mqON&&|4ZGC)i-}3Te>>g@!JaL+|KnW`bs!1}| zZU^W8yWKu+)H2c90x9;7`EicV&kO(E`yK44cGxe~K^snYgQjno2v&v@Wf=e)jsg7B zpyK7~CgIJOgkl)ZG?0PJoBs|I1lCFcSNA$Fo94NBpSaw3sIpq*u{$L4u;0ap*2 z1@nN+0i*!}g+#=r6Kd$KKR^;N{XD}|;?2v;;cSZk5q1xn4DS=LbdxTkFtTnt$ym<7 z-X%bl0S^H`h{bNK*2TW!g**d=(avxon@R-a0-;!QisOO7B5Lrt-i|lau6e7AIhQie zR%n1Of;tG4?(R}NI9H;W3@9-^Y;8+R%TSY)BQXlb?g#0CdoloT#05Pnf;kRoAY)%D z8n6@Qtb0;>TDszHb2y?P%r6Z<{+GOC_utJS5Ijym=YwO{hmE?Ol$MBe8rC|8!V^0g zSp@a>LPMepu+>q>;$tbt9%TEK$LGNFMvQ!Tkp8A|zXA@AhcXs352#QV{9sT~@Z@|! zb@Fa}5augM?V!cNj`C^?)Q97yF+zf)i^paL6gUn*_ElkPAhA%u&a4#|E_SMInzxUj z^dz;m^z_{XKb%v*HM{IBB~bwPAWWF1kvjOUv~j=yS=aYbW(@WLX_JUxsa!3|3ns5C6<|1Z^WFxc_!u^< zXQRbJ(1ZDmO zQ6EvlJhGR%JWS;?=n!@G77J}=q1iC7NduT)J)$t4+((2SoU$ZTocs`Ivcoi+3TI#s z{!n#dUgwt)2!@@HP11GNZNv!LZgBQIC@yXvpkIqc)gWM*82nK5JTBpMJJ~6V9qhjK1Ou@8eba}H_QND14bPr^RqCqzl{v`dlrN6K0_d{ zbpHHh0$Cllw1$Ylpx`nAV*rb;LG;1C%86(O07rljR{|ARBzFK0iEv^+fUqzkk%%;4z3-T_=cO__OWs>J#wL?^tL80}*w~;k@o|K%$o* zW0V>5NGMOiim*0b45kpR)VX+&%q_$`lD7{i_X`fF5h$M_5%mymgxPn7Sr1mYf%z1; z>>_avTbq3ac4Z0T+O|DU7k%~I^LAITrM!B8~A z3g7;MeWw3@ zqzV{q1#GkLlnb899*Ac2vI$o9O6#OL7TvsFi{AO#- zkdtzHx@NmSIQ`&!Etgx#>C=j>@pT-IszQ%)I;F+8T2V?;J%+FMAgW}{Ix2|hTKa6b zu(UhAv8&K6S7CNp>|1fE=vM{gr#!Bg{+AegcXadTXl}syB2u>B4(*Pq;8%Z{#3ck8 z*Rti;2A@#K!vDEZi#UClYi22Mt#EdGSTIkPr>yhKA$Ue;rJtSa_?t~%lVIJF^TOon z?zz`t=Mz%0)?O*|J)mCHnzSL$)9SMAuD4$O>Ia(Hl$xZ7{MRmeoBNmn1s3gc>~EXD z3>O}GN(4{xN(J5OikF`8eWa?auXV26MoL=Qw)vo$?iPDR&y+y7Y~9bO4sYVaCO2Ef zt<0sRtQqP+YqSoiSw$=KBMiPe2-!(c73~41#EFAHT&Rd^7wB`;*>#}b??k6!Psp>7 zT?AE9y#?Q?e8e2#jq7<3r=k_mrJN+fc|ao`9|WHx8L0M>_`@09PFuC?SouhB2C|+T zc+X>tg~jxN2ai^~{S;081YCt-8Ff{zZsMDkf1VyBI2CDh(`VyN4Z&8qG$JY)-9{ln zN3Z?qzl(O~GK6^uch->dDe0diPIQKrUNnr9rdL+he)#LS0)H^Zm`0pb+xYw*?`DI+ z?Q1G);m`vbNQy#_LI-=Hb}Zop0t(a*1`YsQl6VVlXbK-1;n*bbHL@iBWMsgMka?yEBeGj4J ziiMN5`L6KX1&qY}-$jez%1xCO&TL6Q8^9c*U%J*0DWEF^&fe7b%#-o>(TIj@qEI6G zHJr{nj;>Ld)iK%D{ud_MWWtrFxn_!I!BzznQVoLFK@qw?I4{$ z^V&^4o&qAuC#T}gXwZ28mp-X5$0U2VGd1sIfm|kxKk16;u9V@G;LE!6bMNzyRN0%< zt@FFd>_Iw6K};hft@ArBIS7YbOkB!H=Xg^z&y$Y56KzK=bdN9f*!CCs=C_Nj1&z-e z;|*FlN@RtpS2oA3w%t#c)22A!oEe;>n|6v{8(Kr^_gNZZza>gIeB)!`nzf()AgxMB zN{enziZwpu-Djh4@q}Na^SORcH`16IH~OMyz{$;o&~~+m`ys99a@VFwGNx)POgyr#{$MBHa+i}&y0Ry$f@~AYmL>(`ng1@@9+kJj zDeLR=uhfBO#F^yE=f3W5z-=3<+l+#aQinKT8_OZ<1$H{MkJHKeMIC6*YAH!>q!@U`djkGrEhtDlsl;8%*EIt4F$bOU}yp_=l=0zswQ`4-h z?e)3AYMqs7(wQ0>t~`$MWLsoX*K>)}FIHzS8;&?;+5P++{8ug%2OS}Wnd^s3Lz)a$W;)EJ4p|!v7-jr$+hru2bIg1U9Ov_%fh2;Dnlgs9K#(duFx_HM-(#$|L2b8_A)z>cZR`!+E=- zc2`T$)>xc*JkZm<=+tOij?Mn+tV!j)cd|B#^}XEXl+Z{*bT`Q+e@G6#df=PDWO%YZ zc3j8c<%pHFDd&?;q4L)&=l(pu^Z9Y|o`OI2o%OK#Mc?jx3jO2mQwjB*%^MGQ#~gTT zXdj$N_d~Le|7eM4YqY3t^hb~wSYGq6Bu0R0T__1Ve9z^R6tBXCuB!r3A8R|!AvDSzRc-{e8h+~Rlws9Td&SMe?f&_8 zfg1>MohVDFkUGb!2tC`IZbXwSUbQOKdcroAxl80fms3G4Af&aY}`A;1U5+N4&tJ$4$U ziE$EppX)mXvGzUYCEDiA>QZadlU_`Y+ngoueeva)`psHe^QJeMyrL!eL!V#vAYZD? zqhqckS`^E51eeD;{RUlGimBa>DKXihUrrx!+`h-{Zgu6C7s2@6(ZlTCQ#@X4GsjQyKSrunk>B)w=42kSCqvhZRJeOa$8=pY$?pd*bFOT+b@TzUVbWsu1E-mn%Y&ZtW8ZJ@ zEW31xebX`5$n0UgdEm*3E2^NfOuKlgVkPisxzWN6{>2*W+&u$Gd2}G*C``Oo!04`J z)HV+&y5beTacA-EPzhAWQ-=#dgdSt;h$?7)p=;ESR)0SA@1i}-67|o?I+SU*cuG#O z2b3*%RqA&!bVJ$K3y{s#d#8M%&;CSV(jmGnhy3I^?`gw+UzPxW-*gXfXvU_CV(A3Q zS++R=XOy%`2`Z($aOnv4b<`~O8w?58iXNfi?6ic*KdfystG^hI=Lv;v?9W_ePX*a) z(UI%tM(M&tTSy?OKSJACddhdmS}F*wzE0mV{11!unJXPX=ialp^D!s5?!-2?9GdY` zoRyvB+3nkJf6xnfC49MW>4FHTL_um^_nv zlx4W7mZKSHY3(;EgR?kM|5E9cQ<-J#-R%j{;TxR$ZOOlRew_( zt0293s&=4T_&Bk!StBr7#k)31Vn=30PWAMunmA#+a-(wQ;Nbo6PY+3(pz$1aZ!m!+ z1fQYICk}^`DUl8n>#|o~W zl|!gT0Gt=4Y7fG#D|Cw@Jw8>cz(3!Hx5u|fs};0AG1XF-q?qB`&$m-~0ah)aQXws4 z@6|~0^_vC*r6LM?(Y}l+8QN0d+zm&urCwMp5#Q=kc;82_yY1~s@dgLN<)__&=pT-U z*F!a%l*n|_!kK{ZYZ<>x($8>(W}V_rdva2~JSg*5%{nbOO>G)ww-w4{>$`fiOg0R& zChb(9Usf)=nB(aBYp`cq}@^vxEyNAw{YXHengLP6u;&xXTK!e zE$$QUc=XpJco~DyUYWo14}1C&Bx!hE91LB;`tN4lZM0?UZeOKUnzape^PWf7#vQw! zr^{w))w!-Zrc-}ggFUEParE!lQ@7rD?dLTLj@&E^ZF@j@x<9JSHrhI!+x^K9f7?i- zzGA~}6D51)$_&@9FC+D&@yFX$Joi6N+eR`Qv|ep{zE*l$tkA$Ctqr83hu(B4pp-QO z{o8i^r#xt(=gsOovPkzN<##iku^R818)vYriM!BAa7I^S$u6(+B{;@NgfXkf<09*? z#i;~{i!b&^RWxvFK|?4@5JVi}*fA!On-ymHNAVmNih@vUV{0O8ITOS@{OtSUz^u6} z$_HQQ`A0mTITG>lQNWLeS|o&8m!Yc?z0zDnb z(uUJLI8H0F?#{Oyjga@2)L7e4q5k*!h5OYhO*wx(GFlUSouPZwN_QLX%h8b5oykSr z+S|_^iBZf~q^Z2Qxk`00FFX2{O6j-uzh^AeJF=-;S?t3xwqglS(E46H~A~fgx~p20!>(=c0aoGezR=Or)U-2a{4ZNZ|X;z z)BuT`lj_B8&%udR8v3wTbZ7lY@J*G{(P$))c25FK*BC3 zgSXEiG0znyTsWIkP(G4e^Zboj^Huhhk3sm9vgi6w9ISg(WjDeY-~FFIB0 z0yzes|NL|~VDH7G3L}&7^9_SZT(fPVw%4P&4{t7O%>>81W~_eqjR#Fv&iZmBJ`haz z=53wLwAhxm4p0rdbu7N>{lSKfpW-+1&c8gB?35<3ycv-Gce3nub3-i?c9h#(fvN@0 zPj&kAVcMO9Zn-ToCsd>58}_Q;EQ_~x(DX?`Z{^jr946iSoD1G{W*f^(&VFqLQx=Hq zhGsGZUL492Lr`GTX5xs&aBUJexDW_Sb28MQF0X*Rj*s^~S?&=8VdzM7yjn^tm8Zz2 z$=rU{P9qAsqp6SpOemm|ftQ)5k0e*t+2CJZ4@3`7OYHctDK0<$EJa3Q)%D~R^&jW6 zeQko@+M*_G?!3gNZ3oP>yVO^u8 z)4Hmp;8FOpHqz(t-S&}spG`&&`yW(WpD#F}X}y0a!0KkfpGS`BZxH9ZVy80subTeM|cgGO>!2w-k)900QG69VdDxoGUJ`vgL zFbfyjNuk!Q5;r9B+*kGQx6ewCy}#QVG}sNiQpdg)Yxwk2bVD9;oguV^%71xe(lXQg zuTMBt*7(3)y1>`Q)t4UoKg77n&r0fL%z=chhCA zO$Qa7mG%)TkNso){N?tRcVLO#g^BKN4X$Zz7!zSF39m4rfm@L;2gxRC3euw6AyhWd9iGQ6n^ON`CCJaa9ks7&7J)tbtSPaTE;6lr?Eb zkT_WQ9ZW&O2VZshc-!Sidru}qqFqgWuVF?pXcCuo$|4u)(WH)wYX@-dj7TOMdbXS; zO9~&&xKiGdA?P=+w!l*>t8Ch{ai)x!ZpN%8js4-LQ%PL+$|K>ox5axb-S@>}^M#$O z^Ui;|+f;ujDcAFX<)tN0Zd>VOttLpw+_ArK?)>ViX7U-em#m4wlopOtVZ5#>UNr!p zo_4>VH~lEy`7YiKJwchMwluJKMQzC6kNc|pfWw}Rk6MAvf{hVRo9 z&@E-;4CE*35B{v$7BxaT2jA_!r-LVT3{tsAZ0?K>(UuAG3F;)zeIz$N$F(0%*i^1w zm>#cK6z-r$9l;r#yxbwC?a1~r)l6zCbmuONqvJ@YHk&5(_h7F2_J}L|>{>(ccUtf2%G2XsnRC&h8?Ur|@N9rZ5=CkZ=+R1-bnV8@M(5&0$Nms@5 z9Q4-LFLki0*Iu>q%(C_D73KJ(mcXN;22I*0M8di&?=QC4s9@5GFM&_u+`GyC+Z#$ zVtK33t-hgW0cOwu=I$M}jVUS(^=UuRwFQOQRG#f=sxZ9_`Y!&U?cs1mr%L5=)_^&3 zzaz*xKVAJRb;w-kZ$hnsD>ybW1T3MiJG#dQ5wbRbOBa{vhdi$NSf(!^(hV5K%9KQC zF;IXI0#Qv3J;toytne*6D2c0^#G$~+y*IvoH#Jufbcbibh+TN}W`+1f@nTGAyWx~F z8P8R{`cLwiDwi_F^o-@`4+oq^6(tE`Zq|jL zX)}%Zo8Gc-UH#D`pPujpaZ1rPYEJc*N6(LB2I8b{ziO_gr4bzaiHNs@1032zA7oe3 z={H%_p*)ywDn`(&qax67)rS@o;Lm%~xuQo&=$+B$pg)I#H|hnG>H7)3er2H#a`73p zEa)Y@B{!=to_9-n;h?NjqcS)=KYr z`leq;;*0cO8AmpJhwg}R;p+hR_{sZQ*Re%2;roN>POG_alGh zKM1HjM49f6^3ZsAM{e7fzvdOJS=qgxdbLs@s=u-V>@gp`%EEIbrLo-QG3s%JPL+dbdj$c z!avMw^e1_#o2l*VD&sp-uH$PqCF^UF{nDgcG`3LdA~;F9-U5?aWN-&q8>CR;d)?|9 z_8Z!(<>wCThb%p9z17_1C+2Ry{(g3I_(jsirwO;O-(Pc!8CPYMhA%gH zBae5)=RbG1cC8vN*WBQ$yRn4Gi#c|F^lraJauM%jZft(6#kUI_jU8V-Zl6uk4^=a? z-u&d6GduQRTGa@1*q(zk-`IBgRRg*?cV+!0Bdv68)d7WxKO!2sC^2yxqxYR{-k6ZI zHM(Zar<7013Uh)`YRjD?#y)E{NoWjDS@~J%d@s{5C@OWk_3d*ncb3S<9R1Nzh7ik1 ztQ?cR`A9F7j!V{Y`kAdn#FCPS5J;ilJYfbdUxO|L+>nZBLFgx9=WE{akggx6PJ9?X zT12swFxAMi7vCQH?hzgz=lFE+_IVroACf}q8028@s6wY1_fnk;FK67*a{O$edltd( zgE_)2efFh*@`2-flsafn7>!WZ0~c`X`Qv4>WdLy9Una{WX=!Hm7T$i9ee5Utl*5=6 z=ORmkI-5g#Y@35~;;G2e!ZXmzkEbyEkdLYv2M(KO`L7<=2~iKv)Ws()3C_Bveq&dl z8sFw{)cY2XzQb-$bw~6hJ|?KIw|O=8{F$Yjhm8?#YyAbOlq;{C*;@*EcOPxHJHTH0 z@MZ&-$gd37eS2SNSY0_^?w;n@ecsJeXsk}sM{SqY>aSkOZCO_L+I-Ead+Lji9P~-* zt-oPVRGk!H;uN^OU$2|4b3iZgO5xxQ!nU-W=ThC$%3pk)J&I~(+%Ih3W)?7)m*nA$ zQa+{m=(`XeHHBE&be-4j`X@kbt;_j#m~|=_=34^8?4KlA+M+}h)@DS8iAbs`3%j7g z*F1$O40sf}qx0!RBoX}5%HQ3wxInCVHsOvyJ6YnBz~&g7geIE*(67ZHYefLmF_*-= z0-J0g4YMh&-Viz7k3I(GP}@D!{}S^)ad4t(b@pYG7a`X864m7024Aj}I%Xcru_@#+ zaCrE1guk6G^sft{Tf`y+fPY7~dGIRy$QA>x_utS|m+Krm{@E;3L2FiK(Y>*nxUrFM z&WhL97<^tg;&D?}*V~M;x_`!a&x?Cjdi4sMtse7ut(>vAyzF(!nFc5S;azR{=Uitq z$2?~6MV>OTe?x<=Q@CG-SwM>3m0Z;Udxx1)oh7R4o27LQomIPZ+hh}IBgL2!xm|AY zGu&+5D+*)wa~Ncn*$Wgn@wMwQA1A8AZp9-JwiW4>zC`Hh!9Dn+`$8@H-| z@?``H_7j9ew^h!^d~@2K9KFFmw)_qNMsRSe!0i{H#Dyx7xP^Tbg znuMzqQ|rK~Kp4=^gJhK<_^ZsGIzXL;Pp$@5)B@(Y6lR-uoXJBBAf-jkR%RX)l9=%B zv$ziFGQ|Pw?`eUi6#;6OnKCJ9_QUPU8u*}0zk$)(=&nLrE+oP}#0iztpHctNR`v(h z2r2E%Aa~)U_FCKYT5Chj%$cd^D#Fc$-TB6AZNyBDB^ES9{7m&;2x9hG&}yU4EnYVh z0R9E;7yM0wgYQk)+Sjh8j@ex&|jHUyMwO^mL4<{}+$xov? zf8%)cfv4xas`hi)PF?FQ9RfFy4tZE_j%a-CvX*aKP-CCTjy84>*9t5v)Lf*|&-thA zo?oo1N_1Ut(}#8={TBsa?Tk0yAPQKZv{rcwjM);F5S-7%efX^f8z9bry3$y4IGAfO z1yl(Y1Lq^kSs61RlNDOH9p}N-6OrrD2>>eqVTC9ljuW)l{b-;+y15HdRlpWpU!TAW z%Rc((Wcju2M7meX{h7Nr+M4gm+^^oU+`f{=(6ctl%Vr3YVjvevIUA(9t4MIX!)$Pb zTGyZ{WGPIpV7h5Q{xb_-e$m!5_UvJ@hWwL)@AJ$~;$m!Gu##49YIUH= z#Kz|B;QgxVw%lvCUe+)0rQ%%fPRsXvm&0T4amqDYX3iCw#c#au@1pg)t+KVQP3=2% z?AhkGr?Phho2+{N)|NO7nTWU~Wam_{SN`5mXdI}m5w9Qvs!ZFEv^?i;S8amjoTbhB znzZg0qaQ7jEAZ7E$4S+5&$eVH9J%39R4=(*N^n`?f{IRuaa_%V&ib-1N&VI?s+sa? z+G}iTdT1I!g@rb^VqD|O*3A=#Y?jv|!=RiB(6M;Km;jcu!UP1zT4beD+F6i;0(uHk zvq?Raft}{bcSp2<9|EX3>2i6tl7&A6>4@sK$EBvS@b5~H(>3#D44P7w%Br$VQ^r)G zb1AqVN8(7vLTn}!aF8}72h_VJ>%FwILQDY4o91<0VGhswtPaw#N_>=7?@5pp6)&F!xiW6|jDC?F?(wO}PAPp>W zdVl?mk9+eW4H#cq0F zX0q#;ms9n^m$Ss)rbMSCj&ixDmn0lA9Z{o>Tb}T?1x<{0M>lk7B!G>BPj(7|<${xK=t8CtAr));=-d|IF!(#9sW~s$+{?hIfn|6M?v8p)pT$_&s zIo8AS=^=7To~|j(!|AG3IK>#f;EYezekSZ2KmB3hC?Yk(?rV@*+@&j-G_k2qrN zjvj(MvsM8mvnD`J;=~!^h>&THmC5g4gZla>wqf$!w^shZyzahNa|18 z^0B5Z-}*=TfkB3V4(_L^frufZg9uRWGLS^3A3LJfMxP}^i5Ay3Rb$)#l*hk%L;i45 zStbI?Y~`<7?5ki9us(IEF>|j|&bR0*Pz3g(IS`N8>g|QC2q|sK#zfI6Hu2$;{+kgg zJEGOBDa(dXGZ6XV44eJ#Z>`?#rmVNPbf)RgZ_T&XO|}%#FTXz}yXncTkR;yKzf5A* zi8r<#%#*S?V_;vrEv-)V*~AgC5sM!hm+Q}Lw_8{DRAPs}$x6sR=(zK^Y|uB z^@vQh0Q~4c{II0-7LwM6k^6ij%00gWLG~N-bYR4L?d7=Af26NYzI2{(oH*g^R_6ji zV*9Q9uW)?=$mUaXK7&>C6GxgpG-EueS^@Ccu%H?PST}n*2%;<02#*OiiuVQ&0m(V%KNP~wm76ve^d20>Ok=()~Z_y(-P522lGk3LFPy@!Irjxihxn{EDS~C zR_0^zf5I!0#=gplv^k#;{l#O81C2oC0q3Gn_X((DByqqDOl>g;@&gqv%_iIf%VZ?0 zAgjwv*N3z$75v>Az;QKDm7J{w^NATk%@<-Nf}a|AuZcQWOgs#zj! z-4Vy!Kf-s2%N_d`{LCeMBS)#sw>s){KELbq{r!=LbKEzl`?a3W*Sdf%aQzN8F>#hX%a=+^$(j7Ao}2VK z^uCW^C&h8YKeHH(bi23JyeC?VMM3L>E&@3|G|K>9;M75_lCs46p^@wIAB}hZI6mq} zI=3pA_*Ca3nN zP?(_bj`|Cc0|orstYaXC#0p!g7V+sbfRJii@44mNwcAdAuKQf2+Z{UTXxkxM06ZDc zjw}kf{;q5cf8MhwwequSjT57CUrRj9wdc)EWhUV-LU`_uOwH3l*(s4WLWV5zG&ZBe_tZ>a$SPLj=8 z6s}{9sPHMiK-cN^6k;l_<<#8$CNlCp!E`!r^WF)#N=ob-p-^Nsd#izRj6=b5EaL&L z-2k6GvAVs~-M_G%h4@fx&GF4|D(@xb7jV6J0*aE^2m&y*1~?H05SyFR2B_NqM}5I# z1v_Abzk=>g6m8Uf5CV%QXbpDn;ElGxtHr&H7VF#aeEOO}gTzVP)$+W~?y<^||Q?Kc@cQPDLBN^%a<2!tX_xwZg_vkZ%0!J#Lj&J^x1cyapT%j0%|+-;BFl~!`9)hN5w9NF3$NC%vnT3H z%>G^C9_wTwS6d`|VGEviqzj4cm1k|oxo7M%20a7H3BYz@?NJo1XD@M^m^t%@Ki`t~ z$;9=bMCn{>GZ&>+%!a=qlx}Kyiuir{oq<_PPBF$8Iy@Z3dV82dX_ec8QU03!`L^o_ zJL#r?SG=k~!#Fy_vO|05*KR!ik=GJz)~PxFvFW?MVm<1Wu_a&KIrr~t0QIoD$bn}~ zi_kQnY5Ch`<}c``K^+4(M1=34=71|BmF?N4$#zCob%ek`wsjMAR+;C_RscR*GrlK$xeH z-4m-}A(?V@Da`3sL95}DSjOuC>bF|kSiR-SeeHdE3`WR(ONGic*-o-SSmdKi#?VrH zwA9M!K0a?+F~)X0+6J0&CBpaDeXAD1P%1}5h}M>7Qgsnf0^6n7>*$>`c)QqTKi zsQ#=WHmSr>QjosJKO-2RQIEJlXF+#CAElg+oyKy`T*nKn=}Qd&1OoDWfZA{+$AE^Z zE$ZlB_%2=qM(Z^=W}-PDI^dlj!oNELejov zo$;9U%Va2T$x>YKtv4=oZX8+iYpRbSB@{sseeS7BwSE^aGS5w#a_y-Six)9`;G1)! zl!DIas%QEO896Uo@@_xWC%COV;_h|2EoMJ zLBXG@-1iYZ0x(c-WM_2kW(s&hyvw~20IdRGv=6*vJaid+CV-%bSpej^QqQ7*)c_M1 zoJWzf$Vl+BFz_>108HoZpvbJ?;#>#~eSses0MkzXi`u%U5C)8jtM$*J&cJ@{)2nrj z!YCs6liyzsd@|)q8t1|32u%4I;5iPw5F2sN)e35jj)sM=@2nJwYoS-HS8IGnvzq0u|?@8#6yt1wmhB- z^Z37CdgdX8X)1^db3a>R>lqY#yv~>!PbV#D;*X4lx_?;B?#!S$yz;&xi2btYv*uX~ zjF@bsVWLoH6aeb9US8mqcY1*S0d{X`2QSK*&BxhG{Y>4omCH= zHLBHX7SFl)?IaB5PDaYBRev#e9LL)>U{6P`*N6@|DRen!$!U1gqr2;)c7_hr>NN^Y zZtM)2o;1;pLVK&Bh_{VMWj{6NPs(JkfMCi337k(;Fc<%D?VV6Sb9u-cg#@5v4=2oQ zzT)wpCoin2o!6%Yl88&kHxe&PX`+vBI1i6w~oa4LNnmATIg*vbbPgK=6TFjI^k z+cnTmGgebvZS&fy_6@k+JQozQ8FBcZjZXS@?Yf}35s{cu@eva0$WkqtUAn#f4CL#8 zj+E{IGZE^g8K6KUc*%v}KL2s9{&dUt(0dQtImQ~Of@d%7w6gHVbK9N>wTUN}YkLFm zA^X|!A!$hXzfFa(s$EAtX=j6Lwqrvd&B`QBb=&FBN#|XjOC}xaCzx|TWl((;iU>^H z&Smd5_KHqIPURQ(e0s^p;a;uYwD5l5u(uyzcbS|U0en9U!)<~V5C?hwLMt4S=~!!L z<^dI+Po2rEqj>fH-&gh+g2TO-g4!DTirc znQzFS#Ua=(rSYlnZB{5g@H^5Xyl=^LSxbNv_!=6*`v{}ct_$CwwbvG+wk6oxZR&u? zH$VzpbdU0ch-ohRm+M{1BHsphNfr=840k?MKa9<6#^fX{r0%?xO(@~q zZA%qBb@XZM=c&F;;I@yo`IDG_x_bBC$*ETOVeg6n z^fpUna4gL6(Cl^!RP-FiTBG{5uh*@HW#f#-x7KZ=U$>tZT$U!};im3e7wyA`NJE5{jY-yybD1Clv&QUhqWGA_s8FTaW{sA;9KEvir0tpU^eF)wd}tDSJ$J84Iou6kdoq4 zKujwrvpuDOHc(q_DWGqL=KJGI8eQRsBnoOopR;~gqh(E2=sIsW+J#n+Y6YdY|DEX9 z-#~qK9Dum@FKhIj$HgWNO;BZSnUYDwEAcc zwa>aH!}o|Nn=&Ze!0h#?RoOfVwQkgqNE~cO7Wp5@i2lI^Bt9B54>`EgqW)sxF=?pp z>jc{cfhfUCWeGNi!MA`eEk^;0iz0dK`YJoq&o*nUFp-h=rb~x}y!o5%*GSvlZ zb=@z;Jpw59R_7W;b4e*+R+k!f#W9k@z~uw5+AQ!d9%L6mYB-C0sTBAq7R>D4NM%)O zAAa9`@=x0hy2%WWnY3K`l3Dig_M~c)a+Bv%yy)9|+Eyx95LJf;d8f`C zcP5l6-))NXf3ktwx!mo@R7fZmkB)FE6vfMGBI9l~^XD6$i8BbrOXw)@6=%ONN}jb1 zAM5LQHt3;vizL+acUC{JJ4**hQO?MGCLT9oY~{AQ&e`4M&tK;+q*%Pc z;!CFknpXN;9H^%XPWB81OVc|r{;p4ttggaUiyUVGR|cgu3DCNQ)B`}y4%!8}XjWbV zA`Yfm|Nbi&<-@63v!<6yXmH91Xfr@&06*^$;Gj*fl>#hee75c{8i0lj=YQ#xYs-?f z-w3WR6Zr}MEcG|Fj>3zxs+_@>f^5e!C>!?r5C-i;e%?Y~_Hw{nw6+7*LJ^fkx5!;e zYLn{lS39l|-G>?T)`|f6w7gyM0~}TeY%@lEcE-%UmtM4vW%<;`tg_R>ttWM)Kf`xH zSOW*AADf*~HVe7oq=!=W%xFug;x^_h7a=|T??p@#lgXp)SATyYUL{SLy_7BDyG`o4 z){q4=&=ATGUXQYw;lFOut@Qk~x36J;jub+xbiU}E98U!SngI_taWZ?CAj)G%Z+tiX z?;AZBB)Tcgc%~=u!cNvJE77Go)eB4IQbN*Fkq4x%?Snm=6Gk3SNqsBA+oFRr6z>bu zGY&RBN}9a=TqfEhOJ>&Fao+oSov`p}d(l|r#}34MOuTwCcXZR0;#sI#-qWi{t@(YM z8>g3N{!F)^Yl~zwT~F;{hVEM@EVZWb+7+bw8^U~BRJSBw8(6|TqUy71>^818!Fx`` zpvKmY3s(YBV_D=-K>RXy0lfl1637V{)MFqo5Vdker~#H>In6U#^MD`-S)i!{ePfc@ zu@4+=D+8Jmt}RW994=Bs31xrawCxq(REaON{!uh9>T|()S2;N_%BFTYOD8S5{|zG4zmzH!mY5NcyaS!LY9B^CNtg(&b=n-Yu&<*|-@`j_8i`i@EB z+*{%jhEu&g(v*TD&E9ev4tgZB)W=WM2dL_Z3-p+l%Bmwr#_Y}-5=}FW1(l%n zvbnFd3tPIC7;lMTf;81v=9cyIp}@0RUv?6@u1EWShT3Gvm0nqp61}?I!PKkmYxBav z_bl2Bf0k6MBedH~&#rE0p z8C)#PKy$5H+6#>Z72L5_3YNbYw74q(j)Ucc1FQP9MIAvM`v9>m{sqB}1#Ex?95gK? z6hK3&K2I{`!fhbNPx9&@kvVWg%&!3ypL%9D97O?;M6K?xaDxv3DU2>`mmmZ{B0_$* ziX0Vnnm)w}T>7~H^dfkMV#|W?Xz(OlNn^<`xNP#OcwW@qZ(5-K;o(hMO4TIPy9mh* zXcze@@yHvzA^9t+S<^Kf7I${x+>af&e0nVrJX3YH61MJg_0oYTs%P={9C{WBjy(x>nLa;T z|GPqzRng1sx^F{DdwD>D+g;RPz271}^<%Y%UcH}a6c#QGSEfr;tABIo_~UXySKc%) z^o5WBXaOMBT?w-goeK8asiZh22V7597SfJFh?%uD4Ly?k+%XHQGwHZ7Ja8!e+C@zP zCVgNnv?AO1@oi*e2f;=`z$2WPM`{5S=B-0UDdMdP=7#jccisWVb`9MS4cL#q2JA*F z`7nB(@4p3xLxL~F>wBnoE&YNK{oe_0f*%C6PV=l3;7e&9 zm=U;VNZB4pK2LR@S&;XkuqOWl6??JBPD`?lY)#ez7)IMNhCU3F~j1t zQrc4|Z5r5`+Maryig~I3#1Cd_cqTOD!GdRc4NKA@^X(H%Xr*FDC2;~9*Mc`)m%qJ_ zZ|Mj3R4;o?JCb9W$532+8L8KV-BPt6`zyX`s3!lJ0-5@R^>;*F5;pyKjk?Q3oZ0e@nmjC z^@bIO?2u73=lSj^Qc=F9wa#8!qQzIFS}knMuh@E9K2&Ri;D1v9;dP>V|Nin}G^TRq zYX9#(>?IvOeRVlPMyrbM;jOvXD|IB)0;eONrmLXyx5wal@b-a}*8r8%^$&RANe%WQ zjdy~CJHS;kWuR1C(r2^zPl^e3KosJhfRF*NxE%gu17PjrYrsMJP$?)7(Q;{&ZSil} zZ`z#JkR=6*FC!19$*zS5zAIjZ&iqB~LO}YzrVto#u>Dg9yLUHF+XhY&6M2aBG3c#J z4YyWOi(3*G|N2eYYk;n=cZ5QRJQSGUTtk2cq`!bekGH?t4=Hwj`|=LcUWG;V&fX}e zf5KPdez)B|(+d27T#`bBiPGzdC-qvvD`$Vqmwb77JDKxO)2;aJBniuKsrrK@n=Hi0 z2hNHrb%7P3q9grz;|tf`C?>o(d4G*_Q42A>dGXW@-pl!$PwsFtI#}$AJu(vG_IQZb zW1CzL?J_+rVwdt@SAB{roY$gD^10BxHmx6AJog^Q`Ek62Ttz5JKMEG&lXhhrjD^IY z@1dWa(zz%cyW|n8+EBkqoIGv=qhF71)qY=4!_7st+1oo}JPp9$sw(8mF67h*kJm+F zef9_X23AALs!YaBA6GC+(oO#gS)^fCtoHtq&lzNq%+Blad{=j%!RnZ+k?oP%5VmVx zA;01Kz@os!F_>Hd$i9G!ENDw@U*K#k`|l2@gVHKIRAX?@PAPns1tj%CsfK?OiK#xk znH9jJW)D!$0NBIEze7-f|7!g`rvTFgWVoFKMz?7FVV8uUNPRx+znFB^yu-Ckb%ze;94LJm&VmA;gvTg0k7WXv^?3DI>L-!~id$bN57F;sj|W@l8(iP+S3GA`qBkvE?^ zs6YMmy_a5pU<#CV$VHlQvwXYa^W&I+GfaiDnQlL_-JcSTqTUxKeku)`&u3I*>ZK2P zP(Xj;g-ejEuG6b0@6!YA#?rc)nP1PJeok#(ftxn+ z5_!K2I}Vn%M_|z|qCvW0HHJ(QHt%H-&);=jOdIwtM1#ek4zh{^dq&s&4MD0`iI|#; zS?4=w<;65^ox7p%O+U)QG@=T*&@4S@F?DBf+*7UkIDzB@LM^Zs&ae+k&U-$S&alRJq%-DL~Z!UTY^Rm zBoO-+Fm)7#MM-IJz0FY#VelI(>D;Me;(7JREk7!z+vb|;iSs+(JqX3fgUN19By)AK zxe|9DXMm!c%GpTebWZ=&C{3hj&Gy_0fg9>M35Vn=nN?dOy(>02yI23&UC`*s_13PA z%{%elQ}4lVwE2yEPC4!MswemD8Lw>DTvX7hrkZ4oTy0gESo3&kf8~W$JT7sZ47+go zim$N(ObP=&bjtZ))Y+lFFP44|8cZ%SO*vHAzVVE9;_pC7Xs0(6sLAbHlQw$L*Zllt z7-4yp24cB*&vH!Xid&GVa_PCI`?#~gho&TCC7-&>z!#9q0U^srZe(Gy@!EWgaa-}6 zx*R9cWH;^5X7sN^>74|(!&^KP=HlleEazB^;s41!0Pvs6g#t}AfS!TqIi!H19nekz z&a+5_dbZ}YeIy8K;vXP_TyT&Uc>&B2PO;91uZIFlU8DfMu=MZU^+M_lZIo8z4?ay8 zzRQnY13ky)0RIf`;83gR_l4ATSNMVMrm++{XV0rd-hLW|U;FdUHf^QApO$yH1j=Z= z@O_{HpMVp_01A@vjY_spN*Q-K#hP@i<5g)x7k*Cvb@kwDf*?L9ZQ1A%A(-6{y&r=l z4jGEqteHH_2h4`;XJbuq2i@;(t%1ECoA`KLY@Aw?(V?5ztJ>67rTtDLM2y^E$gU&A zqD$A~W;y+BJw1YZMq%UY?K_pjFG`yZawIjabM?%u)P+d7r!*IBLt}icbYL1LT|C#W zSzZ3#jIJ6*Xlpkkk0H`llEZv-ah!oo4Ba0-ik#%(tMIkG+i7<;{3EO!QM#Sv%jx&c z_d9%F*AQQ(T*iO17OBw!^YPW3zw57}JpMFB#-?vUsZF(4)FdaIQ1fs67aFbKr#nv;Gs}1BJzDesR|VM4h;DPR8kW2W%$beVbSZpUc_BM ztg9D+q(rFniLq-Dq(dL7DaayEc8So6xdZD$j_)&)I*oU!xAZT7eVUVt6znBf8+J%` z8D!PTy`F?M^x)O@n^zh=bMgK^cB%TAprO%@zjqB-U=M1g8BoYQv72Q8dhXp3j`qz` zp;xy`5*1O=!ft<4J3=Tb=B3(7_0X}MIP*GDM$J}2`GZ=iu$)w`^+~*zaar0_$b7ph zKPH@MW$9aQUdf^XHQxuB2ZhI+&U?$*zJkeFJx?Usx6U3)^$4Fhp{ zWxRR`0xFizMBNo!WOc%wM>6Ndzt=E4dwSlf(&N}IKuk`mCpF+RvHUTqGgMfufw$5^ z8uJ73(@NU>asB=-sni&9RHLnLSPD&(`1Iy1De}Of-1q|nUWIJyiOWCzK9a3pP7&sV zA_;-CJDwNzM{XxxS4N6|gQ~ZS7Aues(;wCs=(tTM8t;zf@-lINQ{+@d<>06L90d(C}Rcs#-IB zhST6bua*O+*zRRJ%A2v1KI~oTYn{`#3bq=dekIdRYbFa#fG=}|Eh-JeKJ7=nGU9^V zzVj}=ZiRVT19#P=ZPTrek=SryQ`ciOnaSSzY=DVt_-S@@-_XIhjwmos_Sp-ED~ew* zV|zAMo;S256nJGG`@cp^WNS7m1YzFiDekM>j-D+ZTJ{dQt$30UGn^^Q#9ExuSEOZ3 zuurB__U=LF+GKDB&<7t^ow6McW>_jV_k1j+!rN0T{ovX=p7{E0idM!2joy9h1ThP2 z!sst8-Cy_%V&(0pY)@!ufpBW-~{ox}s)n(UF+uzWb{&ifa)bkpud| zn9KI3LXF?eG^$6rk*WxVX=ls};|L(?D}q4}?ix#}FjZ(_MYq@IsP=^7x0dTIjd|}< z>jsWNfBup2lWNJ$1ILpK`-e`5V4H+$T`G(w0acua3ReXp=r%{`h1!Q!Qppt5E+`;^ z;?oph0bBNP$}||E1rZ74{K3~j)kO+?7e(a)hDZl|HZ_m-CMZgRW_6LHn#l!u5Gb$j z%&kA$15WWe{Hp_1{~I-Ulv3Qk1aQsxgmtDusGIBz7QUxIAa^_Q7c3Rt2P=|gC!1n_$qdd=@33rcU+3a)x|RzrsbJ3zcar+axr#MZSsemfKJ}yN?4~C2I(#PdP$f~`ge!p zGE_zQj-Y3}=WA6R9RBel)SNh#fA3?Yv=XOvZBD?O-v6E8L)W9o?A{5wn4T^I4_+@@ z=g8XST_|-IB%<}$p!%ooOPfr)TZckHUIhs(;>vyC^|C>uA`sWZI*@)O0sfc_c*6jd z2kc)f`?K~}gbdv0XMz*B8uSo?pbYLfkfNHG9na5p;1OW4P63$3JdCFnE_~PleO!H@ z*_Z}7v`-vrXI zE21TzIIv&Z3JDq5uehLpHt)zQcVB0!uIHnbxS~&7qS`cHx}?H|?VihOhkPv%x9aCC z0b9tnTAu|Zn6G>#Azm$eQC*I)xH`88XT^#qcAw)^`mX%nyi_)Mvi+JH#1`hjhkCQ?W(^qYiVQq+zcpTxfJvLEyLp0s1lgcjZVZ!Xyg} zCra)T3EQ#~->8!ge54@3DP+sd8Xo!Y)luDV`H3%4lz17|fSniH=F2~PAj%QBaWE9To z-j|B#dSf0qVeTVN=nXl*DbEv1!q0?7yAtvthMLO~j0|||UZ2k$N5?go9|kTu6CNRt z#If`|`}T!c$qU!NDEPoOtd?HcJ?j72eU4Et*73e{5yQJt3m@ER`r{y&K;+1w-WfyX z(zNO=9?kh&=5n+69ceVgO~aQ)*~4dV5ap0flV@UBJPKRxgTpF(`g_c35T8(d=@~mIsHKt5DNhrc_9aS zbUuR%fx`crWiJ5jIFKOJe&9+C_NgcOb|^S>30VQPbD)O0*P`C60N`Z*xToP{*26bMH)Gg#%V08eRyXR+2A-q2OPmUq(&o*74^r!08G zL*%QBT6w~`QsHlc9I>_#txxaIp}hx5)w!2HJeqmWL zkP3sCpvcMR`k*OC!B?!)xF2tC(dq#E>0)7$Ns^A$gFDab<<<~H)Hjgd21t<&Bt}8Q z2)J`_1}Fg(H8KIteLzt(_hX#8|8o?wf9nC(C5^Sv`ZDW)2VRIpjdjlci)78@ic743 zHDvw02|5Rw|!u)gXfdUDt1Ep!)pvuoRtFj(D79`l8gw8HCMh~d=N*efzK}&CLm?w5%v$CVh zFK`}`3OaNP#*Sh7W2?dMxd$!L;_$?i(X|&mFwFAy(u>H~z8;@Q8yv&gp01l&*iJ_oo+Hvd#6j zq%W5=VL$6QID^-{(#vj-&DZ=q&(c$jd~w(23Gyx{@9FIlF6@;{$w`YsqE$ltmxm4= z9;X-7oR4ZQHb;-J*P)k9B?;0j=a&^WZ)Hx^Gk3gpJc#g)>CSufG~RT`MD`C(X~eyg zY~68hAv2IM+nG4E2^sae_&&oop#$|wWy{w+PeqoMUCe4D4W`k+3re*B$me=;;W+aI zkY!h2&rGTK-~p=0wFE@v9sw4ObLDkh4>LsElKNLz&kNXo@vp8v2qd?@JxYn&B*zE+eU{u?c&dl=J<+U>JhQy)jyA#Tz(RwB>iQdZ#kyfwY7=J{NSw z-*2+Nq!lV7DynJEkiUJ@$a2kQAl)_%J%mhYGnQO0K8z&L>%Ec4amqbx3ME$VZC4j( z@qHRpt(>xJcjs_=uaTE9G`3+i&!U0oAXd<+Dk*PI-+He*syrvJ!mic_7RY5v395)1 zds&4Y*#|@X&VxfaB=kiU7%nZ&-qP}(M!r9%cVwk~zVTEm{;K@*kpgSw!@pK4C)aqB zmsEyoZ@63AxJOQe?o_9T8e`Y>jGB#<);Z|ncVEVGhU{FlPX0KT9&zj4kDtNib0^msG(rgziVJLmg}2TLa{T zQvZwFy-2I;KrYN-mdCIl{D&D3-~P=r9e_94|Fx5{K{Ps$rGCKJ03IG51u1JFh`mCb z3lKawF$-9&2L&~S?kvzmfpOWSp4_*EQ&={rFG5tQw*kK_d5g9TeBtk9u`6VC z=(&=N(3e7=u)CB!Y$bp@Wvr7a-~qL~SZ$fG-GVm6FBRo|Ns9frdMC%#?$*!)83u-k zP%c@EBzyKM{5sE5esqK#cRuT_f)kM~sd>gp4-04Y4(>0}Wnl<(zfAQgw%Mc+tr+Cg zskV^oe!OMfZ4Wb~UVXg3=qxEd$s}6v&GYBwTnjb@TU1GLt#SCQ?&3{mTr$FzJM6P{;*DF!s>C-zzb z@uP-m1Lm;Nzu=-u&eXocUXV*TiX;Ky96y!+c-@Ri{<|gSsX83|@;^ zaWnIM>NL6}UkWQ=>z^xg{*J!Rw4@z-{m_O8OI61m;gWgiy#TL5yxEfn(pMqs!reV5 za;~;$>%_iMLhQ7kLccunU(Igz(TNdXpNg=q zNbXf!xRYGtS%{-@eBCKb(4VQpAlM1&jph+}25$YlS5f6YXi~vDE?&3jjx+5oK{8cB z`a@2=5OU?2sTEPL^pv^Nj>Lq$22toT<_m6VlJ?j}&#rGfb%?)n zhE(=_C*y`(L*Y9|+E}Rwj#gefGe?YWo~wlV`%s!BEsvMIg>ceMlOeSr%gDKrq(A*1`9 zV=UyzoKu|7c#_z!)|2s(jv!RhciUZ3_SFKHc;^n-+Au~8&&!<8ll6L*DVoWjS$~(e z4xc64qTWQYyE62o{!gV^Jls#C8$6j%Q^4!9wl|pX{$&QZ-5IW~1n|04Au+Q+0o0k*!oC z>tWubr2M3e_>9=)xU>a)oN)cLlNzpui9`yb_<4jRcs=wAoZ+y`yPluX9)aDZ0@RzO zf+EJcMz}BJa>4iEcG!k_x5adTTtO9`9!EKH(`ZwGxM_gXR`B5kR{+og&}(=h3j|{; zEJxOFZR225u5d-Oj(MCfG z@8j*;w11X!XW+8$-d%uQC%vS*G&tTOW#XtDsP>Rto|=W;$3)o3;}j?B+hdo)%@8w+ zvFL_k^HVZ-wIVh)J_Rg0{j6OX(WttgJ`)o={$OZR|(P<;q_X zksNN*{c}3jJFmU2L=8#qL*yw%lXh1ch>46mJ0m#}Ipw;cDNQ;W6uS&d!?cGa=+o68 zwJTJs49P$rkGX)+wDvFgbz}?uzES(Z6+qK_%S9w z_wDKG{{D)oU+#qMr72meRxLbP<%d&(X-TKTm`<<*d*Ht_n^=j*?ZLKgzixFXH=`Y> zmkJ2yD;*sjmydkxqColr=)f!fw9W@XK;+ryL2ByHt;R0@1t4#JSpxhkU`BI6y(*jb z5w-h$d={X~2YO_Ayihch;8h@+;DDBfu@)H|+6>#`=&DT|Wbiw34vpN;z(nokq$%@< ztNuxEd3+QSB{~FdZAQ!H?e$&c`xIGu(05@CBFl^#1&iI*5P_ zBwS8`N3=QQNDYQROCRqv_T4nU^UKFxUkEK80!D`r#CCY~M$Ry+=djTm zu+*$495O6%eRt*Xfz9$ZDZZ3-CU5EbunF6^p(SgFE-7sgraREXYX9cTq#oa!om@|X z%mzUctF3~*V5I+1shr_mweI&eku68DN|#46gn&e)k#}z<$PAb_PIzyaNVum}=n8v$ zmWqi-W`{#+X;+1KweGA7t3gNXyQh)mgWs`r(6R{Lvl`V3sa4nGx5tuuh!%IuBn+h> zy6SDJRY?u-^@@79Wa2IqNfoT>1*M?@Yj%ltPgBsta zd<0cCJFv*no8Sumc76bDAPfS^u3($l%7=l2z_}YkEMXyo`O*+kV3882eI*|7w*V1CD8gJWLfPn{Kn zGZO;6iYs(y0v~d_d6ubc!R-o%?o4i@@$iK=YAR~dWb-&75r~tL{YjViPm-e=bhbC^ z?^_G63iM}dE-|Ok7sj7&J3We-6$XnU>XicS<>Ij;gbV!W5w0G(1rvj~EWWWv9cw13 z+z#WHkPL6~wJvZ&*n|Qv`GiA^&)cvw9Gdms^ggG0FA`_iV=Qd0m@E)`f&}h2eq>%5O^JLg?lwW=OB}Z(-DX{mruBUDKD-mDV!^HYpUi$6|^tiGp z5@#L+-*5eU*8j=r|0>^(xcKUWWS_kHU-OELvVmUvA&b%28M7rJcPei ztO%6ZVZX3)uePAbbyOY#tjYrjJq|?z*MXHbS$~A3|21=F?^1!^u%zuor^WI*N}z*+ z%E2CW@oHuHNq$9RS;&MW2Je7@LeknFurZ$sFNU{4@G$UN%dTQi|kzy8m5{S zVI)=6`{-iin-gTFr`ejNk(+kKWcY;QLK=pa+yJxwU|yAr_A^I zP1|OH$$9&mDO%7n*YLAX=D8;lz|q;iqnh0V_z7o1YTZ-kXW1^x2)<<-7M8!a@Im@C zBGg=cvr5u I1yd68E^ytLvK3898IZsXKO;_H0*P#X{55eMH-&o;EAYwsAG>EgIM z`^sEAmcvX(^x$f#7&=qUmp4=$ld9S0^&w7xUjQl z0*D}SC&TNKjm$AZXSvDDR`}4{yg7B`V^+@C{vw#Dca;7b8r@XwuI$q>zBy}uQakou zrJWQt6rZ*r{U|YSxiqlddlWD*3O5if01Rge;gmJN7=ag>X1n6808}r0Whp3lP5Vyn3jhw0}dZLaMZ)Zn7|}@XpN8sfDeFE9ai;e zaAF^p#o?u|p(=yb`NlK(Vf@lp%Ucn=Vx30g#EQ`{qsFkB^XB;-BlF}oa_2Q-n_|efG7|zCe+^XzX&qt7E)Zd++HzJ7LL8tfwDK_ARSAEQA|bXDchDp>vaQUOcin z;oJ73;>Zfq>;5{{nt4&ezVaVWJI2Wxb?Z;){qC!_N8kN*lRp#7$}T$T!Gl-RTob-= zg7JY!qXy&jhTaBmwtBAgW#248Sbf|@Y{H9G`#Rj4PeI}G56zLu+cnGdQ#!@(L@(Ee z2oT)y!Hfp66o>gYqt;m(2)VFY4cAj;-3J|tm29H%oz_3k!ZWyfthobw#ehINzi#K zpks0puTx6U4vvx2OgSGo1#Ya@d}5qcaA0icjm!#kENAD;6nG2dWveG0h9RL!L#? zT*8VRNG7x!1q3@??h3G{f3OqQcj67%Di87BEOW!z{vVFSsU^wJt5ddSlc!0Ews?Mi zCxo&2S_F%Up;sP04>rla5)00TNLD^?FFC~=*esA(h*mJ(dkbZFb7BB}VYu|%s{~67 zM@JDfj&9ybF@7RvKrj;cq*dJy#h05{L`zR4K9NBSRhhJA?lKZnoa=>ty2>1vUk)U1 zVuL=GoTm) zDy9@o8s=oeW{H4=!ufYY{jvi3$`}`O!mDGVJFC)O=%dL zurRYu5Q&Jt|MrYh*uJ*}{&aY~da3j!Z-U9GuUUNM!dF`o9Ctdjd&IcKD#+Xj8G!)IPy}p@p)~>8I=Rer4ET6 zEzQivPi#XIZq7dqsy4=0g>{l(Z9IEKLlB(q~Pk?(+0EKa6Qk7~1E$frn3z)+mC*^HEbee%nl{p*3tRp999@2KNZO4>r?gpC|X z5&%;92{%9Ks-%d?n>whAtfY;SjCW7R=c zk?M)LqO$+-Y0bLt0TmG*Fb_#j_^h4CjPqsOK$~Am+Db%cj{?un7 z#Klpx_S+9pGF3h=>u(H|EO}aDQuNN-J%V;Dmrq$HwK~lr1#~ayy)#U0x~Nu@x_;{{ zhP_fcsWR~H_ok#PQAXZR(taT%C6X-B%pGRPtJQA3h`XH+Ynceyd%wfLQb>cO|z_cyb!rw;X<{LzarGtxMZxVD1=eJXxA@&TBaP1dZ{Fr2|b8#H= ziHsR0_p^M&yT5^ZQWK1H>PR!aL3H+L*4rd7%@i%ywSU@;4YA!m}@iEJk_4Br4#n z&;HJT9&5h=GKMSL|9>cni?*QT81Na8{ZbhCU55aEg=vN&cMXY*tO5uoO{M*Arvuo6 ziq>gP2PythDBHnMY3_$#U6WMRvSQG@OPqbpJ?rNwIIs+r&_K1=^fC`uOjrJ8EBkSCxn? z9JN0?tCS>LoONW_F1-DHqft__WGlLy+vqBfiPW8RZo3l^`HD#prZT(16a7oYevS>o z6S?NTV^V0X`c75sn1!@imNJ7r`$hnhN*yXgZzLv7VRmGeA>%M+@)h*xvYmW)pjjGQ zi56bggp2N$`_wN-PSB<ZXZ#!i(!r!Cmn5o{V z`LThI_|VP!&+EjuG7k-x+jVoT=Vaz+Y?<|vPfotiR z)8viXUod-ny5h#fV8kj+CBw>H!!Grvx1}9(@>}PLJfAZ1xlBVuK_VMFuOfyZ9#elo z$6Cr8tql`4Y2L{;zAml8*B%)(8CU)PNILg;rvLZoX?)iLN*Y&vW4^?iP zfFxGhw?hhkk7F4=ji=F}uoUkq21(=lrK;S=-bHJLE#F^%7Zg`E)+oAeD6X>atp*{b zbB55Fq^_{ze=@gfFvnA;fFZ=jJ$y#*lfeD9tW^*3Cv5-5jX-Y#z`!N` z!%uG1oC+FjlOPJXAacYsB#7gFGQx|>T4EDG~xOG;ojcvVbtaKV;ZlxfsOwU6Yzh>(E0j!ya zus2u>;XzHGYOrbhUss`-?JpOh-NjA4rS-3#b4x#XTU(m>Q+MC#dV%ko3#?|e*e4O+ z78hB*G|(dTOYUPcNpqPJmfC2R_-(@55x39_&;=z9X4rge~ zZr<#GVx_g4KyCbe*Co3gZK74a^Uwj3rQ>1j@E>rOa3-DrPfC>+&|A|0u8a>rz#4Fp zmN5!-m(B&xw~W5r6*RgIM68_h0o%<6=PYc{#0~F(aO{ttiJynC zx_Dl1!l!IDBzW{@lx4k{dvA{=!CjdB{SCZK>`qHz!YX5l)_G82^>T9Q)m{;AC42I? z`K1HPx4IC%%Wkrmzt7AW@ZY{b!ih&-iUe^>NM2r)CmrHWFC*k;0Bae~FdU|U(7 zUw=ylfeSN5pAWBds%d(8@RT>nW*Jq3k}U+-^h1=aPrEsT8Tlnl%@$Zhj)H{l)ZbY% zb0gBcME_K}5`iY%^a{~S7%gR~l=a4X4(%h%(ti)+@`22z5}eesC|^t3n*}&z4J#G4 zV%3XfIX%5-`(VNgW@6&IKEx_U*dA;ze@I^SsfRJB!s$!EVv@o)c*@{d z4B-LgZ+(4)g560EP$rRpRi*4h-}6g{*vT`Xn5sQO8l2tK1lsN~y-^|L!f9f@fJb~M z%h$JaN)l4tjjLW8f7Z7>w3GscKo>l|HDWSyIc(FeugZI_vNGyjiz_9 zDR=6&Ll!Vs6k3&(C%G5P zrO0U=tn-T4VJm8qg;o#2Xq^^s z2)<~?I}WqbyUFUre}!$niMEHkSV+@wq>&D5KGv>oUg(02$R*H=KV} z_A`dZ?;uS7o2AM8A>6~#>Jzn4loob-R_mgR(7F*=X&hr5pk@6-xWs1tO(m#s14k&2 z?Y&!yfktJ1LTI#Iu)Eg9rUK8ke+$-J1Wcj(Kt28J4c|H|&4Sczc6n+E6Eu*$`0$(I zn{%x+uVBDF0kClF1DYJ5R|dOdW{xzOn=9onoihX4#^!%@G9Xxg4nle^e>yiof@O~! zl!0>D;RG?G56~!%J${Uz0}1GfpD;667b#BkQw$Z*1{&uolV^ZPDFiYE!AH?APA{Ev zySJFyIC>u%Pz@1VV+X7w&x@@L6%h7cPCN;B5nkxi){gndalP@$jp)1}zTjcF12zzf z+IGfF6!@v8B|S?VyUt4wNe>L7SrA76;J5~SysV4{5dbeW9>=JWN1DEItseZJ#LSWh7$sn8<}eel`n9w@YVD z=CtUdb|9=m-f1Nq$wgNvP|)`Fti0RLn{=%L{L4}VL|2{-W2?eSu@pPb@L@boaB%dD z$MduN?tU7Oj8 z#kBV+K5_)_M-np_9OwSad5zJH&c4W&5$w3x{Nkk09b5#ps;^@&E|S-J6A+&L(yMi% z(xa^F#B>gL^^<3#@m)lubm!j&zRzIp=<5cWQ9RVXaBGT9G2n^H|Dz(na_lohR8>QB zs>?N6+&tGmKHtB2x(skg?CvG50H$RD02};o_UG^)(0!qw`yZ$#MF*}H-z}%-EVU@p zBEEBgBm`iFDW%KL*5CY*d*tn%S~vA$sAFG_@&Zva|D@Qk_0mzzF90zOAlqGMS9^i!-`O3A zaP$rBSOv+g@2zs-WanI5xlXK7b=01DP`DYdZ{E)4_?Oo5Kws1OOHrQla+cQXwDq4l zzr%tpCEsM#!P7(IUnxCdwf(t`ldh5T0%yV(dh!_ZrJ5L_{+tSXm7DrI5rQSe!U*xx zZj%(EKMx&sbdS_!BY*jmXk=UfGV9SAfc6Wl*quPnk*UL>6br`DNhK2K#>F0^Z^i=3q%z*aK4iXG;>F6mv|R5atr^Kk}zS%+O;#m~x#A^jIAVMPcxv;MtEiC>Rm zSYcL$q4je`=CNpyM{Mhq1z5UdPgW{r-`As-I7OJulAsT*61+PNx!9dm_{!V!?JH?U zc+xCww3DZrCTVAU)@-4S+~sJw&#A*-5jnLOYLW126orwM+Lxs}0BPuRUke42z?v&5 zN;fftrMR|mtl4glEwy*;*Ye#IelyaE>Yeunn}8ul4_@g3lnPVV?nm#g*2dUl{rj#vzG*X<2_@%PXazeAcq{41dU=gxh!#nHy#WW!pW zt#FTWli%=H;)u1s-^{v91DN7_u8LJBrkQ27|M!j`(fQ-FfjagB^*)%$i>6@y02@R0 zx9=j>;1BpyR7`;;;DZ-Oz^T7^0x;Xqi6c_-PyY~bQrg`YQx@+o@9|?$*9Y~Lr64=C z+|+g{U+>Qa>tLQp_QBP*dXq* z@tzl8yP>R}1rv8a5i&ScMN3G>)t^LahUuo>c8pV6WOFgNV#Uf`me~_?m`^*L0z>yY9;ivf5AtTX*~eXqT4ysGzOY!xBTNUjzoByYT&!~A za>CucthzyRajn`mR?L3tv|md1gxl?46}p_2%@m*BNKQQ3ju0d3_^3PzqZYN4J$IHOYJi zJ_+DLa5e0`;y&H`?@$0Eo=eC6$@(ba!^Hf*HRp8aqaJ_$ZJvp^qTLOsfCQ;ZoXeK2 z`P1VFbs_N?&kXJR5Baw(pBbx-yKlri|FXLC3;+iu0^WpKFt88&&CKDdne3>dC%l5J zrsa02u&hs1>k0bj*-oWY_Q>b z_j$`J1b9h>vb%9#q073Vl7mZAw^uZAq^c8d!yh%VveL8N{p1j2&rHq2UuA0yAYB|R zxNK&mD8jgV>>hQGvcZrr_FyiQ=mEZHmgqwJaA~biSR4)JIPx~XB^A^Ny=M{WoAV-D z4^Q{9R5=iK^T1)nwNTr(&)9b?N;t zvT%zBL8p4IEr-p}6cQGW_>G^ifb3sni#fCI12B={g<*fuL~6Lwhuu6=HzoVB4jjiN zepr8{J1@kiAi8Z%@4aY)_EJHmGw|(C2XybAT-Ce~Hb_I(NjNDg^yaa$J*x1~5@@9n z0Z&S*celQC0N+-t)po}zVDvxB!6d%x^%wHV7P@pK57b02PAR`vd+*&IUbq}nC#c)| zFWDvWa}-R^YkT(LIjX-q1IRVs%Xtp~j(UtI&yu#M=c9fs)IAnkaDl`%Jgay+I3^GG zseA_vCrcaJcmVc|B(d)QXvCWTlci?45q}079RmNTw3r@)-(WL?uf?U~g3{}lLmPHS z!7`;t-*1V3-o)(UJ9RcUv_5_{5oh`t>Ky4ovrF&P)oxf45=e*>sj+BiWR2RtN1G# zTgZghR5KV7Ri$j4hdM|J;t9mk&CUla7C)9!Dm1etOf2AD!F)VxyS-!kAH@$hpx%rd zZa!K(sPNB{%Mus(Bo5Doe&3>vbGlAB>6iK2UN+R_*ev0I(YhQ;v(iwRlGSNCy)OQt zO-j8_9_qr`8lj#ktn`T29GWzz|HT*}IUUsN(8`UQ-xM&WXUyg0k~)Z?TxOz1ibq*Eh0{UHt=s{=%OGohQ&BT2pFi%pbeL9i5^h11?2vBFTy#SFQR3SyK7fp5l|@@b z5&epco^X~P`v0SucSzjZPBFIKy6Z+92h_mEZi$**A{AJ(1==N>7H1&FFOMr?vO&!N zk_!3K;S6xEg+;#rwkiYLpn~g_Gf0uL4`<65uFIIj+5e+?0nm1zx>@&{N#DP82nGsf zf!xX`Hg)fM_}i40=maFIL%PFC{3~gy6F*&fvDXDDdAPO@_!q}1+2r|b06l(P2Y3nd zU!1<1A5b#|_y+~j6r;ydz*NiDRmgA*uB>OF`I2|;HM%@@Pvz$<94$5#@O8zt4fNA zF=mBuS1xCAXi^U@7iP4HxmG^3&dRKRj#p#ZhtTh>N}OB*AveXA#IP6W#lOl3c^bmL zgc-;4OR>3X7p!YH=G38lF*3F-Sb49|D3(By`JSvZCf6$nlNpcCA+c*(3;CQhZz@UM z>GnyUSbqj8Ddi|Q2y>#}{5U)t>021k-sIL=S07x*c+ZVN*&zRvw(F`mSd;GBN@^d` z`pSHsQeiVc&=iq2I6wht+PlX{D}^U0QXd@fPLuIE~w8F&R`yYLgEPQovrfr^vHc)8UL2rQJqh z!GX8F8GQ6}xlkpHDA<{1!Ze@r2q4>mMlv+Na{4AHt{(yjTN7o7jdzH$m$K%+Ue2@k zPw@K)oo9AXoB;URJLUn%G4kbBb-Qv&`co0#!Oufbnp(T=oc8M)STnMwtbfmA$6l>f z)75$+y#3gRd(l#gG#f~0uc!t~<%c8U8)l|O_;U>GERFoTJMhkZwiR#4&Y#5@Cy>Fp z5j%%h|JI9o2@|xa$CCp7@TsRQJrMa99Q4BxgD`zU`!dW66y{?_lHy#Lz5>C-t&Qc_ zoe6YQl^opvdG+FtF&AB7kr#tr8DtYZO=^h{15sfN$E)vV*)?daspcm!PNYj3-+svR znKF|Lvzc+Wbv`mp74fnTLo)5dS&X_M1vO{g-c6&5$S_Qi+M!8>EBakx-y@v)EuJYi z1wLv!T~toVyBl0Ol*PZb#;)ByHUren@ZF@@rHcL%NA9LGB?wea~ z&q|XOFHFhY;?#5NEcLu12ZoY$Hacf0)Jmq`l_5=+CJ)QJ!Pu>p|^(;q$)F%?h*JtSM^1cH#PXYAO ziJi@pf1xS`R?U@U#R?xa3a<@>b-Q(S?vF#08}s9>d3!VKBE&bKLHy#JhL?@T$txr` z|3n9YlEDeka`=vd+NcxDfDsEC1W90nviJi1NX#ze!>M>sOiu~eQ@iQM_M~e9d;ur^ z-HLSt`(7bK&`RmP$W#@sTIobd14(d92g(GEL8(-YV)lN)GTEQ!%%(f#lb7FdKZQ0q zj!&5JD;hwJ0}d_LzCXA20S3fu@vSezjU~wfIgf4-N{ag(Gl*={KCAmitZn{U+x=B6 z;%I9z=R&CzJqjf27@nMUWXv4~Grs?w*NVN>hmTw(F?S1Au{%@or!iU4*l&>SHQ5#c zWv<3I>dkxC?&M7BVEr|YZ_K3u0HEWCtE^ieU(mW-xnTG8gT`Eh zSd(odrIenH+GaK2?o;lASUmqUhH|UJ{SFZNRSR-iolY&<@@*Iv8_6mgM1~8U%%5QU z;6j&I<`rT)XV8ZuW)Dh;DmsKJ=aQoJUs&6MN;S*czs=%{cx||79$XwgJ%U%G@QY#= zNn7OtEMi_q4*SMYncj78j1C%s{?5bI9gJ7>xsZLx&6A-38{Sd>4O}pr)Rv-kY|HK` znEtdrH7njLp*4#I^zgI@^_LE?J@ zHdlIX0uR`IutQpfcG6pRJOrBg{e z6$BHuAp$r;rYfs5I$G3T(Aey1+mQvh&6(^}sb-ESyS=xhT61gz4YRo zWyr*1dL33rjsFGnx-Dr_|MbBBoh(^7v8z-1HD;eYuQX zwZ%^?cw~{U0Hduhf+5v%C!%isnxd5=)TA(&BsnZwzp0$)U+f9>m*qkBRf>NH7J@a3 zQ!+5cSM;1%rK}=D&E}%yVEen>-$mw)!W`=qr}w>asNc{BO=Vm_#larR3~ZA5^O(3d z7%qE7;rL{R`QUc0eO%OmYHTdw#6rs{sErHrBAdCqm(c*S5)2`hWY%e8jBi`}$ZIlO zXHHRuC7V!65GF_qkWpQs@=52E3pv$twhp;GHg0;_Q6802IsRx>tv!)nwELn?_a4h3 zQZt0KlZ4V+DfF(s&c1$=-%aR_19dE(e1S7Jps00B7BbSlT0kNc$Xce6w=c%_DoEJ*+b4e*I07#W8(#VN28+B}Z@lYtg~oqlA#?l?se8+@ zUC78{`q*y>G$rKfBR%Sx`ipf1SyuUcsD)Ac)f=NsWRvo%{wbo36F5vc815ABQbZVtcn*uMIoK^|FJ2r+M zcIR+kyY?ZT@e~?}2F!eU0GfxAW8>V`1G&8_*U+XB1tvI3EqKHa1^gMfS0VxcTmd2d zc0a(+DS5L-v)sVd0SJ)@1z^a8ZxMi)--h{EuOQLl>$HT;)}Sry8?4@YMK1nc zx!)kSSIuk;-OM>FuB=R@fm7b=EYoJx^e>9aE%>Zz%jlWB@}%pc4;P(>238Xm}31Nw0;R#0pb4U6~X>*)(e2 zSw3NiOBgA9qt%&91=4XUAKN|CP)S$(x>-PGbCt&K*m9IOVgSF!JiuK|v3+%H&K$E3 z*Qptjpue_I5x-q7;835*rr~(^!L853Z?d1vrRvz#iwhn0Wswlh(Dv7kE4lsI3 z&l00;FW4t@q}YoZMXBlzb;tPWWX;cw-#6jn)8gD9GSxOSfo(+vUG9OMRnNv2@Z zH0nl+Yg%gMM<>4`KXaW(yx3XsZoj7BH9Dmdq47f(d6iLhR~}adRv!7YU}K-3M0zGt zPqCOOybxpX8*;(6P3P^{%Ub|Zf9}tRn|s@x6bPmTvz6ZCNX;@6xtcNdY{k}%RS?~a z`2AWBxN>x@5j&*mxiepEL&$a#L~_d`^A2FmG59jSreUM|_-T%T2B_>#7?{jT zT#X;{^I?oy%lH+Ch3u}p6-V86x#-@)e|t3m)luwn16K^f>T4 zhN6I`e3v=~x01ZpHc2sI37NdbY=Tsa zD`Aam=GIEFj_P-VTDd2FN_tL4@I`0U>1wvwY!{aQ?Y^x5|F}CiasEy2*!5_w;F2U> zuC7GMP;TeFLiu~4T;!R{t{P$`5pS$rQ1kY zC**GMI1Q1EvW~5#>u;-6E4yEaJHZcdfoNa2@-!UAUvAS^RTjBcu`>gMKaf6hrnf&f zzjOb#uiWQhw>u74nD$|SF;V7AmzMj!C2VIW&aqmtu+5_VH58^xk=xmY3)K+$>TX=D zMT%V9Jl?6D$d;rRtYjhp?6*&Z>(695RK;&+j5GD&xfioa z1?xD0nd6mdPQo;4S#Byk`)#&Ow8GI|DjY*9Goih749@fcvz4smwZ-(~fZ9r9Rf9dS zxC?EEu>~pvB1DRH(r$a4V$H)rza)}wBQ1(+^+GZ6-$HEXWMq@}$(F$~uSzTdefV2>|3m{MeM-mrEXK*zy**1}1J^TfsvvjD{BZQZU89 zFay*gn^ossUo1=5&PAx+Z$YTW8>$MhT$drPX|*2y3nyA>PhF8y;kPy3 z-GCjNXLdSWKr-)*W~MXizb!iPZI*TIE(%+DI|Xi)y4O~Dg~zpMceDI<2+PTbmEKze z5@5ZRuWpqJ0iOfLJMT{3f_1XsU%FhRl6t9G0@V(YT+pkP>2P$=#}AQo?^X@^e6!i9 z9^hF=&uop_k)LFw!)D8;_*VZHF1IxfVc%)jHM3V-v-*jgRtKp zh<_#ZEyEQ>mwZv|k%r-=wcMo>CX?%EjpwLeZR_+J22lY*0nEK|0NAG3Jxp{F`iWwl zUd2&B4V6eRODT%$*7nHV^5=ey-#~Q#?X+xdmaazQh|>02I?wPnx)0v!$X~@pB6Qrj zl z|50n7?JvT#@?La31T4ND)GxEwAKb)dU% zsw!)`6yJ6N)sG}W;1AX|aaGR13f}g?)%bc-zEQ{Wl(uX1ui%%gi4`-=TGUJiaTnH7 zXLDa_X&#_?1JAjz^zVNU0IeDD_=htTNk>Z@%`V`z;|6Z>_{NujQp(#Zt-6&}D~fQR zUHh$gQy?P5U6gpIg2Q<~@x(eDfIhTR_&+HeAw>2AI-Rp2=y~0`e}0@l1UB-X%k|Bc z1SR&zQZ_(kRs$;U-9DT=!xEN)bs*-W)5sKvWM#%W8}TUMM{eb{RrJlu(c<@sR0jXj z1O(9Pl-f3P%;%p|Vh+f=brDs1^Yjn^_WlZ?ByMJa%sZ-pW1UR~P?uir9g47ln3>^W z1lAdiLqJ!@C^jCJP-do3Io1W?`^HpG^>>4hFTbcarw7-C>Q3+<0Q(>=r_8R=^ z_mSU%>wSyuFZTsl*l496^J!@>s{N^T$-6o{-H8v6bQ(En+LFAUF20jw!3Fgj+Qn%+ z$Y#LaTvUn`59QIEslcZT6M5g$1yA|=#5QUKJZD;e$R_1q!|w>XWg=SH`eM%PLB4TW(Z z&FZg@C5hQY^K^;9;-mg;a#3~Yeyn;*TiyqchC9_QX6mS>ZfhX@>VNF6YmUHc^{5Nz zUfzB2TcJ@|8E~CsdcmWTOpouj(O&;|L0wBEDiNc~orx*fDAghPXlmZ#%+7RJ_rjR#lMsayYD?l75A@P0PYm!WYbqFNLQ6g=mJLz3rFuY$ z4E{|j*mso-xnqMX4v=vWs(*!`^V|iFWqg;>s#nb1$*Q10)_2Kj<3p)k9kOl*12#Vv2;lh)c6Q&Tsrmp{C8s@r~x zo8ZvaR-EUD@nb)8YBK#8gPPpq2Z1qR6kTOY4U<=^smNy7Y5&BfXd_D60Be%a{o8!o z50a`2{EX=?3n*TtE6a}fTN_`RcH~5($;QRc2NDdPaE>7TJaCtsYU)3ARKLEneH&&R zO}0!_x`y*wcd(w07wy(S-FY`>}=JvM;2is5$wB z`e!Pd`@$eSqxmiwvj`DgIMQik$oay_^UFJpscS%j!pAjn?`|*_Q$R<>1u%rSC^1@! zYPBp!PAfP+Y|)~@p+8-wFTw83@qg-)%b=sMH)Pk(VQU?(Eu*SYm%GfQje>GpG$1wD z^haNogb_d&XzYeE6nKJ`fxu1RJ*q~_H7cl7^cJ5)J!{d3-6p67m=y}pu{95lbvO_s zHPr+k<^}P@uBkf!Zp`Tk6Zfzm-ice$@@k%N5;qSj@2l+%J$=*NRndQ~^kDrH>xkSw*xFY6@&3`0R z?EAAJi;iN=FU-0WQ&ne&wYvt}Uy8TWW@JxwjEaYV~zX*6_ghV33?45j9xCddzsCZkz0>k30Ff4}B6R~&X&7p8aASA+Lp8ABM< zjxE`RJw-_N%A3%n7M-^jc+34eoXeDGqA7AR*iC|d%NLLF4Ik!1_eqTNfB1+P98Y=s z+X4uB3JjjL+caS*e1dC^us|h|ZP> zjC2HffoEEU^})pI@B41Ty{MBWsefm*lP~rrLoBe|cXG|^>%U~9bT#?2(#KF$HlA^d zDBWjXO?zE!LXSm@7eI7DIqu>vMyZIL7DWY5)uLAG0j0f2qfh{1g$?EOa<<4rRlV!T zwvkm>YF#3~T#@G@R{b!{!tHDNU=AH3Ed=gmm|0f6qmNZZ6G;5R zjZbL!W*mlR9wt0TOmz~v-M^EWO^Ic6h$zLOapawkvjb38K^89(x5UPb<6LZ1>WJN! zY!-IsDzp9dN8frq(?hT$%6yl7m^8dczt>hrjh#lDXpCCjbEkusaZW63eGb^skle2O zOX8Lk<#!GmjT#wMa6heSg-?O&nMoDw|GCj8pP)-fi}tRvd4D=23kVydFn7fkg?-RM zkC>(#XM00r9b@oK5Sd@?bog@({{_Pwk`*KKTWrR(&jM~=z)(N`{9(^-(cbUQ9W{bJtmD? zzUzX6<3vBlDr!Y6e)v6ek42h^_07iHz?wR+59WMsZ>Iyu?s3(`e<^TNKY_nO3YT(h zQDQ;|cx09Q>PiYT=UR=;REAxF*Df{hcFEEfNu6jFp~71480Ff5V_n$t5I}zi#>(gT z&Zd?UDW=H(qiM1E#~2z;d_y@xD(lU!g;^+for#cr#u@}cl^l7PYYMmN1f*{gWl|hT zufc0iE}I@KF^9nsg_*k~hqIA~=%@xOa$$V8lIL#0GDuu0cUmC!7BUb!=^F67y;(Ld zn~Ps)Ee0#L?tbWJ8tnufgVqP33q?NS;|b-Vdx}F;F0*{0*5m@xW~8?%Cq@PyN0K(m zk2s(>zy+91N}70NcV>vD?LP+xUjrIIhjfR}%BQKeguwzN!F$!n`@SH{4aI9Z{Z33l ztuSBIujE-Bty*WgH|_{=o7t$yb^mc$_X3oO;B;8U4Lq#w9)22G6%pGhjvAHDp6y%9OQ z_dR#K%V3Z652BqUV=T!x(_h`bO}ek#H@!WLqrdg2Nknay$fCe9J>l;Cx4l?CMr>uP zI9uqc&4aF#QYQZ@PLaoou&pmj=xLRFX@WTt7x1sERTeHYQ&C#AZgNai*xOceCb{3#JE+9gwTn5%CpjDJ` zN;Xk(V)6REpGN!)yCVmExIL>g4#Mr4z5d(1a1>Z&?Osv3c()w*uhi}OFMFL@wGP1G z0gLqJeBp~@iqy~l#0Zrx6>ZY}Ju;}0`2aq|0obc^-(u+6o9p6e5IF6TQ-_3r+jgha z$GKA-{~18+KU!TGy62_JPd<+qea8gIto|+tP#G>9B#x#RQoNKkws%SesAR_mVnJVJ zIhyA6a5%hR)$Blgr|HIE6-ee2OkMnEET*@or9}nQvz{b7EP%&#t7Jm0F6WEy6cTwr z{!I*)OS&-4u=_m5;Z`hdP*T8j8k6Sj)ksI3=jxLYqV7V3!g|9-q58aVSL+$_{crOG zJ|pJ_nt`>OAu_@!SCmx<*6e%o;}XpD17t$ z3whJa-e!>nJ2+zI?v(iP#kXtmIp){! z%g^kLce|urhWpJU?f2}d~y(qOS z!u-9X@x`9?!mx(R4xj|b87BT$XjcXiiltBX*!cgliKz-enhhJ4efPYMuPKl!zq<~T&Iq~~y*O_*`8#c_d5tV0?{+J5> z|J%{PslT82apnJL%C|sKNaG(*1cW6YxtAI@re$xzZau)593nrrPOv_jrTN*ORK9Os~ zHc~$rJZI>|zn!hxZ=SmgxI(d7B zc|Oa|s`mE_&f6tk{5JST;g_rrU&4}n5iy*5-^se-`_2??*$_(+!-X5FR?WPJf%Ju$ zAM+1A#hTz^KBRA(QR$k~6kj`e>w!Dg!a+3up-3`iFB{N?HEu>1ksiIz+N^GY?uxBW zeszB);HV}}+PYd0%RzX)jGu97LYSp#w{pbZPH?e;PjFuClC7^&MTKZzf~aV7hWeT( zO9X9MmDJpOH?Z<~Q1Z3d3b(M;OLs3^&kLfZC3bZMUte$C`2Bu6PE<2aJ1_=n}jKi?aG+vkZPy#GSn zeFc-ih9@jA3vaiwEeuof#H4@Vt?U;kiNr2YdE<3^Wou$eNNg*59k^G|63^J3Du8}T z6nf{zHoDPsJGIVG zbf=@Orolnx4eM9)D*NVMY~1g^r1GY)tw{whBy&)|ghY6{&TM11LfYoF_^w~N?2Fk? z3u4<40&&`{26>%gw%f_Y({BDju{-q)zdRS?^1j$^=Piv?5sV(%?f9)ty<|ExG#0R$ zSP`^0A*O8Z{$f7%w{Xup!&S-Y%RCUazk6wVuT zHn!?jk$J7>MR8E{NS&E*mC(yWx#}%2?Cfm>q>?Gp4;LO)1$j2%XFjHQatM}pCD_;% z%NMrtLl5q>BC@Zmtz*hn^==$N_*A~_Be4%zhbDZs^!gn1k@+22MIiymR#N!g9dwdA zDpMTo3dN}l)_~x!55?`K8%}<>iN4L+^5h)eYfe0%6h$rGG?`b5?MLuD2|TOZqqKv5 zkwB(K_N;rZs1-T;=fNtSe1om@`+J%`8gJr}k)VtzADD&bs(6=HD)XTh7?<>D-m$tx zIem*0l$+V{?L6?ztL5g&$<7Ev4TiU=7U$$pEuM5Xm#7_8;;b ze2v%rq|gQSFP~~;oQneDT(=nn*s-d+63>Nz;UCbns5xi;@VDl-5yN`-S3|;@?aMYz{?H}6!Y1!#?Gv@-JfT3pwpB(-aM`gK66*dGBs#?wZ$_8z8y$+*v z25gs=Al+bt!C!8iZEHa$AYa^jAxb3>g35t`aSQd@w{?YPkNk&N+38#|M5xOtGKb$4 zNA7X{p4u`}g;YUy;k6fB(E`GP>9H%{t(VWuy+4Wv^A zj2rn)4n8bemqr)7Clz^n%yY+9D?;+PuyxIH39jbC3d#4D+>5y&%)^||+p*TM@y~W2 zXgksJ5KVoQ?Ah13!=v*QvLb`amGl)wo!oh@c8Yz^2{O47(~ag2XAnX%-E%Jf({n}9 z6bPzcCUWn>26Jfqk?;iJChe29FzP^xN+(Jw5Sw-%8WvFFpCF)Q&KRT)XH>Z|b1}L5 zlZyb~ZPCF>O%q7ak>k|L;bbM<4|{XaoiM)(I!oUty_9koj0;fabh}NWtLzqo_Kf+b z+$PBlQth{dmUqgyWHYZn)+LCCL5UT&r909`-K|`gnZw6B$ z6yI=5nkKMLOGa<~shwtu9u3Hpv^ht9OdpwbN%m~`#t(0;*~@(Q^m<$mav-J|D(@mU z0jrc}ZJmER0|0e1pdjE&xFu0uj<1%gF&@fWd0m?yVGI^G6Let=Ca^+1SGE7Re+&xh zyqOQMc!5*a2`GLML7+1R{B(^VP-W6IT7XiU`2qo|GUP6BS;_&jPHu~-wI=}e<5BPZ zpj90o)wYKQdk#R3$faLBH(h~6TbE|d#|V*9+JILfHVFz2E_A|~nirt<{L@g47yJ6z zl2CRyWcS(2{e#u`X%*p9I<54A z>kA~!9~!ZD7Cg&9@4J#nnPCVXm-1(FtsEV6y)oUdG(VryyP2igqlu|f4=cb5g}bfY z`pQ;xvn#sz*JF;{i>t4q!}2$K{kDUb?hzjXUJH4&K5GjXgIHC#J9qdv+_FIrbb+`J zlBdrfqV+A$!203hKA{%#L5%jX;Gl?IF`LOvM@e#WOPaxYd62$ht>s@_P4ptw%AvP!@ciS`X&!?+`x!ayJ|^(AZp z_Mbvzm@!Z62~2FA{j`VFT;0O0iz;HJChz-O*M@bgk2?FrbttnPdR_-2OJe*z}cVjIQJ#{Fh9MGioG;}7i)r)FYR12TsP{ysGE zoF6ma*$zmvy`6WU5TYO}NOkh8=8uJ);YNL$&Eq9p2$zPZrDK}~KX=__QIJYRX)EMF znEgj_W2E!rXO80-xhHyU_b9;ce~;qqyMEBEs^SW&uCF5vJy)=Q2dqhUn=sH{Sd8~= zG#y8JlV#NlZ&{bPXJ+?mk|LdX?gs;1lB#2`VEg=Q2#M>J$d-?Rkd72>Gy(jvjnMo= zx$E|~NEcD2#)3v}qtC=|$K<_7P8GV9e95eaoA6_O#aCOof|9?QQ8%4vpXuFKN-WqVk0TQu z@(um(@AfrwZt2M)DMdomeK8UHqDR{T9t;;t-~G zm0`(V1lgCDL#emRRQO?-Qd#OYdS%T&%`KaYEeeYO8@TFS|5O$h7UqB9d(7~1c&l;t z)!vQAhPl;N+fyB2Nd?x3w-S~^d?K8i^2iAW$fN3S%nRXX)PD6CgWuFLdADG;U#rf~ zcS8ir4~_O<7dImBc7;+&VF@0fVxLwmgrD)^hf|)|hS=up0+T^PHhaL?vKcnCN-qr@t`$?ur}y6dvO@fT6W zakNd;S1LK^S8a-!RC{qdc2&5qO{#xAB)5_j)0!3d(XAYRW?u&L$u)e`b??1?BB6dC z$`X(BUwQ7@KAlz~a6gpimYOc%s}_Q8&Q)f>^VPj*|LSVAaI9Xi!6jIEhd_Fr`}5ooa?L=JSxxpltQhTFoe3%u<$6P#U_oKn)gd=fH83G_3EaG6o0v9@|Z_ z`iDiA{>7VI@pMA3RGIO)j>+tH?|xK;Y|0^OhmAvBI90+;CI{J+`kkA`l<@Dm7k@*8 z7#Iw@2Kn9RByg zFe_u1D1u_P1kFhee`XOrM)d4 z46`1cG6S0D6Hmd;982k`I#ddM_uuO<}~a*cVH3birWhpd|kxL z#gM6;E_LxQuQ5rFO>+(-es!f(gZp7hY=!vjw2Uw8W=Iop^i_$S!nU~Vr$s`(!Wa$mZ2`(pL(MHodQ7rif|k2e!K(9F+G zwQ>2luKuVbf34HgGkuc$;Qw)S6;Ms}ZJh4zl5QrQf^;`y42DQIsFbwQHM&zeM~6X3 zPb4OSOhQqZFkmVI6A6L$yMBkmIP8pZ&hB%c{}aE+axgAT%^DMq(BEgUAnA#@6L>5F zFYs`vVKc90ifm!nDm$-LxI)WcMU9XdL8$~|gUuvD+TUMU7r~pCyx&;|%M@%^>QLi# z{mrkO?E&oTCU3W-XfrYZyNBYsD@g$th^k~n=cTrxQ|&3Dn#EWZI2_*M9o^#vZ3-(I zC`&Y&>p~&-Mt|I5G?_}QH*U^9Z=d*j4)hMuEkG+eWFQmw$5dmfQDc1rAJlkT+l!=< z82*5n>UC6SHR6Xo{CFTl3nNIB&x95P>OphZwezDprltkA;s7hTGe@ujNCH#0nQXT- zA~=EFb`JRoG_c**QX32w#m63MK>NVG^D!G-h>8+@=LYb-26h5ErvqOC8@58suxaTH z;Kx#P<_HJ@tscVVgYU6&dbe?o_{RSMElV5>do`zu1|)``P^sJa)pPXfe{fZMS$IQ> z=~}!Bc;oO8)9mBkk*^lE12kFv;lB$OCPOtMa= zT&GsT0Ta(O6zB=aT-&MrmTPA!znYbsDf}~AL5Wy`jf6vOusM1VB+Kl?h_ZHiZ{FxX zb;hbNkO%gI7v*Tf1v1hC1mbHHp?<-kDwXu16NaAbRuu%=Br+lwcWyzpK9C_;=A_Lm z2n2|_1f&knRogI0a- zBz85SV|-qio3)WkE^z3w)|8KDje?!FQXu7-m5a(aVZ}R(TviH-SXjK6Xud5Wl8yTBki$veg9$gLtXgt#t~G`_>N1O==YiC1O(RP5L5jqH&X;xJfTY zqoG$!_j{pTSt5BMudj|-2&%igDV*t^tI+J~xwH z7I4Nlvi?Uf66`DmB!XVRG7U^ntj}@1zPuwwuozs{PXc+y<$ zWPlqZ^Wl^!J&EtEhAl^^UPDSoQ`mC*BwaK9fh$3L4a(oFFKZm=6Fkg#md@H^P&Z%`IE<5#XmyEHAw`rpU zH40y^#q}kDX+HW2zwpSjh^2G3e(Yuvo`jx|T*vN#Q=IQ>RdmtT3}D~z7KF8Xly6l* zNxn^iCAr}a<@ehqWspUlC15Y|fbj5rDJ;JWlw2|6ehej1$2% z*~194a4w#TLeu6eeUde>23{j{LodY$Z|oreXUU>-&mY+sD$i@-b1#)0XOv9^G=C5W zep7Q3*(>4u^G|bVEBAt^4D=gXb991!UL+M|J9G8vZX7V(Q5tOr2$28xG7fe`v&=|S z24?CGdQ*ZDOj8B>ZGzfI5j;=}hENCsnri-Wrcwo1B))K)SoollO6EfS6yC8aUIFz>QDkZL>^0_7Z>NPMx9^O0*gOcZpq zlTt9PSLAdTY(-e+)LN7*@VY zB~5Ykr4AQHHd)_YOI$Hjw`&%1wM)CckoHrcL2XA#r=5N^GKGK6l1p$>cW z%Z-xY=1EU|%QMFnyGavef08M`9TJ772yA;=e=hpmEBfxIet?v*iN=HYQ*1jkwXDuP z>M2~S@S+=L%=I9ur*SpKT%wTRZ|?&-&pE$rKJuc_(I9Xrr6`J@Q%3MNpur_t)9KSczyauDNn z9*-*CdP9ax=4}PYR^I8urFmFYspRa#0c9gp0$(vl`3EF-CY8Qx`}_;0l$39keEArh z41g8Ty3xu+;c8C&*W-3pDeZt+;4^R)dbv|3BEeZ}ryJ1P6a$`1bi8FpEI-;D1J6~B(SVc)yaYS}dYIxHZdU)`Puy6BZZhdJfS zp2DV<(bD(D!t|`Eg@3vl;Mu(0h5X5d94>AXXdzP_UA<`%%k)dB_E4aM{yxSSlJ|1e zzL)@>={r3^=GV4VJCVaynq+g7qY`1*Y?f)_yUW6}D7W)S_`wal2XpBqq^8c*)HIZh z83~TMQ!i3|zs=oWTIO~I;Y^pf%q4=C90|&~4?QQJKF0AX5;SVvp|-vHWbVzE))o&ubK0t~ zbK=t+F^DY(E*D4HoOb;&cEa$y0BLSZi*$QrdcKV*B_@3yRThq8T)eyNCAR5kv_D9y zdlnY%u>UT6Hl1*;mTEYM%`^GV>*&f7ZF;`HM&hLap^}a3=`&c6(AO)`Oh6aM#QMB8 z;mLAvv(HgOnct6K&-{55#7~J#Q=IaN-9s;BVvqu(5n8)$ zbdr0t+!m?QMSr0JpgUJ9)62I!%ngKlD3&-;+oDH%m<%_AeGgo-g?2`+#gYhdw7Br) z#>>c_Nd2cjIwI_huc4<+S=Pc;aX=io0Ag6c7B~%!1pi+^q7Z%889S47Lt6=~Lumf_ zK>hjF1fT;_2R0+@GDsH5k11S|hR5E@FpxowlBhE80W$7KghjVfXJmcN z(FV{`LD-1PeVyJI!lWOdh@`B&NMKji^x(u>gV?+lCzS}P+zKiVTCq9-oGNpob)63dGyAj$b<+`EQ-H8O$I&X%Yj_U=6zwzPol@;}-}RO5e$j!#Bd~ zS%3USvUFrE^@J;j^m}qDHe+9bsoP-zv%$|DOy^OkURrGXmZ5)yCr=|9Z|>t zfB$m^z|x7pfVnB3>DPmY3CB@$ca)~_I@sE}7nJONR?o}IRCC#)LiyUKqJH2CK5F3a z&(1t?7FIx38?Iz;7jg8HSZ;>*mePiPUiP|Y$Sz2vCh_)@cv=91FPoDeUql98w_py< zLDTLo+mo}O)QvJ=c;Ovf3F%cM@CDniex|d;Yy>lyN-; z*x?B^cK4MIARGdFT>77JQv@x$_o=C8(b~yUBoZ=SLT+bXzu1@?+a+>99Kp51;qZH0 znhR@rn09r%X`U(1S8m+I+hy6vX3_5u=3`sIdbh)IA7@|ab7Dz)!>(%ILb~om8%(Hv zLfv_tfCsM1#4U3QSC{jhK%2foO(Y0~?Tfe%rJ=VY6+6{uY4Zx88LaTwD0u2`sc<-V zf8}_Qr6+GQ?ia9^<2$znj^^sP1K`9Buma+7a+ExOW|K zQ87Jj?zX3E z+GhD9bkd1hUF`~Sg;~Oj;E#s|^+t9i<-hU9#_>bMTwV3zmJXL4P*!sp1+?c$RCT*V zS^M|H42(Iv%v*MzC|Mzv)fm)`ZPQ_f>4EILZu!&T_1GdUjjuj`POX|HuM|-G%_398 zXlVRZ!9ze#UnKXjzNizMqU8zLP_yHwZ4j&F2qZ;Gx3-}T1XbVU9aGtHsF|#l09x>R zkKj(#^m?vPhx3LaMaDaeJYMBbg55+NnJ1IPDTIqJo#Ekmk%4XbJTU3eeeoM<$OFH& zsFO5CZKipx$tu&>mBDZIZPXRyttwq)j2%3|vkscRuO491fGv%@8bY7VN8|o_n!M?dKVr_23m_>F22w zwE~GA*EZTozx&+DGs_C-nP761?1>q@r)ps`9^8D&o~BOSP;v^t&$)oi>e~;e-Ynw_ zNa5`oR!icGIT~KS{W*Yk(8qth8Diixlqn`HVS=qqU0a%OXVikgKGpv2_S$HdFr4J7 z_g~n_>>Xj)HEGnhroUZ2$LN=sa`(Mkuay$iinO?z7h#RM36v4%fHG$`J)(F-bmz4^ zy{4a$tvzqe@bTzF{eo>Nv&@g2I$cj-!fzb1qcH7E*S5w{jl*e+R|*{EgJN1hF?kg1 z0nAm#Zs`2}c?Dl3)%;xq2G@(%VoDm$_kQZM#c(wNiW^$TK&~7Qc8>c$T|>$J-ZS6n ztXHk`FMTEV0YM<3&DjRPdJ}*^>_^mA%QbEh8ekrP$`|eTo}!2AzT}Eqiu*NoA?LUX zccwqb$F#%N`Zpq3G|oY!n4-jBy9B(ee(kW0OeNEZc&m8g)mrFXdVsfQG*q0tt6z4f zV_a$?LJXdR^|g$99PlQOj6N1juTg2K)2&p+XapCVuV{PoJJ>iZRvM6<&)sT0id|Ye zv-J*^h1~M|?yoiQzp&bK3r1wy7_68hxR8`0pH!J4tTs=wQ9|qUUptE_15Jprhy~eTn-CUR6CF0pZFq)90+4V zNLDB0DnVsqww+aKvcSn-fRQ!%WBq}B>)+QVXxwCxUOTAadEV!!HpYv-rk8z%8k#?} z^?D~e5-7z4;|W+-+iRz6nbdeeQEn55uQ`nDlLOEKi2~?Vi8d>luqtNVV4YF`zm$lj zU;fRkVu{C0C6Bg+wB}=~?~JrRPxUTW?oP4IS!w3;f_NYV4YV<%4E8oCs8BUXGqN>{ zzneT49{*7D`%=2E#yK{otyaLDgM6~RZhFbcI~La1?1S4Puyo8vs|~$D3aZ{U$kr&9 z@OdselFgaDD}f6ACd)(%m%`gTlbuwZ)^{3EIafJA{u0DnF9$K|ggU{L|Vyt2P} zY_{oh6Ku#d@9NnZw{7opBXb1;7DJLBfkd1A^wuB|<9qzjdg5Vncqp%6#rQY*y6^sG5^hKrVl96z@Y&S%I1QvdNJnaTBB$95~&m)n&@6xcY(QdhVDE?OI}(UchXHASr54K6iLUf#AGYYZR^B^ z10%1qFtv99h6NET_7iEiPQu(UMuOj7RY{f?bwV^JN?9b`*c4)VuhLR<5iJ4Xn@%yh zP~LqFHc{C%QDZII!l#mYDI$jI^=n3_h7C-;i?>}Q_jxPmnZwycc8%&XM>795zst8F z3B}~5BgT;g_)J%(H=)=P0^TdIoYG7<^KN5Szp4!hjQe)$$>Sj6JBrx}8tZTF@qnK> z`+6-76Kfz0ioN*pQ2vuMuz*rxGm1G?Iaa~Jp*{>%W(3tC3k{{-WcQG$!@kX7>#y_f zz%@oLpj41*6@ zg*!hyZ%HuNiPQw)#nY`I@8puBxPB&s`8De&2=Bth9-X?@hqBxG(>dy_*k*{coCMnu zDIMFqeeKXddFJ3N!?V^|%G?X~g9aNuJh4L4T9_jBt*(fKv9Fa3CH~re`07PW>G)RR z)Y?XHd;y|wXy+DuyZv9%)n1DIQ&oH&_YOGHw*r7~%I-|D z8xD!4?>DU2DfcTh^baELyEE{wLW+S<#Q|wHzL}xaZ1R?wHzRLg!n-AM1Jc0dOS{ju zBb$Pff{YrQj(jg(*_ljECWhR4AM={k6yI@*vYE*br{A^q;=?`SNDTFqwN;G$_>^on z`8QrI%JLl^SrD$9K)1Eje+$}paqVQS(K6UEI~}fPA|W@uPP+&{COm2LeJYv7qoQvh z8{w)_Ro0$PZ?}b9$FY?7Jxj`;S<+a);^k4&NH8sr4OYGd+W_zKKQvM&JoJxGWfyH3H^cVUnXF**tS%?Qk(Q zUrG}{ntS^gQmr>-?p{K&@X@-N`Fv#C&IGVwqKhK7>`E$u$I4V#col_GOR3c{bR|R8 zSkk{_PGE|{vXvH?(Vi7@5Tl`vof3!_Gw#Mk8*+p6=y4Z`hRAamcO=VJcW5XnjXyl^ry^If= zzPnn#XfL=AQT3$MBO&~f z(xS{faa^{NGvADkZ@AGan|EJoL6JQaH+{+&n_JN<-5q?Cx zfSAm5!D~I?y$h6>PlbnF*m(J3rW{$(ozNj7tVu`@G>EZAbplCpT0RB|@ z(PQO@@5@i4d%~m;xp(av=j^$DlC@mlL*)YRJb5WO!Coy1+yKXn${}}yZS*9b>#7Er z3qVlnDYVu+B#%HiP3Dk`h zjF}v%`bYT{O)?hBNbu(YHyE{_a&)osg)eU#?QMB3#ZT)J<+u|7V;to>O{Rmzd9aS$sFRT7lfq8#u2%<3v(5D2(MtLvzv;2Oq4U;6lo#%uC; zs!>w@wn@k+mONA}bJ*3Z>F+Q#Q52G=@$5?wy@K|d!c4g0Jm!_?Xsp9AS5!ek#>&h0 zYid`v6v=(YS2hj`k9R6sXO-fqF?lD&I?vXU=+tYCDzww6RP!m_y_?nsaKCT!LPUq$ zYti)7#Jo5O(p-Rt)R+I|^0ncpcxp&}CrNt2gwLtxd_khw(b*5rgoH)Fd}d8yxdRM%cCnaq&U`p5~7PeTf?V0hAAMd%$RZwo?l*DDS=&H%ouD3!5uA zkx{bK_{y#Xa?0^fOwQ^f2nja8_+_VfpcQVvVJYv43H?P|1&}BuJ)BGK13L7=$bPLb^2zkkZ{~?@yk$HFa|r^J3K{b@Xjz zJ0eeAFWfUmw1z(NnhnieG^KIcO7badzZ&`z3;C@n-jL>@uWKHQrsB__r-m!Y{#c#i z?-gU7hqYzZ0DPO;owY7hq07O_=HWe zb@J5~7N?9Yw8nC|5+jg+tyO4Em~3_(qv^4oRk6n#Q{Qe~%{GxD6L$}UJdjg}65|vl z+Y~UzI-*kgt4mUY7J@AO{A;s;NJ9`tMQoD?KJVPE-M6#|7laPnB1@;7y68;gioZM9 zFW;P3h&y@p?ACe`v+KNs9@4eCNH%bNWd`Z$(eLSLgxK%V+{b$4LWv4A)G8{{<{GAI zCQla>nv206lqGejDiSvfpF8dD)l!(1}P& z`pP+9yzTtMHZ?T=N);+eZZnJX8*+Szn3kOkl|z2_%U&8_%aArK&zF*p6M~EDHE0Vo zR+E;Iz}o=HnuMV8VxP{aHZY)4(>ASe*~+<#4=SqN!gPQ>^U~WG%Q3-Z@kmH8ijy<9 zdQ_4eA-|hMuFQ{WV`fsZ(SE;;B)sgcY9l2RJw{D-eCTEgZ{voD*8JfE##eD~a{?bn zWU_&50pBa>&kqsL1`(k_OSg$i4ZQ#1fJKymx@H#F3CT=Z^W&A0Mje)Sa!>gTLBppFZDKw|3X$K2Qy-;<<4xWe4eZtkyG@T z=($V%zJkdf)d_2g`V+A-)AmM0-{=3-|ftI~fsU+up^oIU1 zf9=Qd{=+HUI7p{8EC zEwu@sVahx^Rk-t?vzXAtu7N%LCmNTgo=<#;T^VWq_8po0Ii^GK?@;-jRxNm9V34iQ zG9*%Plos-%j+j~#N2@*Svi3pztP zGO&Z>LUP8WRaEWP(g~Bl02kL`#aun0aVM_%>rVbb&+A9rHr{GAv<`AnJ)GDD@yr<@ z!IS45rp;bCi_s=bDIJtniGq#(GY&Ok1ePQ1+8S*KSiSO_uS zu>7g<_iD#HNkKgIRC86sv6sdkJZ}R1RPGyUDNniLj%J6I63yK+cU8=rxqXwVNE%2X zJBS|_ko=mKkeYpC=9-v2bKc7x&~JFNQq}8j(W5M>GTVzmCQ5Xz0M71vcF^$&_s|65yVKxQ)vX1E*mUG#3?vINAl~&Hp(~5GPf2>lQh_+J z1~a-j&VC8V5gkEBzP1I1BtQX0=SK()2gJ4j$!Z`SY6jp?A?q%REF=R7-U|(%z^AHm=8}wILf&B@xy5dOY2X?0+n4>yWN!>qrAF3jrz1Z76TN|w z-TB)`l7D6MMWYUzEUF!YFr{lAJS9Zu=QT!ln)kiJXxQeN92PWawwMN`gN2)weKXWD zvwnQX^x_(!bY8d?RcfRxfB&#?L4eVHx#<_N1Gv1IC^k`5ZiA2~V4<1z={9NlGl)d- z#@a0F*$iqe0S9_9BI@ahM#77gZ0HcOs`I0Ro!Vx&Oi6WHBEm+A0dJKBfve)PTydDL z{E2?-Dutz-V*U^LG|`tC*u~ushUv5$Zj{JH#yqWI#VN{SjmK^oC_N@MOoPZ>XNh>I z^$?T-0#cl5hDlqo*b1lyi0mD^yDuiri6(n&#+z73`ghV>NSVYwH#KGsR}Hq#5K6oL zT(d1gaA&wVTtnrfPxY6!SGFHQlGDNjsw!DeZhi3S(==T)6tuZPxO#^<@U#%8p(o9G zL4}Ma3_l4!2TVO0>TA>sCec`(;bW)z2EGN{;qtdij0=s1s(SH{$B?la5hmV^I---m z7u(52mSZ1d=NVn#g;Gv6tg`urEkrEF`+otGE%D6RO`%*&LxPV`1^c}TqwL;3?^(vb zbwqq3p6f}wAsjv9f*I_`;@RdgGBk75$SvXlrm$mIc?M_7{>zS0m8phyVlh_U8>x69 z%&0hAf50~vY1<5?eT=Jbtx|og&t+4S#wxtz!F$K=KCQsWI^MfMo}3%@i`VQRbQl;t z3kUS{I5gAt-dP{+0fAyazCKXjRbpmWZ0F><@I12)(9dgopurl$ncWwjj;lR>j8;q0 zQQcr-tmUF|PS9l(nzklW`O4}x`NYAP$T{SRvv#01Tx5V-V(v8W5^*&8EAzCmhP;gbBU^rFNK^J48^{n?h^&c2LO#qzR6E4Ry)V4cI$;}R zZJ#Y|>nLmmFOA?z{dH)*YBP!kiDjmHwJeTGrUMY!#g$$eah+ zA*!gRRzsyJRma-Z{`P@COa3UHA3TSyGDB+ru-%#0(-XLY=#p$l^$b1&x<)!r^N-r} z#{>Q>t%ORsB7}V+`ai4cd_YhaFGl&mJtGx)y<@$CYG2cdi@#&W%&C2phc;S zV5QzR#m`Vc~~qJlQ=F<+b7D7 zAdwpwr?8ky!75~q2!L`W2Xop;?%-XQ^VTsP65kNq@}|9$R``k*H{S0PtGNB2Vxr@% z>U)>9sdCx{p&7@PvpF5w1>vC2zld9|uqXzm!(t{YI-hGuJ^8CPid<#7Ds13LYHZa< z0==^!pP0h~#Ul*^?}O8)%#2N6Xw<5ul=z*A3ds7N?q9HD+UIVhajDO0nxc=74MTtX8gZWc(bP0$t!r_H5%aOq z5dRcckDlk%VoK4!FkBDe%S6gvA_y<<8(C#yucbd--d7KNL=VTY{IRVUYu}z39->(e z)a)v|?fb+o9d#gwrravZNO+!><#hDMu$w>KcvdyulDxw$TM<8 zZXY7^KF9`$!DmuCQ9joGLX&MAwNAd_{=BTUx44^a&WyGDk8MT<*sYLHzQT#X6~cG$ zpX^j;&`dhl_w%JDs;{7xM(K^#FYY(;p+P=);zC0l{jDCtQ%+-aMCGkWmqtameo7k8b}%VbnZ>5%qRD(_dM+EWR}f<#GT z@Oa_!)P7OZcg{`AAWiOf>xZW(whTU*^DtX@QNy`_&I9>d-^weH^$Dwa#(|wj zHG-KbHnJIp!t_w&(7WI!(%sNBg!I@>malB~hU@Y^!}LM8KtmaaP+f*-c;nq6$+E0v zzG(0fAbjD~563iy^|CyV>0?Y;gc`az-qK$Vgw1iYz}_ZmK>25E4ZUG?oY^0q{p`v# z9^?s(sn>;}!W9p-GNpQgxbpsJjb zH}@=3spTdkY&`!~3N9R7Ikn9-6OAJc88(V(>yI9>NmPiu)k#%sUY0Wt>V9K!d@$P6>RM7L6 zdk!_s`sidgryi7;1)g$!L0Skuc+&BEq|1WIw`xVzItK)rQZ`(KYHd?T184yCC}1*$ ziW=Q7)B}XxE7YOl>lqD7cxV?ay4@St=S;X9mCS+a-#rUh?r3Q2R##W)cTn>}-_L*X z_}R14E9rNe(zl953tf8|j!l~ET!juA_;{8q8#1sGrNMcdtk7_fD70yi$=m$Pws zXWeBBY2wZ=Y{gMSWrY=y3;QeT^LmL<+&7W+d2izB9L+<_g$1JkuRmvk2U^}!wK1RQ zL^0J$W%2EI!RGkgQBYoe0?t39Ds0W12bNbA9uFnyckijHeq-XDGkf{ds3A(WU;R=t(fJmy&yo45&1_nN0-})b* zNu_+zTqOdT-sv2)6$I&bKGBI?AdDM6*lwd9Z4bfqdHZGsUsn5==2t1ETdvTBZwX)2 zZPz^3C8PZ|6oem&v)i*1Kn4^JNRWgrKl;PLhwEjqC=(GK<guis0zA5CM%;Gf5C@s z#V4hvtv|i#u&}772tXczp}9b(>*JS$9s~qi-#8}@h)Na2?Wh2b6ir&+0K+w40_A;b zbPb1&{R!{BwhVme+=kjUy16~uUUL$NZeS_qMgxuVR#msw@#5WF{pt?Pm3-68=BPBJd|y_*MdJ8xk1b$5RLYv$>IPFob^+Z8<% zPW2vZRpHqYNI>6)rHP z>>hMc8xjj0i)<4R=G#ned0^-!`hfp0jAOf}zbsTw(0HL&+DqGy`pJKaqZ-$Iq zk&-ZLgz@I9gg+;rh0+@_nl2VpMb&MT3fo{;cU44)rEfZg)gvg`KBdM|^0&nbkVa%- zO~v7oCL?b6;GhBnj&&j_9e1+MrB|WI{%%_2mak$$40Gr`bM2-S+3ntdE z(SLhtD8RgGNBFLj=c(kdt2;o=moGFoTQ=_R_LJmK+Dot)yw7?P zE?G(DN2FO1VYa6G(%(TA;~D8F<*kty;(N)TZh2;sd8z*G6NzxUr=KC;TTR^|G|e48 zOQ%|w{B1*EWHJiuS^hzVvO3jFEhKoe`h=)lnb~%!W3Z;4sB05%j@-aBNZMo%viF|esi|U9B{dxFj!QtqkUbr%HavamnCh6Lq zQfAuPhs@|Ll9;ycHDhMaWOh;+=75Y!l#X3&W>?HPxX12_u^YPd}5FMn)%BXq-r)6ayEkRhe*KHg_woWJtHK?< zJ~v`F1|mcEfi-cU)-f=?f*tJS<7i16k%?R*N{KD2M@c7)OnmERFGEEdJSe)pN zsNaH;MQvdv_&dBcER?NZM|V=BViU=D9;;+g(c|Ve~d1=QhWc3LL5QM9a zY{{j-NYyEH$s@!ppPDaXQmk<4xzWoaywCtXp&=9dosahpO_5Gvb1jg!;k7|l%KBL? z7NUjMYc1r1^>iNW)*}KPrDBL6>yy=1Jd&ZHF~L8Jf1!z>mBQGXr;{%w2Mf-J3{ zbjvrKqw}fImE8}ytUJ!jE;}<6cM}_H*NUgycq<@Knj7pRz;C{F`Kf9~rMZ>s6^o?% zlQ>8Mle})7F^R8xi?9)Y;hB39zs;0Vc6|Q)H;FENPT{i}Hqx01+5&QncZ!^JheN#n zo8BNI=e2N>6&xr@gqNS|P-re_+mnh>Un7wo{-IEWf&E?EC!^FHLm4O^OWPHkX^uxU zPq5>{e>?{p_<>{xm?A>$4&N9TrWniJ%K72W#sf&`F+|o+%h<_PAF%!SB24KUa+{>l zjP;RXU9-MOeX&Q;P9SaIG;aXuCu`Gq`g~&@&*Zk3#k50yl7UV&6%u~R=@KbJn(cy; zY3|L|KsF=T_z2ED=VZYquVAbCf$_QIAb_Y->f{>RqXK-9-vR3-a4}Hcei{3Qlz9vQ zmrdiwD_O(=pmGAR*#{UJKqSRoDfrybRye_*10J}a07;e)x#wEj%>NOX0WI5N4X{f+ zzwx!806ZV&Z`g-I9R~SM)eUzGq@lamf&U0P=aE`h^j97?UJ`sq)1NE340I<0#l=9Z zbRb68LF*m|5VLhJ@T1p9J|tMBR{~5&&ZeIQM_A`75qPa@yw}Ls{sea{&-p&i$b{)( zyz@>{R3v(MQC!Zg6=)iA!j8QJV*>Q^UH(wlM^xt7;X$VcEWA(9@6R=VG{&Ntb1P}c zBJ8sIqxlp)WfwsYC(YCLg{Z$R4WACR)w=$+22eXNxisPG132g+4I<3jE zKXf7oMRVA~M7pL|>+A9|TE#LxMvlbqWl|Q#TFc`%^O+_}trro3G%9L+gPaUD%SqF@ zcpJfTLp#c84HX+`>;v-|LV@$)X&qJ)%$ZoON&wS>PN$)x2FKI(Dog+yk^Ws&f~AYP zla^d@pQM_Fw$~1sM$j_+xl;NuXjP4`x{OgKDgY*-f?7q(R-fkp+l5& zA}k>jDQeW!dtLrPP(%mn=lMu;GP|c@ygfNuj!%TAD{kB_u>oHE388KjFmrW}MFygB z!;5P#>dfl31u4baJqoj~bHD5_9r{=U6oTixTHnI8t{C3e830j}pbZ4f(J3%x{(A9G zH<1_^sYx6Hzf%Ig5dL3(U`zZzg1Ua7=$iq^VPXIn{olJBs}KK2K)oA1sC5$e4fTJq zvtZx^*22V{^IghZSzVbTG$nhXzR&;d=QDs(6jMag}dVqbH3$FA| zoos-0a>hYpf{cnn!_|7K=-*CzQIe+t#HqyNp?`;KnXcp5M8*HW-Ynt9%YC{67HUc% z%0#-&dUzePpnRFFjeH)zYSWp-{`!i>;ls&*0GR8$yqou^Sul7);W&@$Ermw@yuonHMX@LmC%u zZ_!FCS1vGmsyW%baYNFKIv=VAc>Wos(syn>#~YI!^S0{PDS3?W*xz!O5(QQGdq})( zX6+D4(9H^dtZHrWjOc)rWUeCzVf@7l(4CNy-m_zK^gu247vsoeYKO3fJ;;T<*bQ7HZcCM~Yo8+Q95xRw zO$l5tc(z)}>CVk6f6}=Ne&Y=PhjTplwH{fOn7Q(EmNEw-Lbu2!_nejGKZ4)P|EyfR zi!ft19KC>t<>g`77nfroTuQX|TyE%^|8MKgcS8iEiZnox`a3Y6g>?hE02bk;AnV@P zxbekvUz>q8j~ffz%oPwn#~Hw}M#goU+hZ*q)1Hd(+?I)f7F2ENV1@{fym!XM#IP*{ zz*bdRzqRYGgQ<%@FGEj2TByc4Q*1>lrEl9OHyih&F7k?)ln=dlHm3H<`xlCl;kEk90)7Ed`dMiwlOt+k@UBgi9X*wY!qj`_{lK;cWTP&QA5ep@G*;=%w1N?uW%izE4voP z{rbJ%jXPeCUQXEcyYY%mn5W_D{v!zX_Kr)zTAes7yHI+z{C19i0%=j?6*v*QB3R5@ z9Nxn@HQ>@nIwsr_&$^XDBL+~+U{1%mqml3Es!`mm&F%Q!P98g?`e2_IF)d^=G`j6ma>Ckie6nH3* zA!>ei`W1Ezu0#N}xGpF@S`JYK06OcCeAXrqTn#-jTfTRIRp|~rt8$dx~m&Fh0 z9J7DjD0Y3A0rCNW=SlfLXW`sT33!jK$bXV22{FA-;sC(oIayWQyI=nT?S!r0Ht|;( zuk~tVgSPqEl&Kx__zZRyk*9!3Hk^$`Q1!<<6Iev~l^T$Chb`;}X}~Cd;zq^m()Wgm zMn1F7`q3uz=!#*Hi|=z!-9;8twcucrngS;2V<$Op;CbNj4Af{r6(gB-AD*D5+U4Sm| z5sGC8??h5LJ~$#)Q9Jsg%fLl&-?aXIK%ZJm;}a#Dv>le+zb36ezNG!_@yPsqf9GP} zefvjg?<3mZjdAatC!hNMNAMoxpapNqIPaGAFJnBnAK>G+z2LBK)s#8)+VuYOfpP41 zW+5(ZJeE}&H+E{L7N`91YsJNem}JVvP>a=_B8sqU2U-_9EPcBKf2^`u($SZ?tHNkz zyy^VczJ3K2B#G;P05@at;6UQ-Gufgi)^FbDp7(V@BjdJZhiq+~*c;5#hV-fMXKwx7 zP^Z?S25Pk!>&9*`s+9uxL+d$~gj!`q;NBLjIFFlhEC+YldAgj#@y=R%Dr3Mp+V+Nh zGBxhq0Z_Eer(I>Tii7^yIUPR&u5b(BtTd)|!}ja{fj|#j;bY53amTE8UORsW5L@Dn zKv)66VHsfd6DVf<{v&YtSCDAN{c{aWYsZ_R#cs#|aV{#!xE0J8e)FI9#8%vuoMM!> z0RyA{pWKRTO^z>13xW?;`7f|=iLB)MIhD7e##ZmMPxTziG`nRARZI`uql0i zm>8FkZDm(LVDuE!7_2SMk_(sDoMWHvptUOIpiA;AJMc0tQEp4#ijHoL7bl0ASSL45 z6VGNceG>JhVb?*t{N58>5cm6r7NvIqr6` zRC8o2@{Q!Ea*cFMNR|#?gn@lM=)`2#yUgIonYz1ur0!=w-&QM-Kekh* zm(8v6Goy@)CdSS41Fhd{G+OOzH{4<>`N_r3dsEgPn1H_%&s~0rrvfJC=8N%PuhI6d z7u>Q)=OT!?P1V{uN3H2ngpvY2MRof)O7D;JExxEIk)y!F<-APooy-Z+NT<$i6GDCB z)hqIf?`KZw$L36_QqSkrd_WetEP_Wc+~^plc7z4+x+q4xjhnJ9y6B?p7e;pt=&z>T zD1{V#`-e6qI_lrVT?|Gy!D-e4t$?Jk+;9{7&+qmaxblLP(Jd;aKy++MpsddMj-WLs z5|KdQ#9a}kete;|j~!XP(K?d?iW4@Ven3hCqC%Dc#y{>#28gB6UvW2-P1rGfyVf5+ z+^N~TUfhn2%lV$^m$AKnkk(_o7woP{$#|lrnh|6`WxnhNMA%!P_P0IBN;s}v(>iwd zf}~UiQP1$VuRtum+*Z~cZ=7<%Q}Sr?5Vfref3bTPT~t5*d`?n`UE}`4hv48@+abFB z?4?2;MF#!q1MMQKLVaERzjQ0k2vgPFf#mS0ekbSPDPtpP@0Znq1>^S2p5n{0@>$8m zQUtd`P0HP;^#ZLUY;#3QtRVgGySWS@a?Iw*%oweT8>vsX30t=68`+=1Zp0-QWSS4_ zPF~l*Po#ErYI)1NbUzy$kC@77Q$74hZ^hz3#v>0r99+8VpUY$gX&P;xAt(f$^Zt%aia zd{t=uwBllFLu%aRHIPhnC1c4q>N3AyJ54Q)j5x_UJX?<%DM)Ddd>YgxAchpPx3PLB zyVio)V4`?GanR*-?0Qu~36G1YUsP5f36Px3HZed~6U`})pq`CBl4y1Wf`AOA^}}i2 ziEU&A5-`Moh7bBk?q;=Uok%67#!cE|k=vHPAQ2wksqg#tt|;CE_F%x>Pv~BAqP0KL zW*N5!X;A`TplK>&=e~g6(Ae4VEI{kEz2W|P&9k!cMe7PEDeio|1WqApl_B7fE&!9smPLzn{VbEQYm@Oh4qcot_V6>!L!% zygyt}2qmE5p+AyBcDLL0%a3p6tc?%tp66;QYHO%PzEyesX9YHTm|^CSqKUA{g(oj6 zoBN5YZIH$G{nmX;N6xy2mDI*QGb2xif;5SyTh@KP6nK}*Yf#S%ZRDGtk3?+MpZ*=% z=c^p}+QdhV-J^`A#&y%*wh@aB+0@G0{@Wq^lv{i6#9$^>gXxQ!OIE|u-%oFhCh_fO zHg^4p!$nl#=7e^z=Z~*m7q(v-l``+DGec}Z&^zt@jd7D24)hVr4+kC9=j|J`A8;oI z#`FxjEw@OEr;0m15kGCv1B+A(=k_;`t{wqZMv}xPQK0hxrb#_AZrtaJ%)SON;WowJ;{#GgJmc^*T?f+Eu)xIC zsyK`&ve_-aQ-V@j1EfpE^K8l3T;sERCsEm?P{FHMz-LsB(5vV*6y->X`jVWMdx%`9bvHme z@XGQjl^5~peR;kieXSHM+W?+=pw`tsQvnyBY3U?~(PoJt zW(8b+)|4q5tK%kN3+lRo$^S>kZG-$}(W)>&Rc=QvntE{7+%+sIaYT6VD{PlZ@XO*@ z5FXD;E5wiHFRx~U3O`hmd&XEysstx38v06E6fuREf_}0z{6gpB zrzBDZ`R!`LYZ|p<%Y4-e7MGVGx~Nw7(L}CstYIFhLwME@beJ*3iqB zu07w*eH&~ZvI$NLvta{w@|A~eXNcAm+H`uy*c*k5??uf!NuJU2NGy4q1Qe}dBk<=@ z6fbHLQPM!M)w+SqWSZ=2iRaNQY%(D~l)|y|i^e_6<25rvDhu%P zl_y>MEq{{l8;sH}$iM~K&gkcY1Cr9LSZ+swNTF|&RvSj7B2trUH>$hI^_g7- z43d?QU$n*V8D*DG)ZXaB(b*=IrBd!+Y@EOFT<>&Zr9 zl>&sxKt?BMTj07kD#)UPqS;i#$CNi$ zYIpHH7+dQZ9^@^Ve{?(+8R3YcwcFym5}`juSFZe(IYrBSPLkAbqCT(QGVM9NnXzN0 zJur1xf(z`q51W+q?NX8;Y&J!%=NNo4vdmEX5C0W)4pq7h%f!!<@v_W44;pt2t{%u@ zR{oUsu%)csAo_Vc^xUK_IZ0;u_Q-|9NC@p5tW_SW-+#&vI2O6q?BZ1YA`>0ytHrJ}E@of&q1Zu(Vr_t$<{!s=Rg(eRX#f3k!H9MzAe)GN0ZZ{%2+ z$4P~n3;0{ysmt#kLsz1I@Jr}DDASA{Jz4;iRw(khCIf-mBAQ#$&!Z%MxT)usZRxuX zr)u23iv>b1v)YqT05WZQ`N%Ds|FyvO7noVlg7P5&d1wC7nR}=NYXTz-SEPv+jAc4m zBUf;!1#|GO36O=llVQ zBB>M=Jf|_U(m_}WZ(j!z`jr-K%s@h<(RXH$)Rh1Mv(+8Fje@XH_LH$R4T32#X+82k zKq?j+r6w7rQpvvK7Sn0-=1RH$5S)cplOB0}5jN(sV#GDHwiq3HMGgLF$dRG~;O&d) z55uT-vRzPsk+ZOKdf~@poljsTu&pQPU3=R-5Pwv8-x3tNk0zB<3GVAV&N|EcdX%!? zMfpo8sYRNlF${kQOoxKNcGZ4BUWJRxJ=)MGYL-*WI@SP3kz7V9IJxa;&qn^&hLm4J zg&51U?iz)^Hr{LcbM~KsEr?!jp9H=!)a6@&U4HAESO*1_rkjYYGwhnk6*+J#Aw`r?DsmN2-kE@8 z02gqa5yww#fFI~(BpE%IdZTpFOu`_7b~)X=7zlEbD$5FLJ500(E}1FgU&(cPxl8nUa+Q6}>+ zQyzS>W8~=5ty@horfx)a-$8MW%P&rV;mkxu%k%avZ0F2KTr(PplrE}k)VWa#)K(mC z_L(bMriyC&nhS^g{leQ+V*}U+gpc%V6!gTD5@SQzf4)ctN6)?Y6u%YN1~1Q{LZOoefY&7|EPf8=Xv{}&E1?L|A- zB}%7``u%@&h{7eiD=5fs7)~XDbSpHqCd>p!dh_3=eoB*eFKR?f;JaP$!yzPeQ>Fy+ zJ^Zqh80VJVC8q%{LbOtmA4#*Fi}N;%tD*D_bD>VX#}`Xw11^3@6M*1WocAQIYT}a4+^LR0eQa{09S1-OR3Jua zu3Hs8w~jMOR58>Biu|}V3m8>;0=Pv=mx;mu=v1q5OD!dtPL{L9MsuMc5n#2va{R(3 zxldvnSs(%{E(0a%+hzPA`(Xs7=`1PSMz61Rfs=jq3hg{M{^_WKjtl;d2|>V(kMBFB z_()PmhpHKQFJD#sIoMM_sl{Sh=2cSlb3`kZ zf#~%s6XT{MpDicHW0?jGzkc+GGkd#Qun=m~rr>~f6f)D?$V3eYTP^pd2?Mr@CNKsH zFz#(kHr;F>v-2Ztn_TSUv_YbjdX77ZtvY@ZH8kcnKS=!Rto_&d_TQi2%PJw}VDshw zzREjmXTOJ5rqmMuVo2E&&S!TqTiIVsXRAW19<>Krt>I=;-{7Wk-*#O7xVb+p@cLr+ zG(s_PuoGr8Bvl30TJ9Z?`yt$JSNtjdt;}pm4`deo#@vYx{()h(`C>zkWZ8w;HpVcz z+W5*B2uJ3MJB;FZ-1X5sEb$zU|L)qLl#^!^EA0HjZ298l)s2d2<^~nB|@;zsCR7N zSpr)!++mY{qkx#B27++wQ$KlVF^vrsg++@<_HZ&aia-JQ~ z?%q>Xw#Y4pnG6~@ULJECJJ=zrl;;XH5ey@-@VCNxwxbAODR5IfY>)FdGi=KfV3SeE z)p%QnlNzPcs1Ii?K`(PQ`YQsX-!hG}Z?Xp7no(Z+yJ~X=)6JRqQtz;onnRb=yf$2s zTp=XP+YkZ@1}T?tA0ip zD943?tS^eD^YAGMSRlw*Wi)cdasnM@L1XZKUokWiZ#q4bykIOvP_8gcno%yXdx`$y zSQhF8Bb^CF_4Pb%=sL)*C2q7kZNf=!FjS#&W@pzl&bqZl}SoE1?4hO?3@V6>+5AemX|3>(&vw zQ?9plM##A^AGtsOv8*Cfh)pyJr^Y7Fe8Kg5g*eS4iC@At>Fgx9zWzXj^9`b*`V7SQ z-tmq1z`zu3`PzEj+S1Q9S=~cHhylUmOp*4&seUpwX{7wJ1?^Exk(Yt>RSwv2Y++dr zCEzY5`sF2xf2l{O9d+h$l6b3(0+Z|;ZaT5P-PgG%LUV7WP%Q}%Ch=_WO*7v(nEGa>QG=J@H*QOl8yldN{3e#+0sRZfuu1UgKq;{fD|0#2qcnfqd-*DM_^t*orS-K`*^h8w8-vQ=!WFL?_EkiyL3Gxv5F zQwV%Hf~GgVYjwtYyA}U>S{(c2^x?~E-Od+GE{B~6w3=#K{Nx{K$9HR^Z^(c_xvBNj zD>EbD%1Y8Rx%p3FL$Z0FHo~_53w8&v5b$+Szd^7Ggb18F2zVg~i<|~XOpBc9@-xR{ z^@?1n{c72ioLo#eX$U%B-DnE3aR5cw5)+i0@X$>qqkaHrnoI!-DH?nEbggIo!#Ml( ze{^So=iMTq;r1VDAy!{GAJUGTU8Y)O+;<4TgjG_n%i0{z_)bu??v!$^7$t8~uUqB| zU>qFVyrAbBpPo|XHx?w_T6IP@COV(AfT;PW?o|!b3?#p-C4Qt@-JvE;LnGzyLM5{2 z8`VF9@99gCQY&_j)tuY!Zl)@5UWo@C)XcW`ZJagLvLyBius;MyMUA5i~RAm z)(EA2D+4sU1(7m8bPSk5K1aHufE#+Hn6|D}o<7r{Ufu?k6{8Xn#$kSgQA#hQ&fCm9 z__)yV@}6Kne6ck3%4?>l`I;V=3U2zg#2Mw7vS|a9DZ{hqZzXcSO|Bh9p^x;pEHU8^ z2yTbqvs6p81QuwcR2kH6Q*shM{-teylAyT|Mxo3Vmo$PJ%?o;FhNA;86>8IYAIwKu z@}eQkuwIokIlTE&9+=fN*#z9MRLM{avkg+$BEEHcyWGh1&rFvXgXP%5iPrAk9UC-<{;FA_9T{1IP%V|W@ zkm0Y6b_kDM?LSV}r7ws`a6QDx5Q{La1O}`aM#fUxHH!6qWOUORuL4EZ$GeWt!?sBwHe6A7 z3L-EDI*2o9Ogswy5ql;6j&+-TO$<%ULE{nDsDqalMG7x+6 z6O-=gACMLE4LW#h@cR^Y&?AWWv7Auxu6XP7Xo|CFJN|9(T`P=(hXZOE=;nvk25Q?q zOH`{>PeQ&Fk|cQU*eC5_SL*P&vehLMhy8q}Jg+j8`qHdii+vftRYqrSRD2ao zQAVvmQNYQfYic=8O(!U}0jeo5OIe(nO7=*CVm+FFthVruJOSa<;-6{kF)PZkg-2M< z@A9(*2$tAmmfyfFM=3?54v33_uL&>)5e;zGlKT4fZI9Z4%E6XE z6}*SoUCYzL>g)(a4mnTkZ7b~C1gftEE%+f#zQTJV4){|QEryfD*nD}6HLg^*C<{N! zb?VFMShCq(m;CP0s~=}}BE26Sji(?lx93C%y}t05CdFAqNH`V#=|0^~h|izSo8cPo zH{I;?EoYuBmVI1FZsec!$ld#@$$47qFjh7OQpXD^Qn*;^%gy|f#u9C% zf5hYZWK_#(^(GJF0%SWXTEtnc&bbjcBx^8V+M_IyA_Pk0dzu!9J^;qhuc#%FwHN+V z6`>L$tLEWFExB`ve_}z5uwO_e-?(qRZiPEhZ~wP>$sdHL4qz8!t%7^pXUlf7ghe|N$<-m@#J?w< zvxdBA8J^uu*tKQst`3IlZnRF0i-~&SJk23#pdW?{xcwhp8#GTAH=FEB7gm}nxCXzH zF07F$eupAddtUts^-nxu&e2>ljp#l!Qaj9*p7W0`b1c;8S)}h;-uIO2rR)m+UE7a4 ze)vNxYrKw-z)bx3NB0}mTc1yuHj4M<@Z8tF3IJzMseg1jHE+d%@i>bb1kQnC0+0#t zq-ejkuk>V-R+fHP9QWowIuP}cIl=Qnf}o&^CPNKy-a*ZOOC|GSe)!cJ0(GG8<5qMa z)KY2XJujupXw_t0eL1-j(s1<=7T6(jeLV0D%t468`xLc&y3FSr%t)+9AnQ+l?2Ykp zVVrU9j>ckBu6;r}ZrPeojQNyl{OfS1$deZ!oK=?&w3+>FM~J0b$#>?mwNSx61Gzbo zHI=gGl;@Cd-k3hs3TpiHmgx=^`p5T1%Xj`m-ikKSY^~1wGH|~Al=2<7wDsAwXqNCc zHE&fXcZKbCVUd9N2??W>?S^DZb56URol#8h0N4O7@>vd*%r%buJWQ#bBT}*Cji|1# zV-~ZRGm(}tJO8J9GUhH&fDLCKc4QVcj|wl-t_4%vh5W#;0!SH*32kBhS=YY+mXMw zZkA{0C64%~2pr*OqMkxnK=Wo$91 zpN^}w{W*zGulk0${ZCob(G4fl7vGTR$LH0dBbj3d0jH*NFH+?kPm`NhBJ(HL59wRLIinU-nf1}@>ENiJHbgzl>H7D!Lqr4+sl@s#6#@CP@wU^P1CxA65!_60auA%eY5Mh;apYB`u zOk+Snys0Rqgy$9oMOfW?LaR|_93O+_Go){T$w|xn(U}o`p=&bdVD4WY&Ssvzzkmol zvu}|Y=^Jmb!#B3QAO{NV)zn_6S};KFimjt(K= z!gh6*lmEqxU8k*Z%QpCJgWpJj?~yX^s8-6UxNKZ{gXI*cIAm>}@_#MO9~)CVQ3$I= ziuPgef6MRKt`)y<|7pMwm|z^S79xYA|6>{`_1sd9k5XN-#=dZKZ78GWA(cpR&U-`1 zR{)+pxPyZb_jZ=Unu;(mty}X&rig@y1Vn_*1pUZtt26&CY)e&2c>He>By@eTf7^B6 z%^=~0K2zleqEK=PI)X1^dS%F5UEVEX&te*3Lx1~bP_IaxaLe^Ta`WlYHn@h#O%MG| zoT?@5YG~pY9=90PL;;_apXfjx38$_(VCS2h$rdzB$D{-Nv>g7ie@Sax><$2vv7Yt$ zioJw98G#xb+U{+@ol-qOMHg*-F0cgwMks(F#{TDK%w@=5|MIm`W$LA4gF-+9xTfaq zU%P-%Buf(JJPT0$1Ob|IEvDjhEykOW3IC$op)H%G1cZy#*cGQ%w;u*Z`+itLTV=9U z+E~GD>%(d>VGWk^O)!#(3-xoT<{Z|_hmfoA!tR!b&J2X45O7i;X6=+p&AFd)cl`{M zE)5Y@>C4HmyoXaoA&xtdc znyVU#XSOdOA-2`S;t|v-jZ7CV5lXx82=>UP`LU_v=5zRzaQNNlF$HhsxpV}C<^MGO z(FHdBvuOPn=erR^KzaS~r_nA(8l9@2N|k#btaf|<_YFXbD`GQ$Sd_Gm;&(d35f7rX zRj^ZYsT3~jGaP>vKmD|b;7CY98toxEstT*d+4TPke6rlt7jaE@A|Oy1#^aUdrh)oz zkz$`2OM>u&iAXv6AhzFpp-cfLku}>NUTg}(;`bIDdrjVy^ib<-G8!Dk&azXjz~$l( z(jk{MBXNZ}VL{#9NDr2R!a&2{)BHAKVscNV$l|A0Wu`t>;%Lq4l%K?L8~-opuUkNHm}}+#JCyl~``v`dbKe5^`eu7|YX0e-2z_buo~IHQ zPX&jIWkh>EDbhCj^RpZMTohFV)hdrb;qWHCupzrsIz>kkqET}^lLbLF+#F_@?Xl4;IqZ?u{s@p*!>mo83>LF<}gW?k2=>-Si6!ik>H$F;f6u5FrLg4V

        l-d|*Z;kJV{m1oAl;^*YhX7i0NYLS2aY1Uq_LZ!Y90RpVK-;V-il-NIluJmp6)OB3d&~xrXeVm!a zsOB_Wz1dtIs*l{osKzKSRnv)56_gZ5UN0a$OG*y0`{UWLJQ1(om7?j(vxBh08tIet zSynuQyO8-gCsyYWJNV#LqqKt4$b8nQa3fcxN4`z0TiV+LQ|8T1i&Y_NA+w(%CLbXm zglevm)}lePq41AaLg^#IOZ*IL?+gC+=w*morKO)$F=2{CDAWJtKi@-^ICnc-+mYL( zbTD1#Ikpj&bV^mV^j#lf-Az&bl{95PcUtYgxHxI8s%-`KG_$qCi${*P3fGo7U&7=R zT%B!(Z${Z)xd>UlPttm#rY(2nhLZ-&%q(cKL#l_XmLIA8$nG91+My{L*72%G4O*kB zW}nP`m|u=rEkg6~?u-Kvw&nt-9oDlBdfW2l7yrcex|6pRIg~Hd5+PUg9NY8&IHE1_ zpA#JQ(G*+)QQ#Lwzw9D4g~al>W`s~`vty5IgKg0tP-Jck8spKcsFFh2PKhdz6S%Jh z@RV5_TGtkE54IFg^LAKg@`4FvP=8Z_ZK}7;fN11c*5dy)SrF^x{|B|bn+i$|{TJ=9 z^L5xaK`FqLq!(I?_}BT-E>*8KhP64iR9%c|lz$wMkEZ_SWNms>&lgoY+jymCDQLla zIpd(}!4Kz+oQtx9KNz7Ub8)H%de0|tM|LA7HGM@yZSKqyzpA|V+D+vG2)BaTa=n%- z2~Hvbz{O{-@anPMoBp2Pu=&W`nlf4P^1MI!820e&vsmU`ckbJ5?^7I> zoQWswA|QN6zn)y!(R+ZDL%Z#p-$_{BibIBQUf2jK?TxHk3?`RvO_`S`{AaJ0PapfI z!Bp+HNCF{yaNl~4Qh9=_^?iIr8r3S4hG57bMe3K{%)DTZxXZU~rd)Kgn|@N;w(8dA zLyN&o9h>FED4&gTb^D-zjgEoko@Z3V_)VoHR85sO_mx4T(&rZ3VOOa(>COv;jnsRn zYj&AVC(7RLS_dMbj9<3Q4kOI+gs|#fySnF;b~M54RX2C?KRREAgcYgG?B&|irur5~ zPPR}c&5_YDr*{Xng8as#1hH&N8@Dv}UGpx0q_(tqOS<(*XVja0ip{&DoRJ%aeSgw_ zxh;AwYr!7Y1#XnAQa^EB>cE#1el_#URj`YI_Ycbr5|)fVxe_EO~_NJki~D%@fJP>u^n~Uu03gx94ely z{9mmgsc7rW0F_*cq;VCIfsc;dW1HYtz$L**+BhkplENw73VBW5q8;F3e6Smuc~-v& zBy`l7G|d65ZsBoym0^G?3sSUG=+_3AP8sB1pohI^azn}?a~rDS^| zUG*ZEQFN9;xVte{qR1Ema!fCem=EhAPMQ)T0z`fNN}(FelhA5)3aX~Z{Tk!sD2K(g zl~;p>r+7d!`U%9zf6d8z1V6wF9pjC^b{LPq$&s!-D)yVB1!y#coZ*tcY!D}w#9G_J z#3&q)|N$U+=j<L?4Y%adf5LSi9XJy5M^EE@6r=v%i@7L9^F^decwAd>`;xGl~{4HqLJdY+-K7;SR; z=mX#ZA|0;9)#mC|GT!dm?jc-%a}9+C6X7VkPWBa_5ZA{)>ssA7Cc zI@Rez=IoHWcC%e7S~w*dBTmv?dg?M(LST$Nx!%?ybZz+%|cwBhKJ?3K4abO z7bgxDvF~KeFnq}5{1uJHeYv#mHbaQgN%I_opXf@!)qg(Ih7FrQh-H=Gk7RoQBV6Yq z@H|1v=XOVozh|F<#`=oXcqD$uJt}cGO=UPi6vncMt7b{ed9hw4J?K-v6v^gIH+(Nb z>hfQO8{pb2jh{l_IRBR3YwOjyeKF5o)q7CsCrc&OPY)|yEPIg(UR@P6?ni{QZjxwb z>ZB*>B?vi}v9fFU&JeUo!Rev2k#~uIx;l#BT`4I1wBcr@GL5(8;lIm3gJ=s{YEsA9Ehg>H; zzE+jLHPt0gSzqwBw=;QWEFYe*<5fB+*b~wAd))6jo8|rSlnQK+b7@}>mH%>KLV~WU zh%wA&!bOUnjaq2KPjiof z;|-62}YE@*KZ~2oR%{$~8Z?}S~sm#!)kI~FN6d#FZ z)m)!_54zE-!P@&4W6Q}QZ+`8srLBp7E)kXe@JeJmt{D5bN>~bS`&Y(HZrkDu@r;|x zq^0tipk-xIX9$NfNC-HSf;sC#W76;WT$f$`U?^G@o=_2{M~X05uQ7l5hcT^+D=)`$ zi$90a&7f1ukFil=RLmd-_x;}UeVnETJ$Kr7JIALQPdAs7lvOQ6tt6Xo&ckChUa;&s zr4HG4UlED?HA(ZA*fq%f-q!%t)BDEUYdae#UewCWstP^9gk02LgIV7FEH=0cMYYr> zb7|lb#&%r7!ta8hrot_zf&B$jfPX3h7Duj=eRfc<)+_mRau`X8OL zt69nWB4~>HKBu*=7C9e_oUea>DB1gQm8~RwBF68;Id^&)XG_$0>E@@RlM%gKZ?(lk#g`3U8fqB!B8e(Zd)MU*%#;P8M<_D-1t`Pk0&C4SP@> z@8r41XL~Me*!xL&;h)>LRPngcJ#Rh}-%p9i4vvur1@|;Xu3S1by7fyb<>2a~t&zJp$ z$80{wq<^rmIp;NVX$7VrDn=<^YWWSzs;%Ae8a_V|wz0`l&45#Lx^0nOhGWUn)`>Yp z!iRn?Ri;tLjg#bb-`5*eEEmcH-6|3y zVs4x6C-kKV$H0)~YVm-L$lF;xV>}mZ052{GAA>p*{zUpJU2hXD+4C8NGhO!Lf}Yxb zS*kO`3{`M5AwbwEH7$|;r?O|v^&%$U*q6bJIB+Kqz4KW2jiabzSB!g}jN{->-IhA} zC|Do+4`u}j!xI(7jiIFlmTg5)&MZE_I5Hornb$LOvn|LO_FwlI;N*2~KJ~ZC2WB#9GPMyUj zpDwF28V~23Cjp^b0C|-(0Ff`P0Pn%60pLkbf1r;08|H=H1CDhN=OI_*8%+qCjaudd ztyn)UYKkT7P#Ly;pSG{>)U}B9Y$Hg3k~|Z#=5QBj`8O!B^-OlE25 z0-iJqc*)=|y5qX0*;SgkpT$YKA@4aPzg;%0d zK9UUPq>`I!Olk#r)Lr%5FToSpE=1X}JIP*Za?wcrjPs1YZ_T3~jQ7Gf^F8AI=wkf5 z(q{ zYLrmWc*(0`gX!EqEk&Q(wkE-hRn2p#Rl?^Vu~hX3;9PAF$}a}h^=GX>4TS4bn9PQ! z5+ro>Z~qifkCf0lDXX~l@$)Zj4zds`WmBM00n|2|S`lft&D-V7qg9`Me-5AuSf5@XvTYIBiVT>aomh*OeJr*0 zdJi7NvI#F2-F_+{<+&I29V!ow3;%9i{|S`TNkCl}r7P ztuD&d-J0YGYmh(JX}$;%e5uZ+ef80owVhB~V}%$CK4Or>Y~k9qC+6MHNS=Iv)RdaV z%lenVYsKbd@u-ygTF|{T!Q-NL#0*_?*^1p4dJo;%?(8kIxF{a}C3hGhN1w5n!KH6+ z3YFRDdR~LuW898TGf9)GVfE}p5^E}t<-$JZqTF8XIWF8stH)^mVx^@TzW^R>GLJNa z$aLxzN@E^>h+U+Adr{s8u8ld!aIR2s9QkPtaxdjPAIUxa?`*Ahgs{MA4*HK7H5u_! z&yxgV4(p145-(uXV-de$GiLD7){zs4Q(Wa1&$ApJw=Ap;GN_GP*y?LBq+TW5WKjF+ z&3j7dLrOfy!sp$Sy5()1df3v82{heaA&SU;d!;QCZzLUIF%>!eLO=M_*D$`v3C2Yg zi_FWv+!o+0CV6EJk*jjM=08)ETaKZKd$C~Qu=+P!nfv6u6AjT3(aZt6byYh~S zWFmLWOTbh={TcKPgiqFA!d#Pl)F20B^HTeduH~PotupK39}Ui=W+07;75Q?$Q&?N9 z*8nvKYYf-P6ZZ)k@ePKoiKW4(lq&8;{AiKlG%wf8lFM5XIGf3sY^E51HP&d-%j>vV zRAD1SHo%4EktM%NPH9{cwBs;CzXMEib&luX^(My>H}szdtWn~LCKAtl+QiVqPG$i( zgS4AX{AZs`7Z?e|QzYK=UxIK%zhPFLl=I^u+zGhr6F%W0T#v@;UoNf6e6`AJz7SF~ z!6|fY%9f{!-=2 z`=BZ}SEJ+mZZENd0Z~gO~oDrG+uECqC?ZiT{D$WDDK& zi?KAmw5Fz4EcOyc{<99UZ9oE)LF|qiF##|*CrsIYM?6A z!zhBlhHe?1QN%ci6;V_?2{4LWNb+aT{nEY)zWH@2{rfNA(mMOjIy2^|8isttEvOAf z<7+`2Z_i0~^efSqCnT7Ril4?L=GaP0-H=kuzl7jiA5_0P&2%XaVas9EG$$=XFty&I z1++BayHYNO%&A1u&8$J#|X#V^Dd_p51bp)=J@@;IxTI(g6FcDJNy z(i=p9(YbY}lXba|^mOOplxv=1yQV?y@sfEQp6RA*+lyA)u5H`pIdAPduyVJpry7IJ zRB);6mjxe7@v8fa3*O@C(KTWyCMt1k+7BdBZBUDgD5i>*v_X}5yVWVaRs4`_jX07(l50^F z3YcA}NK*R`--9lOwZB}wfM+48d|=KQHWvn0*8r&`$M`6S(a;U!&Ipw+PA0Rhg9lO< zyJ~RCrV2LG)EUnZ={=pjFV&XvT(nn`*@xcAuspO2|EYzYuz+cT;(qx<3NiT5&dy`s z!{E?r&T;={Gzbr3#AhsZ>E~+Mw0KpgO0(vr?iju-E7=d9Um95U9c5p&I>oMV^E^Ri zd6(busTUP$*t>qE!KFr){S{{2jwQ~b#OqhJp^y<9YQ*@w%dZY8Qqa|xcqIRcYoN&| zQj(_qA%M7u)K|N(RV|8S8$=QTc|auvdfAzHCSf-?_sHfst$GL`5wew-BS#G+H0U(d zG#8m9{*O+kYxe<4o8iJir{Zoz!+h+(>b)$vRQSWUGY#1XfI)iC?h;Ga68C4+o~;T6 z-Y4L`YMj0GDmrC^lQ`wD5i`6Au7F|eH_XJx3gr{ZGM*9Eg67!doI2frm%lF=t|IfS zG`aQhlcIKhB*lW}KA7dHJLD2kSM%cc#V?DDWTBLI9DlG1DeG5FCoi)6rnKXv@Yp+O zhO(?Uy2l~?MoM9@q~b@@3jP$Vb*9b{a>F_Ge^wE8xRzr5M<&?M1NdK_U3;iSAg*)A z(-z5`0XV%jgF%nDjO-95+qk?bG+)=wJaERS(p?~em_;qP#vO+e`p5OBsZ?M{Km2?E zB)J8&4yM0NC0F8;ORINm@n|@EOcr;eMP8!k8Bg4qvtyOaRtW4vjY|ES&FtB|L-UDO zGHVuav&fM9^2`6VD?DO%4Ks^SoJX|w4Vm_Ne6sH~rBqd+nckOf{c3GP`i3?!KV*_R z>&)=}x{-FGjo=v>MpKJy9IuV)_=`9f%}U%JEVx_4^lGYnj6FLKzRI;dWJf-Ve%DS* zKd`^%xz&@|I!8DuVvLRHzC6NE0qOi*ZV>#hJAgHt<~K;tS$=Cu|IB7?ax%cz#;4+`4Zk@ZB=70)CnL3Bo`; zb0@@+i60$kh}KH4VGB1>*OB4yWgTfar?#Bw=?rO9K5KTTraWyaCR&p7<)!Wqy_vZq zz7*@!55(=X#$R?Nk$8S7^I6VcN06-3RVtNv#!<1O1JwJxuOa=CW!fGIH6H22!7rFv zy>jN%2T%Hl)}rL3=lR62lN{e~>gQyH+da=;8~zgT?x3nCx7akqmyyX=cfj&aWln|d z{o>N7zgKs3KnW#s%Nq#OV~~Qb1CPf&(0IVLuv0q<)Ymo86PS9ahN23B1cwY^$}XbX zkHK}|coNf#^~cRRM;mWJ{u*=vK`R165CK#fw2rxmU3ffJ8EB7&3Z2m8Qcyecb z<0*x_=4-yXXo;p?e-`L75%AVgMK$*~zj>uy^J<)Ve_g6Bkl!h_?G+|0ErA*#BE~pf?rF6Tz~JOB`d3^pbd-8D}Ofj9dmco#{(JJQ_ieStf$L9Sk6i*1qb#? zUgbb%mL$oGJCih5xD=S#$U}o1$yIH9=21<3FE9o4JGkpX-o~sAIjo>!_c_a;%QA9N z^D67U`4Fo(M93XcmXPPZvh1@&^Sk+zpm#+?im*`QkJ%XJHoLDpU(9f4e13f<>omz$ zw_?RB?~O|>lkfA4%mwX022a2cq3+s$Wxjx?kyXxybz@1?8;r2xIYnCTPM5qRyHV=x zdp_O{!g9@pm&-B(jlUwtT1qbrt~2XX!>_zIarUS0e*1unJn>4Y{@0U^vDU0G@6}lQ z%gon-NAoo69R6Dusd3K`kjFj;ot*o;XYl5^a`PjrfN1Dg8G;i1<9|O>DT1!+_1qdfVVbWjd@>M|dMA8_ zEI&%~%EkoVpNIFxxh^IlCh-izKa&kl*D6PhYdBHNhF`M{*`JI&!_@fmzQ&kk-KVqF z?}BjOEdLZ^3F|`MkTa7*pXL*KOH^!C{!8$Unj|+Ax?EH4*skG$_n|$(^ko7`((8}r z140ZOElk}hf{0y9w#lOt`1lj- z8#FXMnjz^8st-xdFEE^-k7GNdrcW|}T69qiQEV&+^$+sr1YXvR`tHkF$|*_mg}k3{ z7<64?tR&dfh!6o6$;RqRC)on$>~8={mV|GHUrRik;QMY?g+oxm#N+?C}wFg`%hvwYznyC@3@g z4_c-PvLZP-wrbV3poU+}r9VG&Sqe^<0Ru*yQMpFWrv6+iTLb(Ragdja8U!wmytXSZ z`(jb+UUQ`1e1j6^7baB9b+1vyQU^{s!}liI#o>GHAK&oTMR#}76EfCvFL$n!8!r{# zs2Ya;5Yo`8t_t%GZ+PDkG}-2ZglDt6EOmZeAe?a16SJ2Z^88bN`)lN80!<*VWm=Ue z<2UQPedD#`=h*ZwZ=%y*|Lwwf#_rYJYu=iCFMso%wM3Z2jh1-9mv|o3?&mmOahgxw zoz5yPwe1gSys9=`N5YPjnP@^-)b~504OMcwQJy`HQa@E}qs4P-?U&B0z9Pt1VnLHU z1p^0NHneav5Oop4!F3O?hEe#>h@e(Lt9XTR*Ia~?AMkYUA2(>mqgLcA!9&m}6@C77 zsm#OHA0P$GjHEH37WW;k53!H2w*vT2H{v+hekS4fon#)?0Fmed6Qwq~_NUzZuL4a% zd7Th!!>?zMYJ-*eR48hh6Om?MrokKOo4Fepn4?&wNhXkKN;+h?-D}r z3~fccZ?8!~(BtgI^*I2b_Jrea8B}*Z?@75;eBbBT_C~D1mH2WyKgv(WNk<>o`>pz& zL!KLbeY%fJlBs0p?XE%*6=#M27`i!%)JF%J1kQqN@Sm;sI9N|5rXI3 zHqx0}3z9ERVY%c4oead=p5oIaBAsKBFNt)`v1VSl=04R(x{1}3f+!R(*4gq|PNzbA z+cRxT!u+ZGkG|ee)GaPe(cd!Sx_&L2pa62zN>-yVmzo?H8n!Ca4JDZiqDeUrAR2*W&-jT{%kpqT@%1C>(nOgNQ=x0ANY@(LYCZm+$N%@^m9{<4p6}=T{@lk`MqXf~1zwE` z7%(*5Qj5~@{JCEdkn}Nm+on`LyW(wz-IkhV6id@xxX}tJ#~L|~H+y&5(vLSdzrQV^ zFLAhdO4+OGIp_sq>$$0``t6hYT6>rSxYh8z>cX{$kKxuoJ*g$fZ`Awd?7#8T-Au~*Z7R!{4K-)( zm#Fz(sQ5PE|K#?#@Hq2fsT`-n@)>(4X@Acizxyr)3oK*abgrzuwJhfeLe+Ttq(y#e zt^Q`S?_%Pi%e==824*$)q)xqdzH`e;zej;i?>eXb!wJ<%rz1Gf!f_E!7^7Lx@N^G2HC;X z`yUDEq{L;JV&AHax9Ux;9BDy%q=Hwzz)E0i(Rl3J{{20xg0E{YZ{3rX^j~<^wHTAz z3SQU4tD@T|%hdg@{Xz*}*E^%$Z3J`!R9v1;;ul0MY_?sI$lfn|)~3Rd==i(S00*bY zkG@Bt8!iKRjz0H7hb7!ueTE2h_r1e`n4R8*nPM(WJ0NYru#*O7FQZN)+WFji0n*kr$-tM@4XX^rgy(k`_r=K0~bRhKQD+mZDO-ysjNp zTbMg*t zF1kVMmSf0NgSUh6?xU*}cany28w-M83A}gO?ir#4DDjpDU3OAdh$w12cV_ox_PI|n zP31A~#^cgb^iQs~e{H9Hy177C^ZV&N5}Qx1i=>QXZ*+y)HMqOnFes0T-;?dLCUWe? z;QR}Bb8Nkg3lEY~yrbqHT6j{;bbzyBzTrNp)lY5pU6wsaDCW1{&!&d9D%NJ>%hB?MEQBQ{H|x(OJ|(- zK4n$?iRf#PN$TF>AHArfiW|TCsEk@`lvSG?gR?+Z>)*DN!y%vGY@VAB46hVQQBLk( z$sd-GkzR%vl=O2Js)K;}#)Q0;krx_tg)l0Me8q-}&NRC)(*kvWks$I`YEjuu>wscK z6%Ul`1byDSwoMzUbLaNtZbt7ofMcq+i!rrF11fVWy*!OwkNU&(iE@dihe*rCAr4jl zkUIoL^AP&A36cMItqx5yN0V(6l6lg>D&<;1eBc(XqsA)x9gWzBzhowa6_A(s@2jgS ztTM>ztDAKSJ8uf#cd_N@zkUoYE6ok_Pl?NVF5 zL~k{Iy-THR`2wQa-u=(JPn9~y()pQ#mB(VPJ&LK?z4M~{BI79L$GuMitYMaBn}ff_ z17`!{3vJbRh7w-1(tA1$DwRDm2Q{{wH1^>S{g=_8l-8J|L0r40sYs5ktSgt(VdJsG zPNU$P3(dyTv5{_gi^^h6_mB?{cVB zM5!}4wl90f+tc>mnx?TaQ62wn$Crg`mHNi(I8By0E{uBqrsRcLS%2yZ_K`u`*X0%? zq7c~=3oiIBe)OY)$go_;~xAQ;K1*Rq;Xdttqbk0sC#^Q@=-#jIpAgbNLnF z64|2+_Gv>8M;Y0)HS*+|wNkn)O6$dl%z;V$s33uB?l~vl)jE`P9k$!bih^b)Z}FxJ zjm&Qae(P+f%za9#qS4|-rkifjpI^~B<}J#6f@)Y1*z8TyR?R7QHGV5-sz;^>$FH*# z=UW}biSyb2B)&V{*HF;yLHUi!Y*IUYBQ2x1DEYNYfDEHQ$1VQnM_1|biBsmSB9~Rq zzk)5y3zM6&%SaN~-wJKLQj~GU)$myS$kh=`sjKWL~K*2T>f${ZfoH5N3H4r2^fS_SCry2rJ z*i=IJQ9M3Vm_?cXIE$34ZIoK(+B92k!A3o%) zdVJLJ-S6VgDpDA%*gwF)LS~6y{V0~Nu+!LgDgY|S~Fym!bL(s+t zmSg7}!X$d@)lVwmxs4ZBmQ?;T8_;Bz{0vT7`t(oi(~Ny5SYt+W@0nA7=2gFsYa@)A zCTirx6*Oo?tF4G%Wv{%o)n>WJg@%^#n71O|@KmGd|#U$tyH4T7uvY4e&I%UrY8A5V7HD$xl@R&Nbl3Rt42% z-A|kBEyuphLPeoUvTA>4Q)F3 z<9ITU=^_4`ZEOYovWP1INtYafIhZUV2*8^l_c$ZdVM8r&{x~A}=R7u`Rvaif3)r8A zzMmIqSesveMz+~LbG?Ylk-P9(=+j0o{InfEEkJ)5WeE$*@xgWW;I62{1*C_c&MWLj zP3)@elE``g;WJa%PFkemU0o3syN?`WkERZH49%+0;`~F4RgHBnu=G}xM0Q+8^rrMX zC0!>xILl14q@ni}lGC5;gWeVyZhGyo_h0*)JVBWKx`Ol1x=Q{fm()0s+PtBQrt~um zmh!zzZB1ITxbAH=wx3LwJc`6jblDT8P%NtDI?P`4V@_nS`&fMKG)@nCK zW^;}B&+d=b+19(h8T`h1?`Fg{Jo~myfs%Ao@zlq>+iV|7g8qXNk-shHgl2v6p;hLq zl8>Au@IPziOSyJP%uyBbqrK%S0$Hlc6ydw5*}-SiE3dM>Z7(!K`IEZ5+e_rSH_IKc zCCfR#dkAJDWbRa)mz);$6e*479IU~4f4V5D6o}eD#ymdyBim%%CfNIHaQZN+BA||> z!+|iRuu%|2?4>i%_1MwwqT0XKNB9$nJ|2MG3HN}FbWRjqx4bp#0{dq@M{@=J_H|xt z%+a+$R?E38GAaRvni1*JQSn1bWTxTD$cO3z%QRn7m*=G~+;E&XL+z_@Iqocf%ugx9$ zJkxc&(+@_gm40ed4zl=_Nb?RESXX17O{kNii(^WahHWDQ6LJ$z* z-Z}1!j|6Tgd}`b#uzT*#g?l3t#fqAinL})s!ZD2R6`Wx+84;J-s$4iQ~;$@B!5;1?-)>{fA+sUy?m zHPLtM1T!s?n8%gSN^Jdm$X!q1kwvZSQ+_QNAQmbV+OY%NI^l^fRhqhMQFrXoN}LD_ z(8vncjN0nE;l9Q^;~l`+eP8QbfwK=1hPMrX^O!i>O}7?+GC@f95q5FwL+owMx1dy1!?Hvf2*+9qOj&dp(?= zwy5o)aUFJ_q^wMrEYH96#dg;ht;4>ho}Se^!ND;g$KL5;+FaUOuO0zwG)$G(JnWh5 zIhE-dgKJNq>zd=qUqJYg2I=X9ukUW*KIRlFkW)Q4#(#{VbguCsBOkDzo`?MUiogF|C?P5a8+@pEl6+orp37=Q;{=^_>0aJb~?D zlfAK~$D&4Ij=ndO2V4y+oLUL+11q0eF2YIxR{XLH7R$KmO-n((`93laffDjci zq4u={O2QR@#Up!=VHI=(BJuOgK}4VN8lsY}-2bS}il;_m0|}E&uuD)TL8~FdvV14z z+|Kl;`3};aL@;J~(DgQH26BP-vJP$e^OX)@sxco;NDqB_Y#H+HO!PCXT*>Oc)$* zXdBh3dCt82d&5M{tj_6PZz>3&?KeER$U5|3QJZOog4Q`t``-yu*hl3FGh4t@K{78dxfm%=B^) zV94|UOdb9N7B9FxX{q>76ofz!7Omn+5Q)@Xh0WAedPwi_bz~*3(>5A z1K5=GNy2o;T(7uEqcG~83Opd(H4aPG{R`l9;6H)!KwscREL{E!0xW?Nn!p-1mA%@M z$`ENQB{jqGIh77?KPef@7ScWa2k&~)t>sP6bqdXln;t4Q+eW>9`eXh_!k99$4K= zSJEel76^%#VsGHDjsF}>Y>}*L8>$R%$ZL6TE`3Yt5dS*TEEi)Q^3(IwuY0bOpV6sO3XC6WBZ{sX9>AQov(WfAoP0`Poy$68XiYoQa zrL_X`Z$sLV{sWu3>VYMj1~9owdUXjtsyZQ=hGFAKA7)a(=0$4){i8DN)nR(v0qi7$ zCnQ`o{}FYy^yP@cCKkF_LWD6OMR0fZQC@bcpBXe8qjks&FnOh2O6hw14Bnr4#l)_g zm92%lbbZ&k2#ov}_pClj%5Zw%^rF{6P8YPlgSMR7uvx2(iDts4kOt|0&S+bQ5Ebp6 zUME=8`R|H*e(~AlyJyPt*622SR#qDuW}m|4{`TmI7H!eq_$j6Taub z3G{B(1SomS_L!|%tAftQrJ1W4x5CKLG;5LP;LS|Pv#9(A{_m{pmHda(;wmaFZ`a8G zM?+59Jyxq;(~f86S7H;vpxh#;U2zsTjN!6ck82)@E8ay6M1Ep&F@^?n0mDIZK+y#x z;S>W1s7gpAjKJ95MbS>M7H>fMfrX1bW(m_e&O#}~o1DkR zC!y$a1wk=BvI|CAF+}Yg8tgO3!1%pjP`?8#^h!SC&C?VHG+DpN`@5cgAnN=}6qL0S z3%+viJ#`Uf`5bSP{i%3x!MzDl)MOjsjN^Ez^tK`6q|>z(uJkM9>mlz^#x&?=SbzV1 zxOYB!sT>BHi@-|i83pE-RZU>v&Db=;Z+{TKExnRqFf*|5ObpmV1_00HT_DcEGX}|U z9-yG3BJ7nABnG_89*MIGS}yR`R;;pVDNCse7hH1l(`L|wFvmuS-G>LZFKpt%OK>sV zppUK}Yuiw4vTN88c8npkmU!ODo`0|=r zdjQQ{v9ll-EYrA93(b~^;xJ|js5MJG4%dNDo&vnCq#gYw)DByrM$SCnUSSxU#$=||3aX0eqlR!!;&y@Va z22s%MJow!xrmhHSvwYEiig$olVIM5gH17^d^R-sMORv#i?0}0ZyqoRj&^tp6NClC! zvFPw=u*u9QjVHsiOH%_6+Er4hJH_oxk-P10qQ&J8!78E8EX|AJ)MD$4O?l?ZSjJR& zWhj+31wnHt$4C@a!f4Kt0#Mj5aqI0Q?b;5M4Sw>I*+F*Stun)tT`To#9!alsBBuS+ zJoH{g_oJ=w(3tnYfi7kBauR2#T$1(>nrF8RS zRzlJd4#{*?^q2+k#R2i4sAv*Y%x%K-kOUF0RHA?@tb(qAIN=?5=MiCaLV}6WtL9+7 zY2QS|>mg7K_CMHKgnst)aWeQ>0sZTVtJi& zgH-b2IA^FP!}4EJNs&4;?CqzzcbpcpUGbJW4&<)OrQVj92S}` z4+TGfO6qg)TM5f zB2Z5V&^`usrf~yybMa!Nr`Q9wth2JZzAKwF$PhWj8^cxUN)kDp-IwcZ#$&8}&%~=Yl)Y=k|G>3=Bi*bx`0dm4?XG44 zzSGFFvB9Ife~oXHttsX+bJtRHm-!l5G{orx;0$?58{e|{kk`W*v)=maTRv@eNBGQL&cFO-HF#uE%=nFsOfi!vWiKCj(H-Wr@C?|x%5n%_&SRIKn zVVW?i2q`UyCOEM4lV(74eSm#q-x@gs-V>9Qb|lhK0bd=n|E+5!3&jRJ4O(LnrFsMm zhZR_L9&nEQ>a?@hd7e$*`LQz3FH&SKyPUUnzU4c9Q?H%6Pcf8yKScrjJoRZa<=E09 z>Dwo5HHAmgj7M;+{+OfN?rLb}xw|Zx^P#IcF81AGPT$|P;y-_5|M(Y4+duClF_Q*1 zkdMWTR+)XUNO8NqN`M=@Tb2Ih?JW87!=pN!(Q=MT9485sx%1sWq|-G$^!}qbYYUy=5c@t?;wWn>6kToQo3GjIkb2%Cm1civ{lE!|-;2cd83O#)K?*tx?D@3tTIhql9wdHyssp zI}}$>Dxn7ukuu>Y98yVo;|#bapr8{OiipHRMaa)^uY*JdcydiJJ_*LUTBr!{ISNd9 z@)zI#(r?%$)1KsRVVi+oyWR44pjHQ}Ge_G^M8<)6Kn;4;F44`o-Q%WhFYqD`RugWS<4Bp8$(Oz1`lLcrf>zcU}M>>;BeE_4{FCCM_)2?Ebj zLOJmC0-+an86?A)8O(0&? zDRSJ;_0qe+Tj>bxsq|NB)l%258DwPL&8k5 z#ayS5`Y%z9YlY!N78t`X;H|{s14QsF27k$iVnHA4SxxGi$P|+j)nUo992fzpdb7Z# zvIc+uN5Xqem-bvwcB@X2&c$_kbkk);bssGp9P*}#aPH)LE6oR;4=iB9Fg459b9(rH%WOIWYCXlHuic8m4Nbl%&J=I3pqk9Y2Sw7c+9pZ7MuJa~SWs)Uq ziebpJbot~rDwB(t>S@4w&(VuU=&wVZagC_4@tUxA6sFS~_A%Kz4u>+@HEu+vtNT9h zex?Y}o9zJ5`Td!vUoHZ1iD|Dm7W_kO-z%9087m?P-$y_k(L@MsCF@{X_Q60lP8amk zFy=1+02_Pf#Gq;Wf&TMBEZ8H%PRLg{QKk#TF)~^WLu&QOFz1zpDmDx4ac2_6_<$Vy zU1Mo>E&>;Z7H~Zjl*8a(6(KOeLhIRkJ&5Gi1Tq<1ryO)xwN=abPT+nQ2M_5DfzG7w z)#__oWTYzU@@@j~ZAnyRV{;guUA#oKO<;MdBC087r?sx7nzoUl^r7ry9h*H{HxNs- zg+v~G$7lU2%6Ik$oi&$=3o8!8?AQf)Y5B3ydnUV$d>f?Is0Ft4UI~pgrOK5o&+b0a zP;${qwTwN;l9$R&R6b`ctl-@PO{!1|&)e8KRsY3V30* zIUi%5L%Q-MTnPK@;?SYYCJ+un#$hw7RDJ@`3@U+DS7K#3{UM1okO&R1awP)TxF8E8 zSK#kXAR$lyxU>&P)B)a2uK62w;1h66N=_WmPH~eqKyE3@b9kW96o(Foea;_R@Iv|)rA zuZgX5jVj*ll;K=ss{6fYS)+UAfr7zc&NG`8+Peq5iPPJp*~&``jHsphe(qXU#tBAy zsusk=T{Ks7TF=`u-W}5`8U%j(=<-r?_jyV^xZ10n(KTkPkFd8==ez2`h$(pDOvcZ3 zolQd!=`s80TCu#Yvse7mqq~xA2=%s}oWC6h{d~@dzz5U&`sg~~@O&s58peEg(Pmhl z5VThtWK8Rz^GVDMf#t?}0^esK?`fRz*MNBVGXWx%0DClKOpw_AnHLQ4*{{e4IuDTC z2LM%r65Rk#DqMPPIsW9OsyS_{ZYO}AGgz$!!${k66A9>2*eAGcp^F1$jm5hK_qX%> z>v{u47qaz+af9(yn>;mhZv^yT)?V@L__8Bq?eS-2YR(QldzboHb=u5vdz*aW9lMv) zHl~?!=lmi{V|e`}>gk_Zw*^X^0JE^l2xF_YJ`yuqjw5lYgLU;`>7&Yx*5WUn<7FUI zZoTi7K!2*;#`_d?tSTu#Q%0rUu1UNH5( z^|_tU2R#Lh+X~8^H48Mk%2I+7djyU80Ay@bfj)1U?qPHvkd7f<9g6x$!pa^Qa_9V> zk0dC*NzEPCW=t!TL!|Zxnh%0f1=tq?1})$jr10<2c-j<7dZ1JR+g;XZKK^-on0>YS$GwVVZsFYXHW~30l?_fZ0#?Yxz+CU$w%58}=B#)R2FX)n z9PL=5R3p<^;5vU8T_~UV%7kp{V3x995qG~_asNB1L#k~eupr7kgo)y~xoIENz|#$9 zyi}XUOPV%gef_`{4Ig%+xvrEvss42&Wud-tAJU??sF}S!%Inlt%8^U13;DTM=nC}+ z55zo%kwrkkNLPU=gglW~iQ+_w3KojCh7}@T8NWd7U=HVG5Nmp9kTIhcRmUCxn^k$M z(_%ImLU{n5gU}xjpQa%q_@SzU2w~hcK$6Wd<4Q&zfwYZd%`)Hw;!x0ST#?XA#~He7 zbn9JL0n*oY6|8eYM%x<@37EbhLQ5`Qf(X_e=$S@g7>@I4(7LH?Fn*`;a_>9#TWaK` z_PTTyk37I%eK9rmQDfEAlT?P@Zl{m0z2`^Jepsmu>zP|t_En{_z;s!61$@R+z(N-w{IJpq^wedW zHYFc)SB2rY)}VB=z?*Ut-eWU{#a9KsLS$P3IbkGCJ?FOD;qE&-hYA+jI-Dp& z3zYs*YJN>|w}!lfsn*v=o+VnF2vc4lpH)!BG{S%x3;eKaV3Y|ICFr~=MAwbz`t&5h?p@)`YNI8wIS{b4$mu#k@+oYi^=f(iXMZ0AVQZ#IkUr~V$ z5Hx1N$<3QV6zfA2>NdzmY?$pE!uVe=6bt~7K`jt35eqbZ{((*);I<6IWddkHk|_HC zkB@NZ+&LZOH)*Vkn&vTsJg322jcA4a9q3WXFDSe0ECjJS>QV@DcTz<_=wJLWnsUr+ zr&|@R*y93rqbYE$`e7H&qlG+tNn5v6OuEt*o)TAb-RT1Up_X>cDtnFMFk6nY9d*D? zVC9*vsXcIHXJi+YTPc#*GV@c1Aj&$FU3z+p>-W4nAgMNIHM?G~+S1v0O78i2kGY*L z-W5%6=Os{CMn`N@8&kT=B?FplZD6F>4qkX{tar$6&Un?Id0&v{F#cm77|Dw)Z?x&B zqYo)*IMoJUN|@%=y->0SE4mGa@-NTo7R+n829efeAi&FE)H|?6Le($AQvaAS8QqHs zuV?TiyNmIGFk*){@*IMPf97@nna5I$M0ycnIYjJ0Bu>I-`WUnNK;A|0>HswRBr;Q`PU|5 zvvFXGunB$Mu^DI9*aS-c%e_-{_DIUtZtt*)BOh-$9>+iNjAffRq%>O&nzT76mJHrC z3Q19Pk)s^e!K+VF7fKmUef~!Z8R<5=QL3f3zG_u%VYQj8nXX^5#($6;F*UKgSJhr% zC9^Z?#&DZu0Bs><@T_|sYYL|XR?0K0ht#~Qt3Id)T+FqguYqnm6+!n7OUQ}B9xI`Q zU-j}bQf`LqC`~ukq<@;V7*s=ye+PYB$X@wp9;`wTiHPtl0l5p>0Pq2@bTL?#*dTta zPOVY5kf==pQgqCF9RQ+u)2 z*t&Y85iW?A8c!*pE5?qkZF9H9RVC!Em9Zn+e7=_*6?<^>)kww1u4L*{^1`DF4@>={ z-i$PB`jQJ2HJw{{224*{TYnI3AZ8==_6K&P3*0Q(Xf-V&gZ9IuiIEhzv4@Pj3Cov! za**%Qj8O|I(Y}+D%fFs%q&eZagw141b7tQi-uL}@EPbbh*ZL!#Cvij_rN+@4C`e4O_~DawW<08 zXbXI{HXY9Hj_kn!4WqE$E%f`};IhDyhP+~d&-jBeg$0{l3hi|~gjB*LD1o5@)l7La z9fR}eekYJHU{wKv;a)Mhd;ZL$VNz*v9amzkbyV_LD{Vwz=IrqNQceOxW~<)(f`*?K z8Z&yyY4+}vXuljE&vvp4r!K8-?ebc~m9v~k7b~{a=hyqd;ghEpF!$0HW!QV&et_f9 zKD?{as3qQCc&voI7UUti2+234 zUP+>E{*I6QqBgzUb&XiyhA;}BhwKSl@$hf5;b36M-#i7Mfu)7|LA{b{0$b+bXCJ*+ zl8zn(=_uavxA=642f7|83yWoTNa*7npykSSy>K}b$N8)y8A=O?fISWa-~z$1P6fYe zu%-I^e6Uj`zF^W2D2cp)oH={#m<8poXTYk;7cTRE5eXA2s0y8d9(`)ZO`M@CYRHx@ zV#{4PY_i>K2b_u*C(+0;Y1Ga0-Ml@Op8;gP!z10Wl|FB5qmOc zDv*JS-u`u@b*t_nJ>-YofiIOKO$W8kIoVY4jz82O8a8e_$F{%%xG@1PU$jPv(kFAI zxjKZ&ym^I9_jq}Qfzq_;;Ol2Zz}QH$nNwc1f#+;}(cr3d!hE1jFk>pxBa{Vl4O;n(Hx>v_}?Qf}hi|4;p3#T~;l zU^fn+8CW!JARO3m7z>N$((wUs#IMk#hNy%5bcW?7`+!h&Mn-#J=O%%|g^IWnb*?aZ zL`Su9`OsDNNV6;=iDFEbiy(?W%<(E0%TkCCr#zF;6Ab%}43P7xe;8SZ(9g4c1D zeSchD`Zfs`@N$dK1-_1x{}^ooq!D45FkRd`r%B>c^~r#beoCAQYJ!NaKyk=J%aL5cFr>>3W014B{^-aR@=FI;ZC1nJ6v&fxU39$K%OJa5p<#s%#Iq-}Wa2dDJ8MNH zY}t1~BZ~0V^r&Q+tfF?gWbLqSL&?~Q;xuyUF!TlRAL?8fs^zah_}*u`>aex{4IhH< zHPQpA^H}(f#jspx3o!+e^n^4BVTY(UJ)_VHs6POd!1Yq`02cv}?)olC7uA4>K+J{t zSLX^~u!F!E#Ug81P-hV|DNL|?!A`V-Qkxc(43}evku`jG2I77c9AD^}pD2DY?k#=j zvD~rA0z(C$u25wL_Gb0z8zA$X3?hoiqy9(i=BMA2zE+1kKYP*egY+QNqOkp%ora&G z@~XCD4_^`{0rve{L(jLFgCoR%;bQu@XVM!}KIQ5tCB1P`vw27Bgp8UKI_8A`*L&EMdct{aEK!PR&fV0byY?^@54unT6HLtt+PKX^eKFo!J(M#OP&Te)xZGu ze)wY1tpH*GTa%MfOq%vG^fkd^okqa0Trm%pEswmdyo?nSsDvP2J7D#oQNj0xrA@8I zQmlOwC>vJZD7-@W+BL$MQW2}5^K#*p=HfbFoB~2Wj?I{DnIlZFfoW9{c*neVPV`sp z?4>X!0eA}|KEdx9Dgu_Sj&sqMY%n*;uo)?3z}|XRn$LFtb>|3us^k+g zZPmX{?Q@rJ8<}pG;i4CG4Iw->UaQ+(d7iA6)PW2zyRYgNwNfmTUdZQ;z(Y#^l?jvj z_4OTS`T%+#nx1oqVe1Z6x>N&!|~<{wHw2 zE$qxJ=v$CC!z?HQSX%jK-oJ%PK+BtWt)o~XM%EX3Mf8=YPclw)gZj68uiRY`{{@%_k{>=jwkSVD%F2NYcn*JB^%9LHRvL@`Uy{z$fS!1uOQ!M0d% zRv<}}psCeMK*gPxwQsrxHce_c*r*2#qZ^mG)Vr@C#Vwgr zk;B@`;2Hipiu<^;y%GoRjLPJeatKKImfX;GvayTlSJ>B}uppk)LjU3#l`xc$uk^TX z%}JSkYUEUVs1eJXEY}-i2w7q^7p6H^p;8HAIs^ffi*#|JJ^~Be^cmfu7 z0FU%_r4{Cealr^V3hdp(0*mX_Us28n8cWu@LP3#TO7h{tyB?zV=(-N9bnUj8-Z0_& z3!5j<`wM7I2I7Ay$GqVrL^Q-n<4OYcs32m1NBa`RddLvSwY0$6N3}rxI8Z{ueXuz} ztSK3w)7}Rz`S!AYgQ;uCUfom;#;lr-2P&5)JyOTvNn6^e=6K$=lU0v(Y<*IoyUupW z96qVk{naIEg>%Ab)s_JH1v5+!kcG`#QBx0)7Jn5{6X0YuOQa?rWd4FR8IhI4k$YGg zbd)?mA~%vs$ICvh?Q~k~iuWv~w}DI{d+#aN%YztHjzLh+?U=skNdj>9!2X%WH8(S~Ub^u@qMigh#uWmwUnL9A`^U zXc7qnk9{;-Z44jtq|M&Dm+NJlJo$W3i!$u2)kWVKp3}igfiF~m!-(!Nt^Du$K$&N# zhe-Hmo=N#97|0DNA}nDaU8{?QkGWI^j0f2~jIq`D_dFq#sijR*>lz z5vIG95A;&r6NW<~@&i^w8e+UDcTc6;uw+CKQU5oD-#P?Bl~0)1(<4)}u_fdr`D*4} zA~GDJ2~ef4vHFrXeaxiFd${Vf%PQ%B@OQ0 z&wQ6Ck{;$>%qjs?L-ug{s8we@+f`9U-%TwmuP8j*;6$Lz{KG|RpB`zvuerqzXP(S; zXPUj80E>$a8!rRcD;@E#amnebKQl7z;Y@K*0(wM%%xRfJM&D5h*b%9TfA@Vqzz-c9 z2Yv$1Nl?OuU3{HFL@j7!JG|xtrKfaw1JeFtSmhgx(Ul*uX14Xq{(GtzYzaXqeB=dy z@)6tzF8CS*tA&1aK8IhUEW4ojFL1X2VdF1VaLmE-3onChGXUUWiYkvGL`EIzX`ij25cvG-bU9Ks#WPXnn(wenC+FGEmCY8F8?=EyeR-6)DDQ)|@4p*p-Rsw<5Cx&-fQ1b|j z(`(auj{+XAV8&~7F%PIHIg@T ziJ5RsaF9wmb)kaW9*yOO% z@KQb(j<=?Wa~a|Z%)y@oMFCp_DDO8_txEe9vSAdj_^&OL^d5oN6n1s8CYLy$?z#9S zb5HiQiZO%i9%)9;`w>W*{%0`o1b;q4Qyh;3Yhw zRP{^6XZxFb%H&eIEKLJFrhWxgdp{<8A3!b6sOX){+|T3ffecj@3)Lv|xF{ec+y&w7 zDKKu+Bh~oJ4P7?`|4-N}!O*jhd5NV|yxz7(dL+jg43TLJGp>IvhLy9rxo~hOY4)#p zZHFJpPzLRRMO1Dp{q4kj{R2JIS*{GxmghycJ)h&16(BJLfPy_1sWadkXuB~k{S`9y z|As!g6J)8Gd=8%JW>05Lk-M@u_n0w$F#)cNFDO zH_hevj$-|)vcp~|d_T2#geyrDum4iK&Y5nY0GwI!aQg%maCrAT6m+nGQ|J6R_o^IQ z8^YmtTEbEg@+f#*E6(#2Fy^07DFOfWb9GMu)m4z+b^} zln>%k{8e9_ehlQh?a7XBhp)(Yse^M`Thqx~BAs-e&5u!%9KYt|Lgc4W6j@ zK&f~6{I$SINA`(c1al3j@#5;C8z%KK3HgeKFQM@={^M)MPpf#8VEUzE))Yj-!3q_C z9XHks9SDgcrKcwW8S7m!veA3zG?dzl#-O?YxEP~Ytq}75D&U}FjsU>$IRORv933(P zzXF^SC0sFE1Z(yIj(lDBH@Umon+ZCJ0kx3_k>=MAThDgLS!jI;Al=j*7oO{M*2~Bpgd%R% zR4}fSzSGG}s08B$j7OgKz@e|y?z<4E7-4U3$91MXIWo5T7T9RA+L+E2z4yE2j4m+g zDjlaEe*;IKV}c(;2o@~{!@|7*$O|hV%q_qj1^Edq!R$@bN*`Ke3~s>)(3SQgo)xBKR6X6!{%`zbGd@uQd<$Nh*pm z{$V`b&xNva?7G5PSGd1{ow58bzXi?u=>86d$mSCfKBlP(mIF1a@tmiGI9}rL$+~d(@IJp&SMd#N#?3^svMakU`9YWk0dj=g}3tu)Gbdc@xgt2oSmR zQ2=y46`vae?BDB(L%AM8mZ%q*rmy#)4Jev(G&mz{K6~2psU<;jo`9}&Z0in&J?Ys6 zH;ln@x-e!`McfSxu-276@<;|qIj&)wrp*bINBF&=khw~h_ZjDxoVR<#5G!D@o2jiR boB!(y4o>DmBq{_foPTUbfjL{}PuKqd89|Ia diff --git a/cla-frontend-contributor-console/src/ionic/assets/img/cla-icon-logo.png b/cla-frontend-contributor-console/src/ionic/assets/img/cla-icon-logo.png deleted file mode 100644 index e40e5904f1de739675b4f86f52ccb72b3f35e08f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32561 zcmeFZhgXx!7cM-Zh>Czwib_*Z5vhuZbUc7TP*95WA}F9p54}Y_N>mJjRH+I90i_61 zLREV2(nBwy_YV0c_&axf|HHkmzzV6340MuZA5vcn&qMl0)~wd^g? zZV#Ov12;D};U{)B&K3_H9t+z$Sw+puoB`l0xUF zi{AIIIb8Q>_&#FdUp)|rysc#WS^nnNTvghu2-nbQVlJ-NRX6R`ql@V9dBOnRMR(+L zL<5ZsEz8?T>LJ9;^P?k2z9DH(s9ash_Vu$i)TlWh8M($K7w1=$f5)=K=;CPq#=xXCib(&FJATd{@h!LoTquFF2oYYPa_AVd$qrLn}%-(@#^ z{kjr7oqV-rjk%+>F77sjpX-{=NaVYcfFOkfZ~+q?Ud%lb1SJ2zOU)yZCh7aTkp6t_`dt6k9WCR z9-H+cOu7{Ufb7`{X9;d6KVI4NpwT0el?hI&{>dRs@31Ik^}zRExmkj^Sdjjqc0!3b z?7{>`U)pK{?ahi9$>ltrHcp*Hd*uoVPsaY0wV6TR>Ec(0hOmQk*0yng8@=L0;$`$=ynPAAiFfL zX~S?C^IFa>#uyo6sS}|0IV}LYAqlV3jq*3?-f^?FFD0rhCDwEX?u&7=AWP54%Xn(o zMBq5wl-_3CQa8Puvj22!^J(fx+J=bHVr6s5=qqVeIx2;>0K z2~Ka5m9?8yg5o~$a&aSdNOJ3jIIO+Wmqyubzx5oDsQWT;gd( z5(N&Kj6nTBCBszXq9LAa2aqS^ORL3l8iLQJ0kkEWJO(8 z@FH8jlqVVBlR4i{3YSdB%SyAYcJkxY_~oUiZ(P@uNkOy4Tu?3%M}2x5!k30kAGu0!t=-RmXWQx`PswX?q9ZU~R-A?0PN7SiQbgisZ_P8Hg3H_^sjU9u$U z4L{4#s`e;+IT&Kgf_l0T(MqrM;)*6p~v_(kG|D>(_&3Brq${2S8Lr z{Y-DKGkL>RMqtw27q_~Ly}>UN88_bE^(%vD}iSqC8wCgd+wWP1wzF=B)(&N>m=Fl!w+ahE9q=f>5A_PYI@8jltBe z3;T^ysn0_ajIuvo00Mv%mK$n`%=GTRnf z;UaxnQim~r9>aq+yZpWD?w7cX>FpfsW(?LHO}aS5Mvzv1))aJo-pF%MJnvXcNg?_; z_+AOEgN$wy$mPl$jpy78YO~*)awqeuN+5Q|=CS#glb??6`4e~U*8{)^#*~A#Qu+o3F#|L^x^6`WNq> zRY27}sqAh2=%k*}fGlM}zEfjE*beV=EH(324r*{nEe)SKlL{|G)C(R-s%DAJb{ju| z_VmE5PGjAjNL=wTcnD`K?JoN^x->xQB`r{hl73=s)7-Q$kSB$5u^ZcbkNpaD#Q9i? z=RN+bh~MuR=?r52(yArtELeX}SwCI8I9<-(NXq7IE07#Hg7?DM4QT@fX?PNA0ZAcg zzu7H*yfg23C$d$GaCl0!5qnC_#c2QEh1u>AxXueglr=S9Y~j`iW7*CHAIe2+V2QXt7Q-gXo0ez3oc zFsKN6Y4MKn{bmfumJ4zz0B)E)BoJg#o=)w#zhO1F^qUiJ0SGbhtPg!A-^*Q0)nn)1 zKg+`?G(au7)3(mMHkg=N;M+&gTDWXX-SQo3`lx=%grY`6isM72A}Jn5LAa2~6^|82 zz{ztCiY|`BvkX*G0kc>k?*7$p*l8x`iu&I+yO?=x)Z47f%PqNUX-TTp08J0i>oM}n z7sz*^oYfUS^6qtoGa{zYB?#IGEy*|pjU%ohXy4rF(Re431BgmM zF(#DM>;K@2FO9GAA-8()HlX(wF3Ooj@#D;n{39d~Ere?+1y} zD~8pmLqse1u{7o5NgSc6XTzhXXn=cnT0;APzdk0TLcP$D>=_oEXfml zpKvJN8PDz%GkC4w;Seug4fKZ6f-<@3Vzc@;I>uxVC7C=ly#m`-IChooWcgl{sz1nK zhbJt~dFIf*%uH(LZL<%tLrgu8G`CUO+pZn%9K{=g-dAvwV{7R>s~xFNi!M0r&n>Q< zj2BPr?vJ)JLXSR9?l+L4jqlt##5#0t)g+S?o<{I=R*GgpzK0jN8!#Z5T>2!*edccY z9@#|dP`nPr8X4^W08gV-cryRS!3C8m_9BCBv2x{4HmCdTq-=!Y5Z2<*YvpaNzZ#VI zY}GrD1wt>-u zo)yN@x31gC6zdZXnaRO(8YeI8woZ;;eyvr}3N?A@!a#)cw-^pmX$ksBnKz7IgsLo? zbkErr9V={-Bjdv}+yE)5>1i7CDI>-pZyN{A)VWcjOrcu(=~Ue-wOo;sOFYB~UfFMG zCLlCP3^BJ}`uwiT%l2$btso=`f4qK$olGNamGZRRhp)5th)dxqw(N5tC#(5`23mMH z-Wd1!2}Ren#Lrm0ld8Hc)?Zo`+FjfJ&i{ zAf~*Xur$11D#A%6YDjrgE7Z~0^iP=s*XB2nR`76yW5Mk4w*;9&qy+W92O|B zkRW&eZa>yt7l(dCVg;N0acMK#EL!Xj2KA(94j76-?*UT1anFw1rfwODJ_HP6;jTZn zg*jv|esWZH|1A&ZMxAwZioN>?p>aZ-REzEK1b@V?X|MU@*^6ox3GuLoHX)$L@!>aD zmK6DjClnQ3NT)DlGX%&985Qn#QgSBjmj;b^zzZ}Zj|J-7FE)pFBcU1&l$5mb)L_4S z`BzqURO5E`Yd;{Rxcy#gc`^)Qz&G>}g?~>11^OHNU(%Rt)g=Z)dDVpmsDSf1W#2B` zL@>Qw!R*+MY91J9c>N7Y#;58o zZ?bP1+(=f2=6HlI#K2esRR6P0y7>A%1C8kdkF8IQB7&S$XeK_0ua09w-Cq6o-qLn+!R4tUaUe~~n1kUfao z5Q@>;FYzVSL@LW}T#0l%UhGB~eZ!}58wsWdF2$XTOdxy2@qSm=Is-07Uy5exm^uJE zbEu5XLP6Aaahnim334J75q-KprPPPd@yAf*K&LS)0m5sW$bB z&vvd$GEqm0)>;EfAva@KxFs3XzQZ%JNZICs3>)aSiba6gk0n2&n%_j{zJ?l@XdnHO zF5i=9xf+UB#H^3ffJ|M_=18M^LIh`HB`FvP06LpvNmrxDxw?{FB9o!^WVN6pghH6d_=h5hq*lwg?6t1E`11BXLpb7HfCe z50yNCD{P@GGKr6Es?@*o3?7G8YOUNnn=wGU=d_acb^H$?z<~97K2Ogx zBE`eOS|z$rTA1jR`5FAB3t1O@5;`Gpo*4Q%Qto4e(txNMVu1k0&w4(!N+?%AB&4P6FPV4AsVLdr zVYVM`a2o2=m_K>krkWV(Y@|(~0hi7d+nT$Nv-4;Q8&frK(}St2$uX{vN7u{4cWuYq zA43F-kB{EBFW$-2)i5r3ra@vh^VI_iY?4k6&sxs~4W2}bP~ag4!yFafpS_u5Gw8ES zjn~2yz!cqh4cdpMH+nwXRA`n)xV(&H{ceugVc%F`^m4`&Xw&u z(*miAAHT9JW@_RN!%G(RVoc-p^M+8`4kOG?GZm03d;2rWtDVVjnOp;%Q=!@e&AAlB zVk5Dq3QtRh`$ri%{t8Q&F)}`h^rzUPl#cJSn6yAI(X%-c%>Z-#PoZVIR+_dkJ2Frh zUJv?}eMgY`r!({9JSYJ%8?N}dyW48osAI&A^oP6#5bC;SRQL?36m}dTB(=cz`(Ds} zVsmOAf!W&FyuAFUVRyVD{Huc0JdZK z^m!cjmG|8#J1pbAPEVINdo+ssL)W#0oVmBc*~Gc=VWXvwi_)$Wyb27rN8LPaJGbOg ze!?NJd++pdeM(4tuTZa%Z)YI^K7*A5Ru?gs?g%}PNKU%3{W{lw9t zHN6q(_>8g>on!w+hPUWY`^g_)=dOn^ zB7EiOf%Scg;ae=NU(i*msH3bA3*s^H9$b&{V+$&_J!bHM7UU#G+xaz&l;W`MFdl%} z_sov#Kg13Hgw6xs2MK7fm>tF>?B}I)MsNTDGDS4svXthGG)&}G4X)HeWA}5sMrUwm z@KfWYvMRf(8{8l{nz#6=Q$E=knie;71=b(ljT7xos$}N$NdRIVY9zY1R%|~{gLVPU z_v&^Nw~vXo<6R|=d(a8YSO*Nd)^7igT2L~Z91P1#3yln3Q?|cW5kWm@cP(|&d%G<( zFP!nY-9*F{X4lzIXjhi3f@rL9%gVQ!!-b~u9OFfF9V+Ep22h5GVy^-lw#V92#^%5q}~evQ9AYf&D58v+Qst6$N+fFwA@PAdVY}KK(U@cv2qz0bPBnr zJTxeI$x(;^o)f=~+x-lssH9;2-tgn`pz(fKO;#$J4kqntNRb(K4r4&%9Pw(H5ik6?KIn{hDi{0+zpUFJ1RtlsMoULA==bD4V_M zayVWfexQH~?4&>ybt1Qx+c=?33*&d+F0G~Q+mjfrby-0s_y1_G;$NeVr=JZVWO4$JT%iipiRl2;!b@7Z*=HGcb~(>4g?G`!U+{6FnK~r2u^dD7f85 z>AY*h!*_G)Lz**XD3*I&xhS8a7B|(5d3FxzY&2~SI|Rw%jzY`TNS(K~NJldYZrFwL z46?aQ|4r?MT7)6c6+5ijWaw++^RH5FMR{C>sa~gOvgTKoGTt!W7+R!>UXG3Dc?yA{ z1{f@MO7{G8=_O9Y+;0{T@wwz@LNW@s5jGDXff$P(x$tp@F8wkm)pv@<;upR&YP=<( zwUlIvpiKbz(Rq>-uJ}oLS(mE6f&dH>T>nt~%v@?u3t}iR=!{KG_Vgwlwsv6!i^o%y z2R&?*Rw43$p-BQEH7PLGAa9ufjb+*hfiX4f`IL21vn~#cy#QdIgk3Hd~E7;sd z>dqQ;WH&4_c{Lvao04JQdv?Es1nbs9FTyRL)H5Pakv0r0yHQX)El;vTJ8#-R=OoWT zhy<_z7`U<8VKw-lRZSd)h4HHS)vkw|bikUnoxd`QFHF6tY>{avBN+{Hf=k2`D8(>i ziefjNLYGhX{mJ+Ql@|k5yBTEBWwh2E+p)5Ry^%=qCwiF_iBIGn!#eZ5rrS3q9iLKS1;>%~U3U8e(W4y{n>3znZ~fRDUG%6^7s5!g z6D=ma%Z)f?@dcTuRPmXj7(xqLb;;@=o3QJ5_sm8gLto_s@kn+`co$vJtmvAKZi+rN zU+P-~KSE?664M|x_0d-<%Apuu*X<{x^HuW}7g5_8A~940@Cbwl3*2^|kA4;sE*#{? zphbDUmjBP4bng5w=)-7sT`*qlo_lCoe zf}-d*mP($e;+ahOSb}#?EWBMFu z!=M8{e@^Y?A`4K2bAIweb?tQDYh6D)47MMQ5b-vvwx;`Z$fanCZaFWbDz+h=j?T0# zhT8`TKrT|Ua*O-q`^|rpI4CqZo_ZkEX@6iTNT&;iVy?(RWQ(>~) zTodfYwt-_}-~aqEpGpBGx&K9gDLU3<8IN`MrXbYy)deJJgYWf@1cgS$L0yKBNR=6s zOrfk~a|#{1KM-|GeeVH<65l{5RO>nZ(g{qTiOo=|xJfbN-kR8uk>+Zq7+cCWsXk~((MBda0KJ}fhXPF?0$1W zoKH19C8wG2hu8A2r7`*ORf_5>Hk|7w)xVjpX+TridW=x$Gt9Mo_W12?)Iqp3#&!2% z(d)Tu)Pu0#(@+ZO_H6j)&zJGLxoxLmthLh^b1xrjdoyXzJ}yiY=M+^vDyBEb-zQ=GV2Oys@6)~=*M&# zJjEYq@HzNbg`e2|rBI6MyUEP4mk8()!Gk^W(j4_VXl zkJvP(dTw0*hM0yTJ1Owvo zihSlZ-FJ5H{^q6)2`Spur(xl=1V-wqV>%?@eyKoe$mQT^6b`#49X{`l8|IF<3*X+> zzX$hJ+kTLUUa!~=v4iiR5K{&}tBsyPY z(|$Kk5a;nNfch@us6k%wc1wI`f6$0KG{p!73r#j~>gcX7hyp@mX zS!btW^3gCnGEdR~p$igc*p%0|VTEbVM`Ch!=VN)A9Q4^Cd3T1hLZh4SuJ5(2qFoG? z1fiRO%!TFZBdiJ$1*T-Y+R+WGjmZgXFx`MyvfmIdjdti&PH z7f0tVO6t8l^3Aq( z%^YOHA!)+F@0+4Thkh;_G;;T(zj~3aaF`cxuBF0!@uaJcHB#Ppa_6FLF`34$28GO& z_AS-K=nh;(Z_l^gl;=+-(pQD)x_I7Jl9PtrS!0@*v&tnuao!VSi_=Rm*6#d<+id>{ z*n6C~+Y7^|Snz06P@8mCb5fHN zYNpG3ZTw)TjtXP0?pgVV+La?`Wd^xKDIzcpzmpsh`l!Z7>}x{#>V6yOH|**(OWgf| z)zc%;^;R%B)n!B)l}^V&ccxU{-|FYOu6_#{U4X#5OO=PJBRkiARwKIn?Uh)nEzm~! z7CgPak4oH4b)co^(yq(;9@!SD1sgOlH8Z=e2kzY&9GY@LI{rgq#KeX1l!Q4KdDQZ< znYP}3N!Bndu|(^uFXdu4*V=?y%#)mP@;)QR();LxUOsFgyrTum-}24sTV0G{z@#hF z=;4$k>Mx{|(bLPF(rVQW;g4zi=~O{Za}$!OT4X|B<^vIK zsF=L9hws>>OHU)2V0-5{(u=3fw6l*cOJ-Vn)@s8)Dn}H9BfD}DC2{%)n70MT!F66 zE(wK{q%@UrbJ7b~EF8rF##S0*n;+kx)Qe;f(u%n$O2Kcotg2}v89NAT!8_I{Rmb7Y zZA5b(AcWC^6__nQb5YvAM`Dbh>F45)lg#q%EDd3N;)Y@{E^t_K<6hSYT2@odD>M02 zr{gj)MhG>9oc~@hJZ8e~+csK-E{~0-i4m(a`&QMI`fNIULK?aByiG_{(PeBE2brc! z^&-;{4On@!wZ*OXDNT7W0M0fTV$vNnl%(v<^U2(5{^&3NA^&Bb2Vr*4O^4E>+XeVg z+c4)uShJ|ZfsN^>!}>5#O;jf*&D`i+ly^%KHqEu_`V!mD^Skz_f%f*j;!IyyA|K+@ zhpRQUtwQa96cJ_#6TI{Nj>~_m*h%d^>zpF}LDR+}szLU!PAWg~yvA|3VzG`*qu6=P z4Th`&FsQkW`A>Au}G$WAqp`)N1sf;S{I29{I}9!JuJ+kNgEH%v`+ ztjcu}kT-;VMjdz~(P^5akMZmn^)T`kWvSjp)2fb|X_2HFkwB~w;Z=GlulZiESGb_q zF7>WCJl13wlij{5#cUblM`b?n#>h+dlhfPIDXo6z+=~kuUBgRHNpY>xWwJ$6dLiun zIsJ=F4bL-@Ihw_H^KcjZh4)9Xo7yBsLS0zuC*QDPZnFtN5TV7d1KVyu>JCohK__}} zv!?73iFv>Nry`$*Zcr0e1$OgVmR#{Q8F6}2Sx*mI?3~ms>P85&rfApR(D!}Z*<CBGWOQHi?Y^YPhz>~4tLYd|bGsF)5s0Nz&1+egBerXCi#^$-i(p{|4R2zV2PGvbQwE5f zpHkzy)3EmOTFpOm<6>9Fwl{9~bsJ~S_W{X~kvI60BKgC?or8iiBP*v2_qSUddO1(}tCrn8;Q)$ZcE%gD6*_<+g-sW-GBdp@T<{h8AFYVz~F zrTL|x{k(9TnIB$;leED*(uY^GW`gh%(^!cLwAb@dB(Y-DA0f)V70$KN$w^SMP(;pm zjR8)QIG#6xQ-WcG?8QQ2L2}X8i>$unaID%~EbBQK{(~J}DL505ovupCQM;#)uqz%=_4) z>7?O(KM~H~vxR;noP<&$yP8{zW|CH&z063{POP-t@|Ql!0L4pfDNNIk-QBy@ld0R` zQ2RTzL5KMKcTlQsqHeK1#7@-pGmY%LBk^rQA)!1a4<^wQV=Ck|nDP+boCFQ2(Pva+a&BO!-GFWM*!t{hLzfDDmbK}CG~Ra@5r37junMX(L1M~ z4kG?|L>f7n^m>Q<9eqq8FB_PxC+ZnXA*=;lnn;1FsC!kE^a9W9XFzx<>4NH5nt{U@ z_5|0dF%oR}>j6ua6(FYR6&D-}%$4?qsdLWJ!|vg4!sOVZ;1M&+l5285 z+U`H(`ls&w({1Rb(s;GaBa%F&EAuM#Nw*Hk>B1NISRl%LYjI?1vAipA;0c*iVmqT= zEHLivs_D%wZ|S)g#V#`r1e zlM6x$e9*AuwVOqZL5@G+>8>;BP;WcsQiBt=?eWqCr6x%Rkpefzh0Vq3<`5jhY+WB@ z20Fcs_WO>eZDDogK-VDRggI~Sd^$Bx?!&se1un^Kw8?^tkt7K=44q^mlQmUJ; z+W5wH2lmD{NQ4eK-7%I%FQZAvdi%bRyzP{{i_s`6)h;D5Ta@vsy`SpBAC1V=O3i`vj7#Nl1u+5alW~ zt$$sz-%pFg3MUVy4aGcfsyq59?W)50Z8&cdUFX_rxBL8I#@azs-P;C6c%gpDD>4r5 zq{CA6j%;?4aG=>9hed_hJrQ~0RkxJ76y$3^j=BB=jyE_q*7(KosPB`==}l|xNw*&O zWht@EK`Og$@YaFM9g&g|!Wh4D&_|k7$+$ews_s`xv>!BrIcKHCSq7B2Ho# zrF4xaMs)CQjs(=`r%`fQzy%FkD?18;itrGP2?-2$VEH80NNQ=9U08pwvF%D(1BYL0 zC>@+{NUty0R!}n8HY*k8YetwYtio3^;Ge-Ua~0g?EvU~fNc?_PzK`Ln@ay*a2{HZ8otCd+kX+zk=}-2&>AVFgOxn9}TJjJNH-52J`n{bRmC zT|7*Cx{V>fhUCEYI-9(gcLgj5j`}lgs|DF(Pah*Ak+38#zrD|4SwV#<))Y?;6oCOG zFa!=ZIX~^&*3pt2d5@PisA1B?iB@>d|ykA=H)b!~D%D^6+jff~1pA1az%{JR! zk;L!&Z0+$E94c#oE)l-$h^NP_w+3|U5S|N*Uenfnvi%L4pN3XFM=75N<~vu{YRx>5 zcA>T%Nz{EN8*lG`HclT=L@s^x*;k;rY!wW^S?%hDKZ1BDU5Vc67qK$Ngy-{_wgV|{ zcqtPWWC6NfrckJJZKbM<>6q=`Qn#pBvCXZxg3#=`My^FRJ%EH>n~xH09fBkLh-67@fL?vjtK zQ0Cd#G;#8R16S%lA(xWX!i439Da_#~`k1H~iV-JdP~y6p!PDl?yKFHzz0x5W*4SB{ zlzpSMG2p+E^2wctPiSUeg(TQ}KZ3SOH;@8hgN|oBJY+tC)-AC{gqz!BEd_Sv8#vvD zCz8dJ+r4-yudFPRreD#ib9?XgX%xG0$+6ish*k&87L+>P*<7KT#Y?r7l>Rxq-u}gM zZwJ{*%&1gb+J}i+)@wg&k$Sr7CcEqH6*3Iaj63Id*krt7DH|w)$RLi|kIK)BAK>@g zGp@`g&+L144){z4#a#rNBf&bR?^Py4=Qll^D&&)kX7nlg^&!i9ZE&5{H75G)uE>f-F&sI2 z{JA@IPRD~KRyQlE7q;e==L^~IU(5QU`Az)6kL=s2=HE4Q4Ggs}-M)S#nCC5~cL;w) z`s3NB_ilEx51YT0SEhX$YN#MwSkM?fxq%XWRBM|po!uN6xiQnsT3g8WS}blW*130O zL4rO{;I!%f1^zpit}mZe;5beV7%zvu-8_pBHSp`rF&j9)a9AODn_!Sm-zHAv^yqLbv1@75eUa=f&v5K&tNFfXsplj%e~Q>HRk z`H9FfZicXnF(M>3B4@BI?rx7U(>DEtF}2yFq{*YnRhChYq^b)ao4%kTH&r68fT5uP z-Xd^IM6bc>@Cl2R)JTn}k&WBEq!6=nQ$MO%#2LO>7x$$g%FcaiEw~axn%YMT7V6&= zS(%r_=z7qd?KuTRPYAvNx0(YG;7uDG_Sa4FbKOkfK4$6c8J@RaUTiGX$GPtf5`CGmv`;yh%cW(LqWiZ%=%C=hokd?B=Gcbes0zHhZJR4?X*a>k+^xVP8vn`7aSndx?T zGykQjWhb)FOZ59WY}8IzF7f_+sRZUIX*S^M+?Mjq*2W}=u}4H2k=l$5M8L8my;1S$fT;B2`sLtzdiDk{h0BJ=xEruUT-) zB4Iah;n+>`@0a_SX{X*`asS6oTaP<8<4a%On0?O1v3_;o@cEN)3xT)qCE%CN*tKrh zY=8M#?d1k`+DnTbhccIVVaZu>QNLG-?l+vlF8 zaSU{q=C4{2kASDDFGJ7lS4mTKmzuq=+?2V^6fl($mfk~*u$y0a+N0eg48|?awa1!1 zx|yfqS>SdTug9!Ar+DnXKHNiuepsW39dxkL=PLXd8bHi8H-_iQ3cqr`5X!h1F8lBo zaiFi$bKW?-;S5K{bzmXp)7m>KR`Acwn`#&CF3zW%KiRd^EpZ6U>V6&RiUTU&yVdo?E~D{Fs=2uY(&;-hNugT>vpG^epR^0tiM43 zIx}wva7mheN!~*O(1~qD+-6fH*ang{uh8t(t8sJ4WGAPM=b0D-#>Rk^>+1b$@$x&l zkF<)ss*rq;Z*r7Zl(@LwiWG`WzQ8#VLlwWS^o!V$U1Xc!M(1!g^1^_K)|F?$s}V1X zlkGiVB#Ht7MwY{`wU|Rogh`!5Ax2e2twVga^U35*;MFF_M=%`&ENqX$uL%(ssh}VVE_H(a4s}CF#o}Ne zBk#P^7f;!*{w8kv)9gGt%I7HozS5&m_%;?I5@4o3HnKG7Xiw|x$xSU_1Rc~w)*-m= z9Tr&@2KOW;uIEh-4s#;FSH^ayR~BLgyKZ+~ed>E*9)>EC{=h+TagC?H z7fhb1LLV%1|3oN0x0{^k=IIYWaPpIzeu_^=6yvV7zZyZMBGr;-m4i=#Om_03nTTad zxH~+8Vv^gVzC!t-7z9uWn^2x13qJMUye?*ZzNC~RI8#ex-A>`hBGd78R$8bWvXv$+J<}c!6O?@|7rD?!W z@{?cho9lDfO6-25-5qvVh<(cP;tDW31sy@?1T_E}8NS=t&&{4F0KBi|Wkeyx=Qkr? z?EQ38Tchu%!aRgBi}1PAfv@&A4T6m$>{sCQOF-P|820uaoVV5C){P7E#v&*8H?WkI z67(Xg>fqkS3*~LiiQQdoivGr92=05gUmCu)^PITp$G;H!>7fiYD2Fm|&)!&|0w2)P zn!^IYv>jyYHPub^%}U7ZIQ`J+eKBqoox7fAy!-rjF=LpE%H zzul|Y@aaJAXKWw55o++d=hKhTVs3* zQtl_d8-Q{14zuPl08Ughs}r-tT~eT{n5}+d{VqLRlJZgF)9=dQGeut0w;nZbs4F;$ z0<-+GBLLiHk?ZbnZIu3uh(9Vmd2}IQWQy^&{i(rbkP3(zP3*Fp1os5*-TdJ5KxRthG*9}%k|5mF;wTD& zU)kYA#mtPj+(+eCi01&t_y^|=)YNjYmya9F3ITh$RSQDuoh2=k&mQ~N?fu|NxZed#i5O5 z=bk{xsX%hVr7r0=Iy~~^J4n@PPed`1zd<1DAUyNh`1VzsV4NgYY$)nPLVN$~MjiYd z0SfQ7;S!)JnRUGINi4ANdFOTrbbL8IX&W6;J07loc;>?0wfSG4-?+W5B{HZSf?mjq zQpO!Ze6>8Mw>#dj~iliTQyfc20l~(jBMoHWp#kb`f=RgYQaBE5I0hLa*T$%@1TYY zgTrorni_bS^@gHOwnIQh-~z;$pCc=gm0MjND4-x-H%1tYhOVL&%*7Opkl%7nY&Zp0A%cI(h5dbojN# zLfX)WSDK;o!#E){0_W;}Ug~B`7HWu_f|q`Kg-|&tdHG|#U-=_lwGn!AhIdrtjY_!I z{XqpBzUfFzXbVb@l(4^B2eB}ir_aP)OVROn^LaFx$(-a&Mn~<2RK>H%gIX?#mt2273`N zC{xSbD-?;z8DETgL84lcjW78(g4_4p8^6UY|FhySSSirr9}o8?(MrfQLouohozoZd zjAe@q;gn>Voh6c~hU(r&s! zUNr11vfhh>OkLqZ!&HAnlk1qYMNi{V&qz0>-OA|QDzQ^w@mqw~OVIJY_8cxl8t>dl z0|mI0{DgjmkJ0Wlq4o~j?U8Cum0&7(8wabgez(C_fx91bIrg6{9EKbKIz|+Xr&YQS4`A1|G2QWd_208Qp?%?8f6}9s9 zytk?k>zN(c7XT_pf2jQ^aLHJMHc0)%uDO)=ZNlB7+w=>ppwl0k|Iy1H@Ow`Qx3pG& zCehAZ^za8|&M~=<8Xra)`RX6uPD->W^1A1e-{gNDI`+9!+{eH)Q6Qnp;SI!$S^mCQ zdtp|-LH?BTLRy`)DNf8I_+GoJtA|1GZ~yGMS9u<~0Q2ZS{=2QUa7J4^edQdaoL)zt zGaUjS9{oyx3T3B(zO;5g#jTuPy?7C(`e|t3<RpU2+(E^3FsoL`bUJbUiP7kBFGVD`i++eWe3GHvkwfHbXw&kft!PvvhbYuTa# zy!P%}LSv&CT(-H~DHx91le6i&Vj$qguRmtByMfn>?&sxN^*?%;)j5XgHuO=&!``A$ zD~Uo%UFtCPU1dbvOLLW-(zyRbp)IZ0=3bPbQa=^A-A{#)jZjvmoksx7!xtv30wxzo z#YBGaRE@$L@PGW2+XNR_J8qb4ytjgEvoJDepb$C=e-U3hu@T0TXV8l5@2lpiRfYR$ zN7MFA4E)vRIqMqAy2~`ivBnX81#Wbh>Fc9K?@vPB_d_rDW*a@cAin&jYG}ZjQIWQ5 zb^w!j^;Nl08zmYH`}Bibk3G{D>wfXRwrfwP+oN6J>IlTnc+0$m-lTk#a#Pj`QpJ0=jffw5+{b8U#h#2M&&&z(%`pC3r?PnB;0d4;$1;4W*jrI#K789o9mYJWM}H%y<)Hl&3y4Brov*+kN}qwK;L4Xc z3~rsDQcjs0ep=gGv7?2MK~Y$1M}@!K6IRnpz7C2fl&*AbeW;*2u@2!Hh?=whqXwhs zLuf6vZ5<`^%?VZho(<@r{6DueD~u*B*yKVMzny?K>Y(yZK6?AhTEXvgCRNPl;GFVs>+f9V*!KdAvxjXx4$MfLodISLE)QOBRUto0-1}PF^c!kxDN|yc%aUnh8fhP zIVTO~is&o9m{@`6=>bH%HwB7e$tH1;Yk}2_Irg5F)hy?8inlvMFfQv(wCMTS?hNC& zA_!;xxjS_c3;zd~y6Ojy z!_dypPJKN=ks!NZS6Ap?@nGEgJfe=r!6(p)k{i&kXSz{?jt{&##|EtpTAG(0o*lmW z?vK@-CCu%G75J^X*>f=WIUudHa%Vw&(bb2)%h{li-Q4t%>$^OaO9!TF4@q$);)10#QB)=s9nB{kO z);ni^+iNT_A~Mx3cKPILR!X1W!@JSnyN~SJaDM=qW$g)6ioYU})|>s6d8tWGe9#xhEK zOUo}lgi_De^Acg4E5BIfNV0)tG3N-k!}pkyU?B6ps9UoVFW-^!J>HvLJ5_X%#;xcX*h_3O2}wP5i*bML|SAV zE6GT$>lQW|J}+gC`#Q zvkyZsdRc z$4Yu>CQqOp5^D2kzq4B)g+I9cVX5`4k zx#>wz1w+T0?L7F^v1B_(AA9seVZr9%Ymxo@-M=cAg|m!pelokV`!lOgo0>TM)exL- zz#{CP6Q;d?C!DS!7}VHBLNCR58BO$4^7iFjH?-qg_E*e?>MX`<;TU{E#Y8DBduGrb zin+jZ*SYmpx70JTIv}NdKiyAt;~8DN2^5yBAm!jI7sRQN4;LCQP4z$x?5x8)@28%z zS>Np67DXrlek5~k#l9@Dv3a(gW$cU!#r4O~eeYdiVhk9&=2=NU+QeHEp813+QOJ41x@s;jarEL`?^*lSikk{XZG%e;7zfTP-t>)Q0icfzCJmJUsrO;2Ls&G@>b4cv>*Qml+S}Yc)AnPF$9yHkR&?m;e(^W zGN9-D%DJxL8yBN8a`>I1^VUvSW6W|0j+y{z)aGFp87XR{T0G?jqXnweaiFjoNuoxc zEkj`+Bcq+0jBASMk`F(VcDbtnI%CWaum=8*Q~S^X@7-BWiV(HD5?7-X7HfiiAy8xC z$Gw;ZVN3=Y;A8}!z-nsi^KIa8`R)Hg?_W@i7+q28&aeA1=zORE8 zOmR~BT}}M7x$n!gM#KQApRz3{*4Rnlcm$00uGHS!=oIVh!%2m#Z=;OY-|I*w+ zJvg9pqjx8ozG&{W$Wc~9xkknKW^Ta>UQn!82f(7kv_uY4(1ybUM=F~+$th^8|CX)Y z#1<#0B0s>~mxtE&BfL7w@lX3vnBcn{wNpl`*lpL#i6u|31R9DFAAwhmrtaQ#l0N*}xtGHbQ{Cr=XZk!pL zK;U;;MV9O0-T@$P4DyM+F4V9Q75@uXMCj~qq_7yveX`@pT&*mqb^PpFq&;j#0LY#q zc7$7aWFG*&qAX9bN_pKZ4f6no9BU#f2kdiK)lS0EPbow785~!Y)h6tQm6$t*5bBm{ zU3GJwtj;Rh+E5}qHZOtoLqm1;ckMO{j|^}oyQ3(=o1>q4@Xy#w16b9>wT zA&W3@lwz6tQ>zg8WPut2GX>2YkKf^s^ z8L2ZmZLkpg4{%Y_BR7fdn%rlW^ycVqaO{cjpQZWoS+m8W!~*eCr{6Mp=?&!kW@o0P z#+$Rm^L6{IOU*I;H+8+6E_~<2V*-vDALQ%&RQf8O0^!M)yg03a`2rDgD3He2er)NA zMZhYPQnqPtky(N%4)Y!iX{aewwHOrT7x1CG*nGT|&_CKwfv|^;jut>3TM)qE`dGhd zFmUEKPM@9zqf_FF_|^P~yp=aF2fZO6=#?IQJ|IoV)!K)W6N|pZLV?04h;YH&Eg5x; zq)V~pG{jqTv^VH3PM*0}aVY>27n3Eg@0Io6C^PMScm+|%=#y+*JkFNv!ur!e_UdyW zofRMw1UrVJeHvug@WlN8g(RSE-(>pa5dnuYCxLrkqCdHM@DnlM6U!$fzE{iTC5kT3 z;sc1B{bxDe9D+-R10qNbf7lhOfdk1*7xvP>j6WZ~dpGc*3+Q20l~+j-v|OZ%<-{*O zw6{`cmX}%*JZpH$1EDm3gTXE1a!WgksM?=-QVa!BANu8s#L`ml3D+cd!l**S|25$D zZoi`2rpOg!XP&D1y&y($?4YkxIbdn8J{>(-Rmyl4ra>Da#WBeCO)ll*AZ~nZb>RJz#9E8zHnCVrQF45Jz)n0?9bSK zK*~?76WPqr-6#p?0GL3YX60<}atbd$bOc^wzeSo1JL@yWWad{Sa55TKgR|O^opjCxmM5{tZV+Aq}!g}mBl%;J|GnJ8Ri_C4g25XA} z%8;UKorwkrU9!eVf!O>*fn@k>+jV=LL*tgJLRi~$KV|s==JBdikY(JcyEiLMiPt|o z&5hQr3k~H1K>Anh6zza`1(b~1k#gW3v@Nub+-_dk<(2;fR+9YcR9LCw)X1`c^UI+C z;Rkm8-1++#2~MUw_;iDqUHcdubt*Fza#iqMd}Of@f`s1FQB80nH=UM5Q*i(C7y$bc zpMG(w$HJlLqUaK@?^y`TeJ>f%1L z<9&snaVqI3{&zZ?V&RPfvU#^J`gpNgvG;#nm{ z3KpE98Q{QQ)WgEG3e?=Gkl6`n2KVvqoO;hX6-Ex#;n5N2FFyOwK(NS^!Uxrl&L;*r zt8n5m2naWiYuHh;ePS)9!fs1^+o~sMtfByXhyPqq{?hLb(a|29(?NZ_Oo`om&H6-Xl`Kn z??wS?TOM4Jd)sfWArIHMD2gE@(roXmpIP@6e&ZobOc;7#zyXW3>>Cu7}1@+36E0S2UQ9UTW^3Hm(y0zh zRMe?hLe4G5+RZ|Jk3XDt;;n4y@&hAn&V-!?77fP+3ao(e<@D{^36^3xEC`^YvmQEfsWbf=XMWV(&B07_z{P(*S`+? zm*uCK7UAg4si=?|(bgd2)2$~_%G+H|sqAvToGdzo3$rR&U(EaU>o2qQ?4(Dia9z+j z0Tr`XV%tbxmxH^Z<=k?H;tU#QXhWAQ(ZGt=k{gI;rtwwCk>?Z+uHF3}A8pccD6&6q zrYl{c7ktpKu3h#$5q#_6F9;=4{+~LZ$9AgJ)GUpB*)5?knZA@|?JOOe2dJN^wDrLI zHV9U`w_0w1l`3ABz%bLcQn3>A2_6<-W|@Qb(fa09kEA!Q`*Mg(t_RtFj!#LPT(6jR z>Rs-5&m+G>2YCm(dJ(s? zu>VLe8~C;qg0SZv&q&g8+zW0PLc}01Q49D2Zi>+|Qv6H7fE;Bmlro2HbSCQJXwsL# zTR=a`-)H$>k$)EJ3NksP)Z7W!7I4J;5Q`n>+czeNV&xKN>sPJBU9^u1Py7x}dGXO| zj=l|2`WqT%X2(vZ0}4mJlJC6CeH!ZZ!=}$rr3c{p4NHN?)k@InxQ(!SV(`+Q|4?+n ztt;e*$0X;o9*9YA_9%UR2e+43xQ`|_L|lQjMPcnbU)~jVfWrrnTh@Q~Qwxx1lb`wC zJu@C{vq4|%i+b5C@qO!P)UzIlLkqTVA$38Jz}qM82+{m)P_zVZT0p&QT})C-T(GImJxhL1Dcms{MRGg zyjeUZ$n59+9lXG_nTMmE0u`*PKRb7B>@FVGFXz@k_#L$v_&eoRT#|)G0IUVzRFxsUV<@D9Pzi|KUL&x`aQfPS z@NJ}Te^}aw(L_@-1o3x#-qw%?amW}5o0ZCZzn0gm`Dr z1~$Nrf~quDxGN{ck>h8i(q%|Ee)m@L?x)y-OFO?lX)qk%#6$7>{EJHQ!Ts<+F3;sV zgD20hBY~%=YV}=tw}1X)tA4rMnq1E={;1)K?{@Rz7y5A(DENac+Ar1M58v7aWq*l>Y|ckFI#e{1#QEFhSN`D zQ-&3adGOmHgSdW|?&)6-M(6=cK@K*0WlR}Jk~{$gKrfEa^m?SkClx~)14uzvgnwKk z5J?Bw!B3K*Gfap;lm{FB=4Ib*sW#CEmUWCCAn!}3EXyu`0O}D%iJ6elwPo(tP>H2B z6)jGSu-Zd+1g>OBZ>r9{$=|6eqLhKDkQ@6fUX|^Cpa~Ej{B!L(BD{l}qc`CNe<4YW z9z<{H%0CTd*8NLTNJWPwRF%&x(_p;Ghr-84RjBTcb5i&LqLf;rStdgoK5j5hX0M7P zU)j#5_u#;$Lh__Nj9TuKA4#?_;JLE35s(VUNj;mjQ77Ov{rBG$v%Aiky0z9NLU?5; zFH%EPnMJ~X(uo3P7cK;8T-!SClL{uCYELr#o3HweM;9)z<4$z3S(QAI%8!qmg00Le zdjpEJTQbSK!|HN9es^1cmQJ#L8$r0qQ>Mstj@$KVV)+9@A*1DEh*v73J%CE9Ncq7Q<8UH^v0N0CXLf3z?} zTiX}q;lVz5@N3JyW`X#uN;080O)buJ&QglTLq9G6-lrH4IbagztFF`%3f=lUbTNpN7%YC~cFYOa>&nEr*mD zV=Uq0h=I;JF#N%uFRX)0p%J|R-K)Ye>q*AVw!U5&rX;3R2Wz?`O$-0I zIE;zzl8E(Y3XVqH2>63*+2y&0vR#j@ohSw=50hIE?4n z;|sHDZ}TsI%mC(zF5Bn2X(}0$Pl)$jb{Ep$sg-o!t{YqGixpEOY+&Pw6X&B2}f??0fjyRDm);;xo%yv#6+g#Mi_=;vq*nOl@U1X zG(?R|88v@@$tOQT=FOX<50Y$oN#)-zP5i%b;7&^v4F${G1Q zh#9#BOu-*L=(R)4OR!u8Pm3uBF613UDm7gKhz3)f6ujW0=8xGZ6WuV( zU4N}qz2GBw{JW^UxM|XRaS}E?Km{M#!Wl|fi2qy| zR2c~c>XG&euW4^IR~q4@L~nSCpjFS&2Qg%Em8*2Ixfb;-4jfk0hP&y`Jg)iU8~-$o z%q6Bt&ykrGtvUMR)m}IRyAPc>JMrd|953e#S=CFP?0KU(GxOdR@X^2rF!6BJ#H72A zPW|pP^5ZM4>S|CK9XlofI73rkP?^NLi{z{U-(T7f1r`&S95efYUQ=uxo(ZWH=8{ zMG={_=KG=!ClA43*RY8_s^QE}}VkYD3 z(;i;rq;V~ZSAZrb)G-m36-)lt4MhT=Ba{n_O%9A6v-3P6SgZ#A85P(Y7$Uo30h72` zUr>6+DWds$w3jG3Q%j~*+PGw_OVUMmuY|>J4=#rJ@}-l#nu>e9Z(zs)f47`tL=Rk!WI5=e3jJ zS=kp^tc-W5fJFD}&S4Q!{n8n-k{S=6-L5srT*yGC7#b1e!vov?wD@^+>;cLc)(3;r zGv-lZG|;x5Vh}(OfrXV6U-gY>R37DPjQNQz$`jzR*gf|m_~2*l^>Q4{LZvz_{7`N} zt+1H(A}S>x0@7AG$Xg!f&&u^$NA`)mQcZ|amHo$6FRs^@+*}$Lzm>tT%hX{Q&qXp7 zJbzTD+#n_q`~lsAv;hB6WjNENP~Yk=+;Rk@#qe}9wR^QHXk7Iuv^-#d`YVAZ>PNU| zIW%Ab_oJzA{Cp?0JzAqk{fhxURVP*Zp$woi}A8Yu??g=`Sw}^hMCE| zOOC4i>0T%VXh=~lxKd%aAE?p|?RVc@h{YLuZo5TWQ1kB1q@X_dnLOD1-Fw&aL=IW) z{K_pMXEw7klKlG^2F6@RsiAj->S^N97XyNjHK0eJv%$$mnU6u+;|{o{RK->=ju>%9nZg1p9$cROOYiR#cetNsNF z`Wn^rpIR5xTHMK(8=X#t=ACubEnb#}*%T&!0|bA%HRn-3gVwQal@<(oPi%(#(a3>w zS#ir&Kq=1;Smk49Oxf_C8>e@I9*8ZWlsFKXwX&pFg{?W}nJC02P2C_xOrPWa|VcbU(n2-+W?H z*%xz7Yl&6x2>?a_7hC@pwj#}#Z@z|;Hs1wy+-b}{kGparnvZCMsvY+0i#QX@ZJ}|% z5GhTToam2Zc_e)QX6j&I9;OsEmSjQiith%{@!D#Vqb~0Pzny%*UCm%lC1G0Zr#A%! zqPTwHqlxu!tpb(D|pml^H;qm5WD5c|Fw(Yms}liP|d zz9nFW}U9zuI=?0=cJ6?LJ7!%{FVEaPKvZDRG;9Acntz)11@b{1!3kw zF_mmXrDnEU`sIVL&qZQ0DsNzF-aYs)fpjeLntp!*LeAOP8&8a zI)lc+1RZiJnB?)5rIe4TLKgLbUXdsKm>YEnl%TOhQ0N2c`To1hve{ zk4t%s6?6ja89L1Sswe^ls@VO&)`wXwPhHy^J*H&b zgBF2}B7M;yFXvxKGR$WoHK$=fG%uFK7lo!dilMIOw4F4e`duwnM%rjW7qGWVg!DH@ z8EmdHN~5k9Te^w7!GyUjT#li`le!$9$o3qYdc zmFshp(MJr{3gbsnd8rg6d3Dpd=;j#Cf&EB(VwS!Z0HYRgeN#c$Ky_iFZNE8A>8Z;zm=^;h+7)G(w^a z5IQuC5&SR+rEe4{*G4%=z3;kl&dQpMRe^rC9%{-mM4?daiM~?vl@bs;irygttP(Rm zOf>FFz_5L=wOf?<{`2^PH9AqzoFo_id+*8GhM>~R_v0Hg^y z-u#|*NjjeY;u7X8j5xro3cRJk&?4!bfSIr+2Ub=X8H1&6etg`#aW+n7hY%m`3Y>+; zMn5+T>k<@wji8uJ&pQ#yWJYx93+pME%+T)>yrIRekfS}w&MF+v8bJ?E&hr)9tA!iN zG_JVoDP^KWwWSZ9y39RoaB+eT1R##S;gYJ2&X?X8_1-Njaov}fCR``8Q*1H88@PWT zBmGVK`lb|?1y+p$eYHx;6K%Hyh?6q8IuWl_ylyA38Mx-?>-+MsV=pH;yvgNB$qflq zG^M^~b>gz@qw>6?SBe!8%(K460c(XF>?_IL{J9nqt={^V`_fwQV&x*q2`{qOKf#QeVTVlCP z6i%{GjTA`Z7G{)9dtTr|+Y~gjaU?E;|HBOCqRePBk-K16N5IOGlWN}n2!dz0zt(ry zPC{Q4W2;JHMfhJ3T<&wKpSP>$OZsflPdTrS-ecxeUDK*V`lY?N)gglT8F1*F&3;%W zSt}tS@raWuO9-L@12MsRckA=Vowil}^Kx9yjO7idOxK%M2D+tBKTEz+q!phZkt*e{Z%s9Hl;6Wh*?WJ7)(|KLg{DXhW{X*C z_a6Z4qJJ-gDAH#+@(ahCTisLFhZV|`Nb>^jpXO_d>^4p*K?nj{@5j;LNqJ^i?ffb-ITr>PQQWHFgL`?k2$HQSh)skKeE6 zrTM2D;yehtz=C&|OZCmSg>Wl$UGOa{yxF6H-V7sbQL$KcEl6Mej)UQKY1_?g9fk0a zo{ps z9~hBX@e8%D70NTvKVIKUfoRrts@4piU!dz+1{*Hw)>6~07ju|r!}ve$rxbtDAJn#* zspr@;VK~a11wATcCGgwCXW5Tw-U=G^j~90K%-@&h8?){dmUHkq@w-30%6p6aCzdBnAR zh2WM)YX56p^0cBq{XW9aL4_#Z{}tstL(!YG6dtBMNO({NHx^Y?5!~)w<1Cr?_ci{z z-Ejs94h3HO$vmm9dEAKc$V@CfTO9=2>G;e#tWERD+|1gVr_ovwhrusI-4PgjX7ZZ! z-p^_}B#YoB{Z(W-K2kNEt-jRrZ<$L7cX=1&*z7-yJUS#_F1u{=VFo@txJu%J4|cm^ zE*cchdbP7fxtNjIk?1BcO2fdifq}w3*`OLh2^$xV`h)U-;r{-HW&QQnBnKK z++bUqafWcZEe*oQ4*BxHZSB3cp7CLs$DZLlnd-*cmlO6UVMK8S-@t8kjW`=gRGo?3P z#225a&di&oIGU;#Lug!bSw5R@R4_CPn@@Yd<{0xe%g$q3F55o2)}e7cS={{bgWcWQC3BRp}w2GdbRKyGq?L4X?9`t#SPxgd<{y+lzPFjskS>8yBy0U zXGfUDogq10d;KHq^9QLkCJhb2OrdHbX2r6f`hrLR_(AjSz7WAk-TuYlD+c=Lk10aQ z7V~)cyO0aX^YQqX0MD}~S_CQsX^OI@AAEX2`k6h2olP}zVlvJ;q~k~xumRDWc^%Hs zRxHZbXdOk(i*Zhl&-g4@E3L5l#iH}AaL+&?J^o8Aq_Yi2ju8Dl9t{+G)O!(J^i_>| zWEuFw3q^^yLiT1vyAO^>hKm`uSsOxS0SV~Pk@K^47R>CNQ!(0VtnnQ>gt{mF)Q9RG z5!(5dQ$5mkGfLK$GV__BjsQQ9RaYXP2 zcJ{B21GoIbmyRolpC_aSj*-K5x^)-@0lt1r%5ppAVXl?e8FMv4r0SGI!k@x+?(;K3 zn3VKS$h+I1Y|{t!uo*RPHYl|k>sx4~3JUN!n}Qx2HZ>VyzMVdDM|!mRi1fgG%Ax!` zAm|?Gannx*Yr`*dD<3yFB>RPQ{*o|8AL2U;_c#VC5gc;EI12Zc_Ql-FG~WE!%KZf1 zI)X1dz_^ag$Y;`O#6YjTy5QJzeLFXTE^kCf;hyM355lghh;-QA47L89%3e|D8D*5T zJ|N8xbM1q}_QlPmykpWT?pFx+33Y0VpR4rd&xW@BGB51aA&E$i)s_$}%7Jz{yvO|eq^emUV!qW)LMCQ^r0|7vq@2P!|=J%|UO zugGY}!i3>@J9~?{WTl!j2_4~`A>n)xhhnU!^#?UXaJ=6>3F*lfT0UCtsWsXz1NuzN z&MQ)yTspr~EM#WYFSX2P=qWBOSQe%opkjNYPZOWip;<3awAICQbm(_5%}l13uD1kB z=P*&>4J$xjp5wX*p{>z6q#%qt=KAXQkc~LPF{9w_^xj9~MUvmrg!IDyWO!8TCOJmV z7LWb#%|oB;hy1WS-eJpGx1ldDL9V!M_La7+lev6cn5`2*c_irhp^8}E)sB!%0q@Mv z_eE;Nj<;4Eg}u6K6z}|ioAWy3qNVQ1kv4-BN9sR>)L~}0cJ(HB&tCu+{dVi;BlFT= z!g7kKyP)@Sik@-d=RpaFNfOTJ>0)BXGerCrICc{O;X6)O=AtKN98`#f$`&(B8IoUv zi;pL8eP~lA*s2gB(&YpKuVZF0H6@r5Y4`y^f~&d9AZOdgOhjY>76H}9FIYc_mmA*s z6kT%P+;b|HRIfsio!qxNFR{G){c diff --git a/cla-frontend-contributor-console/src/ionic/assets/img/gray.png b/cla-frontend-contributor-console/src/ionic/assets/img/gray.png deleted file mode 100644 index 91945bc3e58bbed67e40f9ce7acdcdbe85a9b34e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41033 zcmXtgWmHsc+cq%3&_hdicXtfkA)+7x0|HXg4MVp`N~?5ABi-HI(%mui&>#1+zW2vo zv)2A`UdP#W?TOUZRKms}!$3elz*bR~*Fiu)jQj6FLw$V{&@MFc`h)1Aqa=$^K1OkX zfB;5Nk(bf+L_GG5Og<8cIoADs_rm@A(l48;3RnEi;6?g{s{xpX4ne2R^YVk+xb(}} z%kx?LOC#h9@R|4gva8z&2lMzC6j?Tz?OAd}jJ{}302+m>Ur6&+BaeT*r8pD1mxeUl zR+BbdUp{5(^&Zp)k`FQ3$2j#)W+Xp82<$DvR|9>3r<4sI>xCZcQeeOPo372(C(Fb^ zRDYP&kK$X^Y9aI+*UX--$+J7@gXEr%t55Svw}_jk1Ga4(6$eO|K1R&-(J9Pp^)Y8Z zMV4zSAYlD*nx_E^zisVn)Ky@_@i1_$7DS~{cZT!7W6;H6{~*sVzFprOhGQ23Z+&6q zC(yE`X@}92=;p;(>Q7@fjUG?yILp9+@khb&PVpD}`?(+1QCFu)>i7N!k0LiIW2t=+dL z9nI1f{#U|rw{{PAJ|K|o8{U?PE*PbmV@`SI4+jwmj7ff z21>gJKqp(ppRmL@AG1d9ss*9{8oJ4!*pJ|m{(mpT?fa?QQg4@jd?i?E{W@rEDj@cYTbvs`{F7f;M`@HS0NNMg^DebY zqoMGLZ?2-^EUA90-yd+NMx6xtcvMlkl?2&po`)<4YQYO0_Z?_5D{KAF>3u8iUd1%r zE-dn#Qofpm^Ew;N;{tR}Qa+N!^Y63_*2dK|`zFBJo`0tWG zoM1j}-Z@=yXGr@>w_9>T`~slkiiP9KD8CHGSl`Y*5el71ac0=ulIB#F$NBYr7k>vm zSr?Cbpc{L$7S1gRZu9JK-*0v8?x(z_>7qARU0lhpfZ?&GcKlzXDuB}zs-kj$R51vS~ii>XR-)sN}JYfz+ zUstv{b!r!+bUAV2*@NdlTQB>!UBctslwpg$Eq};k+Uvt4=iUd~-6zT= ztru(@SyMv;p!14t&vPr3YpsCx3-qTXx0Z)XnCDBYcvZhQG@Ua1<>^d1=jl@A-HG}$ z=C5}?eyUgLD`EnRv=_F!nQnL0PT^{INp4tgJl_~M0xrmvrIoWp{rXmKWPCnt>7734 z-8P-YLDkyeQhs+vJ?HIEY{=umLGCc(6k@|BH{*$heJpaaTmN55efxh2JTCr_6SaQ7 zuibtYlkL?p0FO(vl*g)I%7tY`PEeeEr+;%OGe-HXb(CMa6J*HJNQ?Wn26lI}MrHRC z{+Q+=hSf*eAs$2BdpG0;-I*v^dj1`?_{@#pW%~Ig-L{s};|X-q;o)~f-5>*LFcGd+ zqgn_TR<>|OU*+jpsXps?^+3&PJ)el>m-Us??|sv;H#J36qGFsQADH9&nLU>`w)gkU zcN!q~6{kZuiBSoM9>_kn3udjRy&$ZcfGbwam1pd{LwnGVKYnJWK&g|{@N>=l_J)FY zZG?nnt|m@*4JY3`qjP;4vb$1p zUwA!n0@VjZVZ&m6vM4j)t89M)*jy&xm;R z&w&0%NO9Pt(<8Sr;CMbIR=R0tTpFCkPfgbjjZ4go#iv;L#@$&zU!(B|3w@1&QqZ4g z=WWhFW0!!AIoJVW3Peh|{Eo2YCHuXmcF=q&{Df;~Q5ANQs|j?Zm)k^0{9=9xZ_B}A z>vT@`-JLpQ`g+`Pgo8`c_ls&v&fM^y{`j8u^A+=5J@JcL$z5CRu~y4C^xSMSu#+mT z%{;zB5`3Qk|4u?pw53`PUpWf*GnaBLRg(h#cSwSJ1@GO>9ptat+0h00?cQ3wjTlJe0brEa*)G`*WT%u+P;0xaC;6^k-jnZJvNz#vP?ltJA;?0SmtZe8dAS7L9Ypa zAkOD!K7Xe$Sf;)3He2h1SG#pI*qf&eaLpAZo~>JmSGEy!=>Ihqf>pj11B+ee10zL6 z)fW`cKW3ifRurDzvrb;tZ%pWi4~%E}&Tb6%8FB6v9(p>Ql3!upx_Sm&Z9v@PQvY^^ z*kNT}H=Z6{i#tBE`xtvYu~E6@)j~(VO=Y2by2?ozzr?eLWlK3%+B)pDH;?b7P%sok zqE{H5^RH<7nrKFHK@`&I5L4=*nH!FFx+KYK!sl!ISmd!k{nr8=8aJ-U2LcPP5fASq z1DGCjCJKa=_SV3eVC=2naS%fORvqEq+QuZqyYy?vd!s^ zwLgA0ZBmv8S9!f91HWtUz7$`VI^+82MMYOJa(WM0MSNuGTzX2oFNh5k1s^Z$>hok< z^mzUtwz!l?)xkK+ycQFl1p2*|a?Be(y>hji-n5`M{DQpI6mR7G@#oV1Atk)^p$6`d z^OG6A`_B1?`|%j^^o#q;;7b)(Y*qW!Unjrg%b|Xud#}T_Pk1M@{S`-BKnYkHdAWtY z( zpDAvAOX%P{(8R9$+FB;J|C7}0hAryKGK&5|&w5Pwcsp zCpyaG1ZXs8@p3oy|K{+V=zDoQ{*2tx?e8bOJL@qbe(3de%Ix^l5=*#lN5m(knOWwdUyu50eLkgWNe_ zfQ-h1;OwmA3wsyNt~!g;L`*;R_Aeje-|u{PZ>9{|d=%CspT?hNXH(W<{;s{dU+WS( zpDAMuaL#9H5EW*LfiR7Q-~*F#m;=fVKHX;+4RQOg`+M10p4}=Pt}CtxW7?0_p9VeO zg4Yi`VCW*rm6NFmK4BHus)aEX%bJuRZi{1I>m4~Mrtp3djt19xoHPxkP&&MV8aZqT zSc>-;eGrqli&GN^Sp8#azHHR(zAbVEPLBRelwvUj)?!o zcBUb$LT0)7huBUqqjw|5yDBrt?JI7@340bSVFPk@2H##syOKhJgmErO%i6t|7ZH(S zC0-U>BGhh#mYW0nQnx?z2_EZyLfYg;RE5^LdPTs7jFnI&pO^aaZiQXi?>;rOxHftjpNF>O#_GQ^yY z!#cAD8-(=`aP`BzWAwt;_CE=PIWX-aRAIb?q^Khe|L*_z85ivhcegQ*+{YG1aL{fIwvkl?3YbTb4BB-}P;O)b{wQ50b7| zES-tsk(F1!it@IpQU3+}9M92M?B`$yUF@*KFV0t<0%{FwGNQgNdse7lLAHLoQ%VGY zq$2rO;OUM6s&lcasBa+Xd8v4;M)>A0-wmg%y0;gA*CaljE%;wZ3;Ia~CCS{=x;FZ#uNgYwrpQr;>)N>$88$Ma{Q0z1T|r?(#n(8CH;z z%DfS^TuX0=*uHR@ov%_m>`2sb{!Mwlf5!5DH;Vhs9%RK^%758XYyX8b!mBOaktf(v znW+;@Y@#Jh8bV&7K_-g+5$PZfkChS>^ietXp;h->d@YXJ)3EhwY;E+EQlts+jBsG% zxq1F>JfxMZ{P~;(3Ib|qO8(~O9fn4EmX#5nhe)szDMFB*#=OAf&$dSg)VUDD`HHfG z`MGInACTb@Adfs92!GtLi6~WI=w4}T@opNV`JY99wP2Vp5NxVb|8A03tf+G7zAoHG z3yU4~H)Dha!jqC7hs0K&8J~&}7u!Q?&%prIhmB!##5BZ#un8~kH?9jeCUuwO%z{g| z)FrVW4(*kUGJ0QN+#=s^H|R6k_}`QsAw^AZrF2E1HVqU|oO{EX3tB%v!O77m=svx0 zr@;rC{5tNH@?nxKzaR%>8>Thds_iJTE#E!XW7z{mRH=nXNY^}D0Wg$jAIPRRxBb-= zWdq#It!gD-P4e^ZGe#6u%axSi25{?FT1kYt=RXXFQ$1Xyf~N8Xx+h^F3cchp#tobH z>cNUYV4EGVh`LR!GGXoGXt=|TK(y$ALj!HXl8hAnz_dQaKE>OjdOd zjSGW~SN>>k=|Y6(DGsB1L(jJ{66VUUSR#3Z zi*5&b`ZTah9Ln0x{M2*?cU`wWcX7b@?mM~v#z3+TvUK#1NlIv(x#(fkdj8%Y(-xMu zBBi`Y2PAHJd=%;4a}O(AQUI9}kCKaJTKu%6pi`Jea&~z>*q*pO$Gy{XIBJCh#2x)h zlYjQU5EDzn2z+4L##9W7mR%;9)R_aLjuUr@kI@4m83%tEPe{WfsIx69o+TNuVFVrX zzT?~!aM_Z6Da>w}u+aYdmBZKt#9KZ6D;}<0GQ#9o>D&_Pg)o!FuD?DPk3#C$D;#e8 zj4rw!!Zm}qKRLgD#02Ib9@kn{7m{X@et8EN{|OlX1(+E{T-A8UCwiJPD8Do8hMD%%#a zo!?AUe_|mt71!E02Ncj5m#`(lVxoD*lRG=*^l|#^ftTr!+4Ft-aEYL9t-N5+fmW%VE#3op<@mDABl6Uq7Cc1ZMO5vnzH4_ z#DjRtwxeto6c29jj8VF=Q3=$tUh!Ii7L_z%4FDRP;nNHAPxVrM=Uy1L_p@$K2M+Vk zn=9?1p&)M0ABCxEJ+<<{tcN28r}Yjibu0aV-8aud2^)yAN@*f0JX!n_d7PS++4ifa z7PGOx&pLwl5w0yByoH-Q*11mu9_lMi0PBdsAX0P+jL4Ru@VAJk&q7FjA5T%^SHXr7 z!%0!`qC?IVU4FR3Q_mljsTB9O&0ds!gD7B*{6FUuH{RHzx-vZ@wP=TC!)cm+}($+>52(vu> z$_yIzT2SFq3BfxCWeV5d4-r!a>|1&X3H5S}D?7Bk%p1%gtO4EKe^LGTW9ew8jLK>H z{=;d9B|^z*!oHwUSnldt=f>P}=<9&V7nuB-?GPlsz~0i2lgo&qR4}roYv!*h;Fnt5 zI*w%kEP{barz3jKgyqPA#8c|F%Ba6>-hL>@W$Z{FPxND9Q^(kk+YujdoP*AKUAh4r zR%e-4LYV*Aa~(F;R2Y1ES+>V|I`cn?J-vD(?n52{qVoK~9H-X9kd2P;=Z>B6Z93Q+ zcXxUUcTenMDWbvCg+BzuUq(ynnxpW~iS-;Kljuqs#SZ%CWyWtgs?{8L8arETm$wF9 zBsd(9K>`Uti&PL=@l@m(W~`g+o#T-Gl*4^ZDb~v&JzXB2OSl#Wa>PvvX??%qnDVdj z1K*MmPGrn4>9@lE28tU-UVEB<AI7dle&n)u9WnP=5rY0(uFZf@7(rH;c|mo0r+2OJpbeih*Q#zCymbcG%~UGoybKc z4agxq$`V(J&f}>o)8>kysGz5&8J-CX6Gm4Yi82eS2-lGmLI!hU8dAW(d(`vkC2X-7u)XuGADaLW$G$PQygjrm~I#I59Ae%a{befD)eDn zZc>JFGVdY;2++F~6j%sx0SC5xs&VXGoJ$jS+UZ+VY6feaTBjX*(%=1_-u{#R;BWEU zxAl_!*}=4v-sXsRR#^#zFQ9nYq$Udl2R$^&b{57FRiOG9ck+rmZ?~V9Q#Ptt*zxR; zSBC2(X6cQ%BAuZ8!*Bz5j$fjbKG;44Y52)S)vwOi3;B0{(c?4)-D|yvn4AjZdPaPe zR3?kWNeC5x$Sx#NTki>I`lc433f^=}>6q8e)$C>O4rY5-`z<`8V2q7P)i$0fp6M=; zk5yB3)0d%P@l5$l^j59yAiOOCQq9!B3lTVcbaB1SOP(t4vdh zDfj3H)llgQRadxx9#+XIQZ}|;gwb1E|4FTEvr)4L+_P`3Bv!+ zgPIVCA;nxNz$TW0F7I=hL84@V1;^ZXGR{vu`6T#_6gPZ0{z5J5!*C}bk4d>*`gu2- zB1p?%p^&Wn{foA07FA9Ez+mw9!RzQpsi#EhY6u8HDE!J&mr3Ka^40AY7ZGs6t^%8N zU4?k@ij|6UL{OeRt_He@!%AW|A(Adi9;E(&z`X(Z$0@c#oCJawD01O1P1z%5KnYV4 zINRa?tj!W9exa6WEF9J#2q4dPS@-4==;k91d(Z#z*pGwSLuB_-PlVI$%7?BX@1I2tm58<6(UbqRt=6iRQE%m)0L(d7&plTe>ZDy7rPCyy4Ayaz} zMftMkg%>@DIa>KW)KpNtE|u*cOV(u9Rz*tYm39-n+3%Q3(psZG49;}}S?2QyxLQI1 z#oTXn61}5vX(mq*X=D2~)~+i-2>vQ(B;) zZ>d6lHEIt0NZ=L0lNyEhaBwF*PMPh(@jMKjHb%Xyj;Qe=4H4tjHZNoj-*acLuqYNhgm%e+=CL+pc%58~t5((X zb4=qGQ2LiBAiY!6hNFZKd_xUsKwvV$Y8IhKMIE2VP!F&;72r4xjrX^;lhf8Q31+JB znnkORRJ;s><@Q0RQZBHU4nIk4$N;c2%#SSnRfRI4#=b*fYO|r&Dh$ebR~X(Kv87W& zA*l>EC-J#Q+!qzKlp})Rg9}uO=c<)=vST8=%Ukry(+UNz>O_+!MkUOZSWntB$r1PpFZ}eDy-62F4o)eT+{Bt3J zManh#N8o^%d_|mgW$KHX%w5BbT*R~A-yx#4%tjTdDOEd#r6<~O<`PpKUYOL&kvhfk z3}-w%m z+9Rgg`^55rKa560qsJ5vE+Zo3kO= zF}G8)d zhUFT;Ns!68FF)R572TS>+b)f?I&e=zpBkOl-+Y}^I+NSXrItPV}WO&DPO6}ASUpakAR7jeN&N>Ljg5SKz zEL7ah&u!`5zWoRf--l^P`Nc61A@0b!6w{Lal88%<{ZU}qbcR0jdri^MU-@cB72$yo z2_2)Q8i&HuDVQ}pQrU>0ml$AGnVqJS$Ab7V{MJh_u70|fooBA}ButCuRC4l0UyJ0d z?(vz@b2^d|C$25_D#;b1p5mI(&NM3zVD#mk41W6eM<9|c-k%(iWgui0kTdpCpXeL% zO4qIO&INbPvI))GwDC%n;2JJFIWU$4fn2`otiv#L6*9t5WH2VFiW2Oyly*_>yMXh! zC;nomSXysTKJbR4((+U#u?6UptiM058`I<8vO|vq?e$-Jd+nj}rIAKtVU*&}g77{*Q@nropKj*R& zPI0pff!^5r9|d^s($G@kICM3TKy1>0#Ll~0Aq>&a3G_rG{oJJ(3P^Y&J&a&dinn3F z>;f`VfF|G@qjxaIdx&4efHr<=Beda*r_`51`OxvdS<)}^cN5qRo*b3U3l|?V2U6~D zd=Gr++XE@2mG|+&);v5vLC+&UD>Ho;&tlYhzdBTP7?#d#GioZO&2DA}^sXJY*FSsI z6$0ArbqK-)X3-9Qq0We@l=5Tfq8`rDX{d4q$PHD-mEmarU7&r?d{CcX*Q8nweAf1w z*p=I+ZcBe=@ZYe1FHZN`u9&^h1Xi&6zGqy7?{=BJr&^{vUb#M0`cjZ8Vmi!;?J%Xj*k6YKCLry!zC7$2zxXX4Xv4dLorIYl;!RsZ-?e@(e zh7zU1MXqb1e&tzXxELupyS5{;jSM|mIXY+LX9@WNtZMvk$eWC4$O+&yd>|_pURzE1 zTNckxbMfsq0{F5R0rV2+_@ju7*qzmzHF3vP3CFbSl#XHMeJUU>$09@qt7ntKyf8_SN*I?xxr5l8RozH8=V zEXVnSV5RyvB%&KaCqtjgh-rv&(L6tuIP>3e2?nQC`X3V1c&@8DoI>BPosZ!dUOlc_ zySbPEGa5FUflBRoy9fuk(4C9<(*>E&8PWg#2q%Jx7dY@Eras!+_wkBi4|zKE(ZpIk|?7P2*s)JN&hiY`Evbbbjln3(@Dw_P8>{ zxz_9|22QX}y6AabSqrD_0`!E_Zbb9>eY7cPd_~}kI~7z^48QqW{t(k^gk$V8#-5@> z)F@bKR#9ebutSkvCrY%fo3wP(EcSHAv`qMDa<3YW3_x5~k+AtZNcK&2+WTsP7DTi#D>MdyAqL}<70Dkq zg|CDWK_`dJOB2FGM2YGdk@^u@`wmW1Y=oSIV4iNZZF0rL#6vNhpPkTf^dMJelH^T; zU+3-7YE-Rrli?#VE;d<2`s>}@805Fn74t&W*VVeDHK~^+AufTN7cAs9qepCh(>SnL zkx=SRDAUn|1y_eLTzlAg9?*(w+TEomYP1*!Gv_py@9TQRMWmC2x-gf@v;rM_Q`~2Y zh%hk!h!c}5G7lNyWieqNYGTbRf#O@a>JD_O81(DCpxLu6lSaN{I1<+fpj|CHN2nGT z#epolVYoE-erlb(Z;g-O=1(K>ENZA_^~P=7sAz9^dAQi5;b^x{S3Z8r>%5VK04vh$ z?Jl1{Tov_byyQ$Q4&Hz*aZY(2x_o+X-aJKa@Wg~Id!9-#*~i(R<+*>pjNDNX35!pN z$1czj5#SPeJcm%iEBlW&i2NQN!{0p0%ZP)SAhIk@|3Z32LY0EoNp*DfK}2Pr3#=yr zG@hQ9GMhUw6^ue0(4^^nY;+S^dOm52aJSUNlVdU;=7t>N)t(+k3`|la?|AtT;+_R@ z#KcBKTF|ZL_uRdJOG&of=dte5nFL*Rux{d=DA6ZL2CjZDY|s0_V%cb8{);3dlN*PO zm&w)!zmu0o&h>s5_@}-yFcTOLN{a(WtS@o$KNsEyh!%&fWL_8y?#N= z`RyI`%2xv0-H{9%R*yiSrrM2)%5`=@xIw6n@+dHWLKrQBULn*?dy?%-`fGoZ-maSN zIEmT%Nj_4rQ)D=8cqH)q(+I_2TEIVAB&_4JunGBGO@{I3vG&+&fwIYiQL5Z5qU=*q z8wI@gd$-&+L1+9pc=OI}NJ`j)UKTvwJWq+DUxq#B_FDP5kTEBy*BN14QXyz z478L&(Q9pQcs{oKu?9ZmejB~%7sjov>@|#;$Qku*((1L<$Fa}9<230~;D+kfL?mqQ z?R1#gi~f#xprD)h@p!OO#4A6OWZ1Q+c+F$0rfmTKGu{LNE2tsrEm@3VRwrXXf<`{l z<`aVT90!LkLa()cABBE`^?exki)eGQo}R1FuYV1t30J>+y4Epq2WTyZ$wIlGvp&1} z{^>)xxKcodLL9OAB?5{NXId48C2-#nCnT%_yMu_lH$Ks26R&v>UNZd1x-{vzdShU3 zZ%MA#;TKvaw4bh+e)&B_ZhgyDDYWkpvDk?;Jp2iS+1D!FdhsoMlK*D?)82Ek|6(Ri zoFBnP^F}*0>46~wSv7hn{z_#>PdW$-(g{J8Fn<)!e68MAL)0vouso^k(}15<{~}m! z)#DkeodifNsNSn&2}V@Dk=?cPi*O}HZ*ibz&gxq#B9ENI9g4bQ4UZsSV2rYMzTP$IBSMhPO<BuWiwp@m;E%E^C{Dz0>L9hc|l0R9Q+Bjq)WFluud0+ilGm-1$12U&RY zClRNQIQDH2g)1K(Af$W;lIr3VgW`eM(iDEP-TOm!@V>MPgBSMs^4ERi8`BGmf&B05 zsK}Xd7gR(BVc%4XJNMwJ98riER3Zdi&i9)@2~hnAfHyzwE)QUn^^S6N(ng>0cOq>Z zGW{UK{#-0IorJM4mfmz_C=+IjnC}%X;`|IhqT3nem2Amx_W>`Z@so|V2>1|iztC>D zWWF4zyoL@Z1W64xCdS(nNLE{9TND*l+Lgoj4HoagceuT25^shE$SxX&s*}ACrV#{j zU}ze(QV81c7cujStjnV-%>J$`RjF}eEK{;&+pv+v zc#Wu})NL+`@G5~1S^e<8=cz%LOn-_vc*T#l41)B=GljDxD%!rPNt=IPx&97{JLS2P zms1&*I&yAdD^~rNPa|k`oPKu+#1q|kSkT@50rav8M$TyH7qM6mO)UD-QH@jjA#QaM zOlKxzUOZuJnm&T=SgAIUXF~dJm{-G0?uPR?cVPQf3nshXs&s(|9vsl+4gGd4Q~!>w ztBDWLrIv8wTi$KIu1WiP0CF2uRK-Bh<}ASZwT@ld`i*N|R0LaYLjvi?R@NF8eLHjc zd}-Ph0IWd#G zl4i7z%e%x+53(&UWWdLAE9pw^PN-oh?Q1x?#Yq&OpgyuVkIcGr$mtIEIY-P){Ob+I zaa2AcO+`)_3130CQUf3{T;{N~R8Z-|X!^rN6QV=WYxSZqGUh&-rxzI9DzG0f<9YN7 z(ItaLA{bH;fXyM>DC}h=ae`Gcq9IW-7HwiQ1^+VA>%c}@)?A&kl3dcKJU4CZra&Ai zyo2%X&MnQ%Ho%3R{(b&*(Q(_`4=d}hGm@`qcn>Bx&h~EvI75MNM^Pd#&I1LRa@r+^ zfD)nq^bqH^6xJL?=&EFGCA+F!Pm7ulyM+Y4awLoUt>N{%qfa8TiIa0V1+}i6cSMhf zF;70O&JIPWK2XRXx^sP??@i#rXY&gTO@9j9my%fZ__2dDM8q5Fnd3HrA6mC#D3%(rj8_A~L|_jwNMqhEBZF zu%VBzpCA-D!@wP9IZg-=Jd^E^z(-sg70*0}Lvv4YJfIe%pEQfAi9WqKaNc)D2_N|1 zZ4`W|-78osbUkv0@!)V6kcr@ft^cLRPbT=6C7PAmo`23P6`Tjhxt(W$h-Z5eipQVC z&+S+(AL&sD4|D*Pca~2ShGx(ea?!p3vw@0b*NC`&oP)Cx z#cQ^c&;7EWu-b&I;gR5#JYMQhBtor`I?IK7Vy#O_Ka)F2WFI?Du-ELe8u*~Q@XXTj zq1CHo65vg z6NC#WdE)WXBH&PG^F~)cmlvrOh4iQtToC+yPT#HtjX{=LZRh?zV)xVqf&?U+n5a)w z&|7NM?dPb?7?lU*LdU>NgpzK8TzHk@)Z)|FBhWWo9@a zH$t%wzmpw=2bKy3 zOV2N>gkk0(p~1R{Da{(K)({xnOB&pi59RzHs3QSt4;xX*AjSOx2jJ{a*Q<(#Zdrx4s@D~x4e+X>k_t`{ z?0Szz-{rW`W{9Wq%uUNy9stcDqMNYQ-Ue-D!9qL)_kjp5I@pIW8hK{(%3f;759oGylX~Q?#Q(B&l;is*jL9kiL>u_zuejm z6shfxKyh6DCe$(pk6tz6-0drQz$QhuFAIo7{2Vm;p=`}4HTg1Mn$j^~G?Z<-;b;TO zIEw#zWF`g>m~?4D@8%jvXKhgH92rxrd_XS#NJfKIT78M`02CvgmXztiEne z84J~WoI^QGTlX?3&B2@x3B@2y+QCG4@r6|qepO~45(J3r%Gk2~IvlM_uo&4Jhxg@L zVgUEt(IG}p4__uV_aSu_y_*!*04$qA$(N_{DYry!X3Son(`Ens`HSbCEAuT5bQk9} z=S!UdGOh2CG_aT$t+c$Uud84N`|qdI0twNw7v)j1WztffHoW+E5i>#Yh>`16kwbh+ zwi0io9@iJhMtX>QOf(wcdzOb_+tEr_^;R{mQ9Vrq80LWb==%ne9CU0C?#FS5t_-Vi zK?)mv@Vu8>;rZz6n7@gV=f&leU>Yc0yVIRrzXbd6P0`4nnlPxggWoqy0w80hQ-`js zW=l1p5$g3-#}FQmov^8(`Ble}@N>-h1X(Z|Zn_QF6y_DKJlRYUvdg((Z|n8myvM9*p6|=zTm(mSmuaPAo+!0cU=x6q!)YVn;M7UdTiq+ed|0AovOA+6J#cm|lUn z1O2}Ojqe)KfM~}8HQen@&q5L+J2k^$!Ut}~!oP?qm;YgeDJmRhn~W%ozxD9)6zuxN z@6QrQd^XS_zYa5}|D6Vm?9mj=LKkU#i3ZpO*dV7~3)j%fhQt5RID5a}h#4SaQn6qr z{u5JytJK#S^16@GnFIek_=S5CHf5Tfu}QQKjJf(|(O%K7K#n8y=A*`KjtE#Rv#nv8 zwxIWvznwT1i2wC8ASNr`m4l|p>j-?IP=kTx-jT4JFPn^JhNC?XVq7kfz`DU}@T^ve zUNqq*Y%HRuq*x`T6MA<&JJ2v8CzK>XLyDZB|0cpTIY%Gd6LTLBW=vbwgEGg&cGEUv4!O-(gmm|ZYN7CMlLE8@tvM=Q?#+RboNwBCJv-9A$OpJ{n z9l#qnQ?r?{M^_jHEzb1}wbuC;nPl88BdIm}6<_jVTtU|7Bmo0m+dgXdy^>^-#L^n} z_0*U0zbaBD5mAZ=Kch-Vo~;^QrBempyuml*PK_fL<00#CN2Uz^_1R7!*D_qOErK}X zEv~1ygjQj9qXF;yLyzttXvG+KvH*r+0*c+(yHy0&X_S+y$0&+8^@HVlnpK$)1tKag zNDct};m#L6g~c=9f0NX!GxK)}0Fdy@5g?+y(mk zO&n<#Z4%7!smMT0^S@Tt?NaM=ERV6YVZ6S4RelncPx(i z^QE`NQibw=Sx*;#cp#kWzcg0Q1_JWxDWx_e9Xlf=@*|*fq-y}1=^{*&|v45FIB4igcQE^)JqF~MwEXG$p zz~0|0lT#AM)|q zL;x0GpRUp7={(29HE(*;GxW})_!<&=ENDWsS2g9I-ghlX_^#P(f1G&Qsm&je|`E zdSb1$P{<|x^Z~r0twv_bnHPYVR{>uC3{JEEhP|O)#zQ9{u|{C|!7{u$@Q7615L*l> zfabkUt5$C)N{PrkINk`aSZz7}n@U=M)OsZ*-Zx_ccEO|woOm-bygf=~MRaYa|15QO z+l}tTOX-p_z{mNpZN$#*dXgAKK_k$2Mbc<>UNaE*525ZSGOyPai!L*j!4%r8!XfRC zSz1u1fMaJi-uV)zT&C#?%ED3OCOumOzB01y0|xF^WoR|4&n|#u60u+*Rv0nNxMBaNZey z9sqXq;XXt_|CPCKa6}oej!}5x){x${O%Q}(rDDI*VTmKyMmf)ZkRXQmcJdO;F@@k} z>SQ-kSNT~S(>_cmQ*d1!f8~ADc4ZdK6YokPx6Mka+#2w(=e}{CUd^OTz3PS1J_FxD6Pg2NMaTEJTuYndyd7PL%B3mrfy?z07{_Q>U3zsY8{= zl*wtfiT{em$4DP~iS7Fjr^6^(+6ny)6^*$hRN3&6v^fV9t&8;m?7Q`!{eIJ>jXoA$ z%=+pOM=bjVcYH%Ec%!TNNACdr+AD_jVrKMOG*(b?k)k!jDfVCNkNYW0b5QvseBr{+ zISgRSYsSppD!KO>Lq%oxsbIr|uhO!06AG zZI1|ummBn(o6%WPy<=4Sax=2p3pdg4+-X7+Z@T`72uWO%U1F_}*T|L^gXa2>TeRo_ zNwvxZ0^tH+Y;E0KDNIPTm3IU}s}uHbgu1FX$Dt0ywY-fC7@f&1Ror(RSfSP?5n4E_ z9ueCgnFgMNz7Mj-Pp)k^1EtKPOus*0CIUi)K6`%eVeWQb`w+6xz26Z=Ac%I)g^fyL zg9I@WOc3udd}kdoC?XJ{_ln^f(aQ3R|Aj(d2iVjqC#~srASO>D8VdyGQkSA-u#%9o zq>YMZ42hUyEOa4Mra;iVy2yV`U4+Pr^{nM~v(YW%dQo{F0d%M~Icl|NLIY_D@m+T$ zHI;^mvDa+>#?Q3tp73FjK(rGC$*ylJ4e+*!?jjz}tj}a20S05@=UMoyz4hzt0=sO$ zU+R6?G7U+@&ShG1Nt<_T=ry~6h#fj?>V}jQ@f;ZHfLgyNlE&Tc) zlUyTZD+`?q^7Hs?X{w#o*a1RJPyHr7riWFiznospMEQS+*1Zd*YXDl(V3Ue+&xW(p zI~alptdS{}2CewFk{a22EroZzMtGoiJf?%^ZjL5-Ml?fiJA~u@ESs3~hEa{Hk%EKS zsUmq9m@Fgt<7l@C1yg2P^CU}=3TK?QG z_m*f}j)A*#%>c zbuzvscu4o22%W(-TS$db<5EBw2gw{MU;S1CsRz5d5I)i%$qDuCFXXhadz6XtJwQYf ze>B37&!X@OW+0_ z70t}rBq6VpW7g;OgD7z>>xv~?cJEAH@fu++g7B)}|B{0Uv*OZMg^-C)?x-NOk?p9u z-;Ip!9XFRAjz3?9pTv+Fwj`U4N~b?v0Fd1QRCrgZJU)Qiee~PneZowsFA-vSAG;lC zYgO4$WR(Le;uKM9vdwOT-a7dxK8D&p$@X?$LZETIQbyJlx(;W6nBd5-JGEGzT^gCV zg=eUXO7vRWOOhP6h;)O7o0PSA@jN{Nad8-&z*oHt=A$D$mxrr?QxW!rD##9*Ca0la zmd8Rdn-%U1wcbk38!aBAQuB>{dcE18v_rqiO8${I2NJHl50%4un9;`` zt68I~<`iThOAoT;fM{(I`9m?MN+BlKluAzAOCk(ClnNu#KTx$y}C=`XMT zNQN_yN=P~_&oUd7hbSx2E=c^uoI)IN>4A<$u)e(eUT%fKJAM57Qlq0Oa0g|_a7|0s zw?lT#JD@`{Tghi3GS1lTY{(4R+wO+mh9-Pwtm%JP)KRayojd1Ekst$Z}0|K#Bk(RtmZ=_-NORbQlcp!xRm5V-!_$(Aav>0xn`NSez@tk`tO_rlU*fGj3b9 z1XKQfi88$!>-e|~9gQO;VNEK_d8AtoX#OtmV~-z~UK=_IsSt0Z`NEZw3lhnMmP_SJ zR0J6ehS@qYr?;ZG(U7g{BF)zH*seicFEVu2eOWdEY60TMaIUtRfnU-L=p^JKhpEI8 z{qon-RwGi{|0`+9;1m9H z2^;<;jANQ26AeaDMI8h3obuoAL?!t7$2cRP!YzPXF>4c8m1- z`nQT{uXiFvMDn}{FXg$)RV%2T^0i~B+)fBdl|;gNWfn_iqYK3mPprK(?Sx1N5vKY5 z#pqn5o)r@_Z3|9OW5}07=-X$JtO*$+yjV(j`YL5e9UwxGn8{V-qVq~!T+vn^5+Q$SvYns{VzrOvwhTTvPA3fu7^*ZXK}I_}kWK154GMHRLo+#y zVlVi=ymZ}4*LlP4)TW3@p06zoo4VnKSQI^%D2@-5m??_oWhg~k41Lv$;){1)kC)H3 z*68;%x%EAFNhQdyPZ`y(MJsw6%9649C!gDu^qBx6HHEaw%lb=MS4o#uqUh)blcq_3 zk38iS6dD$M5LDDk0dURWky6SJ_N*8OQAoKu5u&`{Mrm*ZeGv3!$mCG_k1i9CY<@*}g7t(L z7l#1H&y9$4xg&0__yc*jZ7Vc%Wh;HmF_v)qs;nf!H5##EHfhyaSJ=JDy`6oWU8g!I zaM-N;Ty0d?yj!-j_}B;ogh|R^HvHqUFM(N`#&nmmE2>gt45x^$7)#b)tP{wqKSKZM zhPW+afYPR4e0V>30Uel6o9hUtx52%K7Xc)j_7PIsMye1(X3QM0OSxlMo?zEE_z_JM zTtTwy_7>+tU>wOL{+4%kLK_-1I#o;1HNTXX;tV+RhE^vsI)H{1#;$ETzu*L%wl0LN zgxL`!+}~K=Br8T$?TRGk;GHUd_1oUrJSgmOKH(S3P}Ko*aqMlZ@XGiMBCYx<>dgO7 z2sB5LHG&|Sw4}=o7TGKXnK` zkxm{B%qL1P&NmII7u!LF|amxx41WXksTzO0vWaK zzo#4K+&bi6N^_U@C3v$5K`fkvj?vbVEY} zJv|Dl9IjH5r3}cNbmS+%!`-xQqhdfG3%7}(R6`q{Y$Dmn;a`fqzC$%iOa%M+{~>bt z0U_y~>(GV@VX3o{7-E1c4|zFWx}OPc zX%mGD_w-=P;nSwRhYrAQWN~@MHvN3|z$dw`r^_z!WIdDNbcT}BfoX5n+~nM5uRLU^ zXFcw)jW<;7#OXC%;%sn2wh(QU7tQc^R1kqI#%MDWshkS+xi6FJ*GYJFQi6Cpws*HRt!h9c zoT5-67LEcg5w(i7mdp}YQ;i=1CGtv{f^Gj$V^}x5IccP#vs-Eh9^Xqk6i<$ZSg$%U zB(7?kQKa=(lIgDJ&gj*g!c(MW zhN9$BY&T2bDiMkaT8UTCv8u`%@~P75@2X%KN9uN$AGkE0i5Lxcs*=Ur*C024xwD9| zd2#*-2M%Jlrd5s zq~}t)R&5U9piJQpzvSgNRVWVJx2qozSZp>wHkta)@0T8A8~#YvT{*a(V~ z8DSJgLibz$?osN8o8|+ym_jPL&b5`;M^uFtro`iNoJC*SfBw;cUzYhK>och#Y9nQ7(B>z*Q)yh2^d-m;dX&+2^E@V1A|Pzw+j5G< z-F3kJ^Q8hBttw%WEM7O9b)E*@gvrlN;#(vEIb1)5Fg?IY*p&VI0hUGwqrHj{ACmWL z6#15|?_MnONaL)&zO+mXn2HviAQeW@8+8+9{?0Im+9*c^zJO5)CRRLbvradBpj$>r)!KIf^jzqz{gH*Aj;{hUT4R z4^`Lw{yOP6v@;Y|riC~v>1{>AAamb{fG*qq4IBQ-4i2MtIQX52oK{UjIo18q&f1R0 z<-(@WG@{R;Noi$bP4F;dNNkhB*?KCadxWs28kWh`+1ytLU$WTfsFvJ3P z0%R60qxdphCBz@dVD{X%i)1EQ25Z0I@Ye>cTQ}YKzoX2F?ji!WdY(P^)kl0O(R}aC z>sJ6!<-`8%NS}&GI3%^d1};Fk5Ad@&@f_o$!|HE4fGXir@_p4AhzwJ?zJb~K^0pLZ zS&hG;-sNINE%(8vLgcgTw=s~HID}i?Bd~+^c;(&0s*g4ZN)}fBUBBP_AuCj=`F@FF z8^^iKE5U8l`=Z9N<5gTXAG6Okl#=R1puO;9w$CD^D^lbar=D;av|`K;C(BC#K9jJj z-?w05kMI*Ej>TOlM7vG%6B5hF)LLHIE(a_beg{-lyMI>i(hLeU57+9ltaR4}qARg3 za~ywW86AhAzab0e8uuciK~jaWA5{!E#_Ss4mBW?SACI&e)CJN$ax;S%o`?x~ z5KYjwq%)+rd#k&>Im6=AZ=k!bWoRx8y@FQpGzs+f9N*)8X#AK=Cb=|mBzde5QB@2r ze5~CnQnu|9ow~uc#q3>-zf`Wr2Q9;mW-A_QyF=7{D5;i%SKWm6ygLCO-uj034Rut? zMH2bgJE5?p9B_0VZzqa9$Z-Q;Iqp;zL7^~NF^7EoqaxSOB4m)XrAN>PLlJH&6CQH6 zSyK%tOd+(=Xd6z*krEbv>1iiw+9s@9c6t9@01lTR!xm_7+pof$w!#RVHIosdclVDF z$e0-_{W5*o(2We(J;aH7hW0}K0_UNM7KQ-C;#CysthlsFSZr(%gOprVOrj3M&VWxa z?JGf8)DeyaGm&=Pf=7P76?r~|$}OSx7t4Z@<5P-O3{=jCFkk4_PMZhf74EcE6lRN(Ay}5b zbUImx%QDpGQfTwY=LhB6$YSNgNpnGq-9*uZmv|tl==;T;A=>>`qs=?MbxTx1qBG~0 z)8?XnT_W}Fn#il!Zq_}J@J@&Lk`Ym@X|ga+pJ^Wr9Ivgz=D78%)yBeE{wxGz<*!8h zD)=FdrWzHH3AW62H^Eg}_t1Tz9?3`_2d;T?{y1NIg1*T9%b0otYjefXmJ;!cPtswD zJw`4Jyww-%rF{D6(gu9mcs{^! zsHF%S-gESHiIfjiVs!-OkwzRd58=p4jO-i=c;pIA8@vEjh}-xupK-~bkIoeYadF>; zGOz{FHnbTrf>K-lhdY9KMa!Jmo)neVa*ez@jWp{A3$5@VxVdssu~ci*b&et3d_cb+zfz#pB#$e zi7k_uQ*)pb8_>_rNV=fR%W@3_zaO%9r?E3Jk7V z0krM+-ivQg!rtyf%ddAS;hqj9lNhRmVgypk zVy_Hu220gER=nZ2I3q`#G9F(_h-+XWN~uB?5lws`uE8a@MG4C_YibnuXH%n3{%gv^ zQTh>0cp9XMuGJ!ML0G6levdr?bStzq0T3P1XzXJsNRXqvzkCvhj3NP@8$=SCSfQ&vm%0ey0FBzZ4` zbiCw;8yuc|YCP1mS&f$4BSwG0y#k@QL6lTgg5n?1c)V72gxVHtP-*8w=BTB`$}`|r z9UdnLA5;Ue&oTC$n5hbG z@^|GQewSI089B6!mKPZ&>Ye}N0iKWwV)7&6%{*d$-=34+!qlZ4<;OJYpnjm%6(ii| zIfm-Nuk!U92^5S?j);?4Ku=BNaX?vEE(a)f@(o>mAf1dH4w)vlv|(7ZRfc8>K{k#s zi(6Nj0Iqm^Rjq$AyJp%+!}UqAeOXjTl@tMZGYBH z>f2J7N(kU#hPq|V0W)!5wS_9|DU@UYnm9h zJ>s1HQ~CpY?IHlU#mJimnnn8^T~tW}Kpvro-%Wv!Y79#!gmLL?XE+*Y8KsG`&BRM) zg(UuXeq+^|lu7>T#gliH_zCSb*-+yil5`_5bD5af3-CTWz^9~kPI^uSMhFq&@0&uJ zDjHe4WV>#uJn3_=QotHSl@Omda_v830VHe>T&Mey{M!i5!4TPEi%}4l(S@Kq`hqoG z(W07R##Y{|#4O=%NaHw4>NtPrv#Av@134Op^&{6B1&Nr$FPvYYVJ4qv1Lm(t&C7aJ zlk#OE*;D}DKMr&q0C+)8nl#8Tt07WRG*YM1ic>f%04Y=osS+-g82G{pr?D0=K_vns zX4>Q|YcR|?it#v-(KN!Pxx2gv!pCV!H^jMtOtEc9i=TdUIh1Q$u-YQS zH2C^J&Y$z|{3RA!C-G8{UxS)ksTgVeoI9)lSts43j-rxg-$Xw2iHH@p0e!9lR1M?m zDF#wKp8d6;nPaTRv!@KBU!7KlDdQ_=y5A@Yeu_&{Z1=N29Z|v`4U|X`@WRXHi!A7Nao{3u z0w8s$D&#>7b<7@Vvu)$}|Jbl1>_OapdR<$0AiOb2f{j5iB>Epzyhq(Ka9e5pK510R zE%V+49Mto0^kDUhhZ3G!=`ALNg9WM;K%XGOZ+q28TB?t1EUZx7Fz=h+q#VVU=xUAv z5oZ9$8)A2ObAZ?tm`j2nxuv^3xe;TS_HqY+LtRpSK8 zqE}pDIV)T0}mlH5Y^DoWWs?XQD?D5=9$kjk=}W%U=v>aauQCc zrM_=kFHv-LtHl<&ad|Gk%zQI9kTPR*Pg^7FnP{Sj(Tg(n z6RbJ89psj3#Y8n_hIBL0v2 zf#NKEfEfk35+IfXATpCawwKFwP&s8b9ri1v0A8%P%LAE*OMtU>sAw*bMT(+#)?M~{ zPX2-$ox%2^g-UfGMHHlB-dG_nYbXt7DBJ>yRpD5<(y9K3h5(z({4q(9(h}oy<}|PD zPw4wZQ@MFW@qpz#r#+H}M3~kJ=o(q{hx*ah={qUmvkbv|aF07YQh#QQyssU8TE2~ryv<%6q5kKzqh^o}vQ__SFL*Koi$bfN?BI{;(8V^uU_%i^TZ*+Pn zvfmY;4W{TpWqt^0&;2={bcP(7V=V&z++_8d4W=(*cZqhWHia3!q z&In50G$v|HS{}|r85+47-{YG2_WB-}`EvQ&u*xKnFR3zD*$_sVRznHL0Aq#%g&JM} za(~sWaVH~HJWx8fIJ}FdKry7Oc{nOMdgMrxV>0plBLe7jK9!x>Nq4dKVhHHHJxF|5 z(%n5%%rvyDwNS{ktK;~U9LSSiP^s9x=61s8ZKx1idZR{uBKD3^fZ*GiyAc~U=_rYs zGT`7S88N0$DIvR}UbcXRggWp`cZ`n z8o9$v!$x{`nu&(EqmhU;iWVM(ssXKRnlO%u{YajViMrusVv1g0)0t(a6^{v#FXLFm#zj%51e})vO z+Af#+8jfxvrT%HvGS}_LOs(^~hc5oDDdc1a;l#y z9FWMzj{-BDZaL^gK2EQMR_~S3yJFv=SnYtD*m=nro}mpcgA>dd&5Mrpa1~QTV(K!r z=i{HpGHB&4nqeyVscoW}b7La)h&2s}-L$|O{=`!$XWfD}F0VnXpEI@%dV|{B zt<8|_O2EmKe{f<(276Wy52Vy!KWRmxwCVrKCq9a#1{#Z;BJ8EsYBPZ{1K^YtVSF#r zSi8!3E!3r{%Lg7)=U15TT}C^bL#}aruX~t*ZOExgef)Tk@NcF%?e6`@*thJbO@ugD zq1=T+V=rAfWfpjPhs;nnM8}qFR)3Q4#f^IX@x+>6Edi8N)9l^pT>VK8mxIEI#2|)3 zBK&%Uh9^V84_u#)N?DY@84=hXqbR^QOIaAUv=K0>!Jw%9L^1)wuH+e64&~a@k(F}p z#I{i%@zPQ{Oj z{K&3sdLFbvx&a#QN&vQog^ir;4cscVwGmoF_C>R@)^o>uK2pIzXqb!Jif`MKsu{1{ zG6V%V5j=J?5ev&0t&TBCDmS4~;$*3e(xkRW0d1qOh9D8!IOMwxjr=*P}n41!btA@5RyCrOYlY8dRymNQ#~XvX!*GOwgYK` zi_v{qvS$ABQ*c5=z+lMpgh z_z;QZe@OBkQLu7tq)%r!ePRchnV3z7MVn)zV8Wq;olom5|xuyYaJqWKEh6dM|n+})9J*1>>TbnK$ zmP%8yY(W$+$Q`erz)FS19X1>T>T#ZL96O&AI+XZTDF#z zKLE1vHN)LZQMNr5J#a!ObR32TC^WUVuWAZyX@Ge>=1P%P1e-8KdJYZWiAh)N8i|h@ zTi!5hslsd)R~hch$uVL-V2^LJ#h8GU@o?n`&hqJS146mY87S2`GwuIr1qUdpce<(n zQ;*a4o-#=_MYaK$Qn#Lzqe%+9T`2r!1@u8sH*#5Z+RrKs?{ayHTVhNoF!Rd_-gPfJ zg$_tgd?r6NhMr9We>VnjQ7!c@9QFEjIyKOTh};<)U6jM;YX~LP2MM!`!cKoiI!ks) z6?LJGWS>nVd;+8&vAc8$4CDGc(GJc<%;;lyig&>Np|siVXE6710LNXvb))C%w`o44 z5=GIjuL6fR5+;bSxA^YJ-R+1WYr<<`TqjcHc}yfaV6B^QvS{4_jo z>U3D&P95BCY`UArgk^Fy>)|7rF%lUD2r8Iz4^A%l#T zk-ui72ClRq5H>UB97sC^8&Kfh zmoWh=eiRK~ohly+=o|{IXPrvNhw_x9$WCpdNm}_xu=;@BGh5#*psSXke1$IU4y7Mj z0V}I4-f~Y7JXT1)3;mJPz$~KTNxU^P*uz2~EJ@%$K}g^r#AG9s^KBnwO2d{aIF^HD zafo2iYWAl%;zA6}rI^5t^$;o!OqT0T%o535oLU6|4KlX1>HTl)EgQ^WPZr3Xq17bpd zQ7%*c_MmsBzmpI#@E@d`dnY9h&!7T8xobCF@!cd0Zu?GPhlV5Mb*Qaq+O?j%3 z1+PV-8mrkE?*SQ#qR2tq&`*Eh^Zzdll6pxY9H3!D3kclwqH;WJ@q2qhqlIk%?VT|# z_~0Bo>3{+736pgeeaJXVZs-@}6s(0SZW~)*E$_R4x;eRMUqi@T4inf51L=^yMa*Z~ zmXWKus0THT%*b>kjgaCEvv0t!z-vs|M&>R!1p)d>g~h^93$)^Ks(}lRo7A-RS2#J@ zF4w%(LWBoEiz%{fdZ^)I5hb$@?Q-N6grqEQo=cAROBWHO^#t=A3M$VI#yN81_lw_m z^P@$>pYUs`8{9QiryL}WNK#$Qy&_o3Z4RYo03&@U&0(cgkbiPz{v*Hhx@4xK?2RLH zn@I^YI8Rt5BvTwftk4_XaBntr%b8`^@o&bEVV84~+W$eoP#&e`IkqzV8qwp@ z{_EL}K|!)5+<9Ds82jDk?5l&H^WSftUDWhKCiZ)K)Ru-^?%=ribo;H>$!{-iZBTOeJy@4aVWY=F{KOM{fkMidmj2j|;WXl&c;R8JOm zzbmsd%tAoneEKQf!rNorhR=XmMJHkJHB{WuV;=1vd+&|ZN0z~nmI2RE$AanbPgC*iOF3OfoGTT;9- z)(@DQToZT@!SkWiea^xv*po%jlagfiDjc9MRdj%Pog;+zaOBcK(7myK_EoD7Z4M`- z8M)ae?C2bD?5UuC&Z}d+uZ6*UdxkCN9GTnrEa87raJX(7QH0ZS6mMa%{IBv}DG64% zeCld3eId?g_AxO)Zq5(y$(?%BIoW|RjxLvZcDQj_z$ecN3)fG%6T?EeWW!~mqw-BO zT)A9b8h62-9c6#$7CaT|V4lXy=qH+|4%3QBZfxM2Zzi?y;iWVnJoj*USX{l=>ImV( z478PhDW>!1e)8dDvS4z3gTG==pZJ@$-0;?aCnC_5Z+@Xp%5a3lvsr0tkS6eL zxa6<3X&QSC`Fn^G{A|ek;ohO?t9jL{n4f6U7RWPIu|`vM6||47)xqyTZnv8idokJZ zT`R&Ud*T6-LE&OvD3!&5t5r7lkCoh>O#^j}o;^{mBbyA>TSZ66YiL3dMkDHqqHCVX z9%p90suTA0)!Va%A?(P?By|AHB&Q&Y0*C`&2xpqV9l|PX6@?ZEnH)EBwTo9>X3M({ znSEbZtkFIviKTurnSi*WGP@Xz31U1?7MJ_N2T4X9N2_uQonYaqA0$n@dB|@n?;^~; zD44P{?Bc9xS8yEIe?bi3ABz7!h#b5Z4?Ie&{p^q%KrfLc=B#-)x@{B7G38ys{vZDk4X@)TqQ|N>*n(2)C2NYzC z+xAoW+}xllA9PdANHdesm+Z^VtDhnQumA2EeuI>s#ur>^<8Cy?nB!(B*0l5M;tm0* zXK6_ykYjphHlH$>QXqdMC*WJauvkqlEhUzE=ORXitZ)&P zd_^0j3A$Ka$0RwCQ;xwe4mK_Ql&hk&9Nx-7+b%U*r|HL(01ZhPO+80S*f891V1h(L z+o3-fj?qum)2Q?3A(vq&W$-Mfm9r(48qfVFhj1BFnoNPSW~y@Nvego4-4IG#LNisN z7pSn4=!Taba6%+BIqh(kXaZ}xH)O*Lz(AI>|DNReE~}w<*|{6+=qWPanq~cO3q*i{ zOs!0|T0-7~4;)EuUQj=pC;da3$O&mp=!9>8F=WcRG_IdL1(@Gjbdz3jalA5P`_ z-0&-xeblSutkZ`dl%9=<5~m@Jj~?ib6kAl<9E44WxgEY6B&&Tq`Pj}Eb-95P=`t#^ z3jT`Vn3aEOKsD&2d77k|EuU2|p2`Y?45+a^ir0#XLDJZ;xRKEf7&m7)wQ)%_NxU|e zLF`Y0s&f{&Zm3D=iIYLTuK3`anK9{$h=k^GV#7%IC}j<3jyO|3&?EmyoQ0h7wi55+ zh=ptCYx-C{--1XLzPuD>qvonpwpoA59skIie-k7b9JclCu6qS~5Wtrca+GX>4YVM_ z>qeo&IR6B5t!9$X^@;iZ^ca$alzy{(mYraLdYdJXw;gg@Xfn7&HVu`3hZ4Q-eulaz ze6tbzxcqc(*XuYFxP3uWIojo3SET);W{mBhG&b^e%C33X@?l~Sh?7%=|4mqoj+`kb z-K3{2dA^PKIc1@j(@B5ZsP{IOfBLhL z>2F2Hl&XiWxvM8VvzUsF+kdXo&##}1NKYW1jlHWeIfbFsgBU2HEjpcUc_5}^n?a_d z_s~VxiU;mKyrWBs@V!fgOS8D_Kxt+)2hV!0OO@2TitEIf)RO&F(7F{TH11nuEu0Xi zaXU996~X1_+e8Tp!{P^J;>A@TUA*UO4BN3% zrtMh*&-xPPMSA4BrbfL#ft?&BLo&p%Oq@$?J71_w+e$Mem6jzhNWRawY~X&Jzu%Ny zQ+fp>tQuUfa9sS!=wIMjW4M;~ei{=hh(QyKY4?}8k`{0PRt-HZy1{C+?o1KcO>aQgSyDT&g?6w<@18 zAqth2W|G?(TU%GPvn=X}})riAsL*;*+b^kKxt7+Y%O|r)G1NxCP=8_6aeta+}$qIXDCQY!Y|W zK*Q0anSVQm$3cFyq=oEx)xZf|xA5h}8NID)Y6&>vcQl7J7odD*XexG;8?3s46jsd| zmJI+bb;4@6TA_-8U26Tors$6gt$``cV)_!_aVQt0#H9*D9Gd-n)qXpRlbp(iDEAM1 zQ%+*qcAUpXbMC#l{{(Z0UL6q}tk?QL7hUUcc?y`H(`pk5#xqcI-6VWN6*FsdM_N0EBt6e+1nb{<>!dNE4h*i_Sf*I+#j5*Elh@4w=`a^y&G6Vv zpsztEx2gU^4AS~@Jz@|UW*40_(~`lJPT?qB!&|ElK(kOB-mZvEY8F199xNG-Cl}9< zzro^hxvyChS0#og6}`vkGo~m4qA;y88?Dy zUTDZkzCdL*Zvc>DKxN^Sn~(T>w;ir%(2S7JMvsJZ#9_s+q>YJ&USr*LV}44S#yN%} z`kh9w08%S=Sc&Zp`bjm2Jyx!-LuwtX&oeuDOOJ@eQBzMq@H=8%c`Vld@PPWu^%vPY zo|0Nueyg4h0O?7R2?jCH8*WJ}yh9V7MXQgZz@p5E-JiQ@*$k|kvR-Zp6!nWW!M&VB z93r~WxcXRrVnkeQm5YFhV=icyOk*;in=6!TW%Jo2coTZ*l<~^UY*A`Zd2GXEQLu9U z>Cv#^5cj$dD#1-iAclwhyFk>1;Z{FRmd;==Xgj8g5{vDwmGDbacZj0wDCB& z-<&799~u+0)2&$h*dS!ns0>@@7G>TAqqLRvj>fXu2~gk$hUd{(8mcD2!UkDp?Jl8y z2KJqD&o*vVFP3194+k_}q9FxIhaw8m7`cmRf^;NRkM$+F>zh8*GuFIbsHqwus~^X9_#;Zp?}HYZLKJoecVO1l zTWoV|2W_CV*4EG$;Y%YH7&e%yoH3+;OeFVw$D4Aba>ivwRIqY`!}J?PQU^l^NEu5ln|D-ePUpL4kcAHqP550 zZ^F&`2?SiLS7tXFZkCS7_9odz-8N#%GCr0M)<qAhw6NLaJw0>aS9)`3hY(yPZ)Lo^zdS?LLz?Z1RI)CVQIA zx&lTz<&r~ojH%fuK9LE_Bx;cRf~V5t-sA0zE?$M>VfSX(I%E9r+k9@7P+&DZ9D z_I+0d7^`nF^f+i;jQ9PYoTO z3z{%SR4edGL8kiJVC!L|{>>Em{X0tESU8zQ3)Jr$IxBQT1(DqJTxWgTqv%#v1PMGk zzbu}t2m<|oB!Vr5L-4?Q;=?VK z^Ruwu1WLj$|Iqm{y-kO_QTBmPr5g}~3f}-$rFl=vo@i@QQzz|U#|w(fgICTRfOAol z`Y!zxrlmmm;>O!!QRZ6SJTXq8lepjgQmU0$0AKkSe{&2ab&hFfY0DXO zAm8sm$1_DJa0OJ}8)?K_;YjTZYfgjHFXw=CIAxIwh%n#_+e`SMpYyf%#u{f&C2J& zOS(BVHw?G}2A`!Yf2ky?#Jxg02KZwE_PVgg$xsSv1>R1G8Pkp4%;lwU4L!!0O#hmo zTV`T_#w4RjIbu(Bfr^=+Z4?%6)m;b}5yk1k(0G2~%=LY_p-}xjPgUfor5RoneK}v6 zuM?phVT~*)3|I#DS!T5kHc8#44-Y{ZrubTa=%ztvMeI^aUP8!d4a=0M6i0O`5_KfV z$}GOkO=tzUVW1m+*LI0|{9KIFNokkkLdMi6fimIy03{V8NJb}=(Ajt$kt&||)u)~@ z2he+U=q-xX`n%TDP-?nUAhaG98N#h)IqHZqh-*T6P93k%17M-_gRV0Lr! z!GA0#xxJ71>dH{R`M~e^`I{2};NqPmAWOJWEk+H%>1dG$r%JnOxd+1|)JVz8IZ&e; zRN(2LbE1TMRR^!YP8E*tpufyA9pr#bU{@HP^k)@(vh+U=k1zdkI4w+z)8~gJ*zf*L zV)g2;fhFb>wor4+RkA^N)Sw#tVK*hkR)BBtjIj6`OZ`m$TfA{9EWTC#bh}X{wuVC{ zl^>7<8-^Q~=|I#H_t*!zE;FKbc_+F@9n-}Or zw*h5Oylh!vGE8&OMa}=R_gBm#%;HQvlynf@()`HjrhM>?>+$Y0g-dS7%?QQy_t)$l zg3W3QSOID|iAP&CTSvZe*fHh`@Fk`RtI1|7h03wKp)zSHs!}1? zgoJ9X>W_vFQ1}!Y0F0(}`uRz-;@!gUF0z535tD+7YqSa@Z6tn{T;-give&0RM!)b& z%FpqpZu~0nL4?CX7@AfaK$_7~+W`)Rd4(ZPQH!p`5SZ}(0kQGg5R>54K_}HKa9YTL z#JNGSXF%z+QaNb4n`qy1X!>ogdW7{SJ2Am+EfUF#`(J&_gJJ;r$5RQrdVO9Q3v08N z*K>o~JeUrDhi{IigVA;Ox+(Y{4l2*{t87UuoiC6NHZARI4@in9kO*ZUmjynEZLmFn zn4F0=whN~v-a}|%o1Vj+7}3@8s<=Gc61DXMEwLY1^(rP&&S5%qA+JnDNU=Kdf`|Th z@q>Z^XV9tob6DmqJJk~bgQL+4+x^n#BE!c+E5eIVJAFnsFTBnWy0*G$Z&Zf`B#?qM zT^6g9A`t1r#!DTKOWv}oK?T6_<$-kS62AXgwa#b4xNW~nlHJ>>Lq3B2R%w(@Tm@i> zF;09}t7Aa5{)^I*Tuh~!c%+~38+3>{(=!k)X+s&h{?)~+)j#Wv;sbq8?O`g=ER^vnlXjxjVYn?H z_-@Sd908ci=+A9=lvFxW#eebklKXvPKW*-P*PRD(QgV_>hy~{dv==O&p$T+)ddqzC zTdbDP?_rAd@mYwn3nP?nmZ8H?mqnta4)q11`_&FHSg`*>+Rk@!mt?6`&JIsN5A^R% zR2W>94zHFrV?WeK7?e%1Q7&O{Y_Fil{|+E$klLRm8b1>%lj=`Ckhtt zLNV~|9qiu>rtzPiqqgE0;aW_USK$OyA{ByUr8RJHV36TUDq!eCkbdYavfPGg{X!;k zI5cs!mHVE4RA!lqnShP0kpMR&CaYQb0?G-P%n3mYjmgJ;9rW!qH$0<2dB(&dgDQRt zko)~zf9E7;?$-=td+ew<|J#oL+{AU*cP)(`DmMQ4Y)2j1q3#Mpin)SQ>mFNZltSmb z?JsRv`0dJbSONex`Lj3L|g^w9Nw zdF}r^{UQPUVYuAzrY zjnHL6&b}R8(bHSuDB<#jPned^jq|Y+2*H`zr-Js~oerB&g@U5IQk0d_ab7}An#uyv zS8k5bGW{i_><>Wibf)J6U9F*_13uTg3q*v5++8i+Wp=@s_44jIh&mfAuxVU?I_gWx zfzmXLOvuRLpoj&na2#=v8h?cx>p1N6_k3$)O;uNV@SHiU0Ry#O-?7<-E3P zDh!C4i@y9Z zn}@6`}7Nrhy^eco1~$J8~U6 z`I-LRk@iVgbD-GF?88fzuYFh47s*jW$OJUSSdtI?Na#E)CQi1>TZ)KGImO7+`K6X$ zTPSEg2;Z*Ihvc@P1h~JP*+%72(ZV+>n5{JDnPJ_)n0(g{2-85OV8Gi>PRVywzdHl% z7QCDh;9vmEZ70r0DVp5`jF{TNbtH~{rBqsL84LnV|3hqbHWmd4#LDtPWPOJC*RC+9 zvynM+*7Babjv%}NuT%Y8rDZsuUh>X-!EBoBk8CUVUcFpOh7HcpFbl?zXUbm~9xG^! zJH3a=E>jZpZ?x8H-+}Y_42?JU)?>C<-y5)VzkvOu*cjxD&bNb>IU3}4CTnnVK`W*s zxCEKg>ODxV3`y_m6f|5FmmLxT=B@WO({AVluT=ccwyIunJYS(UvQA(@|0lg2LgIGB z=Rd`^rFqUiUTnrq24%BB%$U3 zG9?(KrirCamB1vlXRBzAWlR}((o*by@{hExL&P~q=tzq(tYET=i!mp%&Wm)W4HGnO z*INe&Ho|8=TyMY7?6%QA+W_9bqp#zJcc+U7imSADo>^6uzMv6Qk z-HDvSJa$L~CLx!uWWjWjj6=NJ2^~v0o8H&27>Z2j;mX zOpeJ~Y#LG}A)`zZl)-iP^~eE&!yJG1{|*t{+J3$B z=64eem>Elu@$B_5~>Q~;RaF9SQ5w+rPzv8Ntm!@6qMqX6@%o_1BCX)!-Qg6F&h02lfHfMroZ`h zZ@zo#XkS9-uxPlhdb!g#|?3N{?7Hr=Yucum=U*4XJ&sBOj}08 z(76|T5Lt;QhDy1Xd}E2-OSKWEJknu>oAU?rz%?}^4jA|#MYKyQh&rh#G+-v##NP|K z6Z2+?r(eU-l6M6Uqk_;{3zLmjUpYW1Q|=$(19EbOQt8u z;egmkkXkBGB$I2vNPB=?;6u7mT2evOph&11Ex{z-QK=i%%?N8L`YZo#WldyrVw;Eu?Uk;5kB!9%bSraWvj zB#0PS;U-c;(4c_WN&$n|=2*CP5P~A9B7Kr!F+S_BCuRP+oE$W!VGnJO6{0sFQk{mc zI6!d9$DZHxFrhIvhATG5E3N&ZSbu0XhG!-$*)Y2#OD;>~`wx`GcJn=`hDld)h{VRl zFJ;L|JhM6oMo9%?i3%35S}9-<&m2p(OR8EQU?&xgwxA$uu|m6V_wRwFy^ojACj~^Ph_uYVTOmq}5W*`d$?$at z2o8wPcFcd*-wu3r2l`#tgLs@uySP)u#@%m;euXw#A!2B;G{~Lw+5-e9{2e5?6Zxz0 zKIg?I^SqkBM*Y3!J8*pf=DgQ3!UgD}Mx2!? zq5>RYfftH&er31H8z#!}#s|o~Fq{Pz-*VuG^sii*Su+M#(#6d+@Lb^ z_5Ws-gKd53WeQ@t4|Ek;I!G%848qQ_sz6XhN<6J_jV+hAs5S-whn!12NDPNhK{)9( z?UnTdgbsn|2=&#zk^Bx4+>pO@O@1W( zMcp>=t?yTE3};GB@cx+0A=#tQlFa%vNpqX)okN5zCrpB=S}9-<=NzjF45|SUZyM&A zy_b+cDj?b*5ih6<^$iC}e1PDR@gRXKVlTYDdA1jB*ZW6WxSu|{7&aG6{4p9W$u2&5 z<_dMmx2}fIkw1Ju%(c}Hf3*@^0#;+s8E9*n<$8d_LLY2sNDK(siEDn^YF6mqJVbEG9A}02wfo#mS<&(6Q&DG9F`6T|Yq3@OO~lPW+3mpZsV( zysdYavZL>s9>j~Rv?O=wadmm#ISB4(*`mqvNXRH7nKL>Us0Q7Dx?}`7*5J2ZhRlGN zEKU~HKyMNf#w~7E_YV+kOb(Ja{*^z>^v1bx`<}ioegyS6w+*!AyOG@m=XH7J+D`p% z>8akPSQ>_tm)hZz%~}$%C85PE*qp(H2Hk*|BNPlaeX zKn^`#+mrgO59~v$#C`i+U$X;G-5QAns@4k7CY{m)%^}G3kj`9R{7YMGmTi97apjqjY<5t$vQ~h6Xh`^>e+vmXKJ)1KKd4!G(V5c;+ZnUW+ zO)=bbQj>{4!oo-F*6qm6n*}=Z;&-z*&h*_K`E@_H1BqLcQCMP*G0uZIcNlDNS0v&w zIA$CZ_)*Y^lt}1U%^6JS&>fga=L|v~VIYH2P#zvC+E>bkH=O!Z;sEJ*2@X7Fi3Nsm zeaj1Hddob2V~)L#{j+O(Vy2{Lqb1pW8`d7UMYhnOQWy9zRR1TwmUMzF(!N+T2*{*F zLeu5im^rJ99jXI&+0q+8RI~SO-j|xucvA^7Aw@{Qw3!;0#(gVdqpfh$3kuFBJ*>wnNG}4){)F14`e zI5D2NIC6mT$FunF+JkeA?KPF>(IHp2-Adt2v#iYhZ|1^#w*7ctRiBVU$ zZ7)|PHMN$KA}@WFZA zjNR4=knNPSu&~MX&YIujiP7$v{dKkc)cp1JEX|)>yh$^`-n~{Cg(aPbfv#!)w2$Hk6EZ22(6O5{x-g(Z$uIkr?(GFhX_6+ofND?4j1m^+0YXjifgV3d<0|!Y zV{Ft2B6Ve-D7v4-^FeR;>v`ObrJMe^o*R>n$hE zNSTCuNNS#3Qv#q z^ZUy?pzd=8=+!X_OBh#GDxgrN!DO&XG!iUNu0v2G_%)!k)+7=i5}MQ{aAIbcZ8Y6? zQNBnoW2`H}*}<+^{Z@wMg_?+m_52R_`+V-^czzW|2XlNAA}Z>*c)Gd5tY+&mSOEdE*IrNpBd?IUqKy z3`nXzl7D*+=>L0tW(U|_Pw&YIVN%YoXu+sbb9~M!83&T%4N;9on$QPiQ4%Tf_A7*R zg+QIqZOTNX8VTTr+@44H_jHZjWhLnUF08GG-InNTe@Xvy?(bn3vjFslmoeHK#yT{^ zjZgXJ1Sj9u>UGj^b4^NSZmS7$siG*re->6M{L&iUL^29LClfZKPmM+@c#Em<5h*eA zD}=NZ9(TNH2=cte8XGo7eFMRV-O@dUqNS}y@^y$T^+Tz36|;!cQhW@1(<~ob^gpsu z{+9HYKRia{vqxpHM>MK6QUwM050y^sfiNE_XS9rigX~5_P@|E$imL#`fGkQPCEk98 zkVZ=dYJfZPOtU|OtWA+;s{@2C2OoHFO}$W%B2&^O+?G=-jK$C*Mb=lYN7w1B;7#jw zM%;o=uIr?iT1@^PRMHN(9&Vva^|LjhLG?DZ2^aKN%OR-INO7Xz8jwXvq{P#&5YiPU zshq|$V%^cz|GIxZ*=Eb<-i48l>XOeOoSj2qTe7_Q&(L&GYdM_}4eg z-_BcGrR+JtGtr?I;aAA2BF<vo=3LkuM4PR*@bwSZMv}koJ7K4g(%krcMSA1%&Q$G1M`X9$zv)mHL)8Drq zYrf|-e9Vd6@>OulrWt@-!80N1#U2R4NI3(t^25PFcB3Jv(MVmzC%Xc}rw-ML9s=sCD5foJOm?>oP``rh>=JajGm?9Tx8T#!(Cm< z{3WE-J$cF4501faX0wq=F+o#4o-lKbW)wt&cAt(8L5)V*8C!wy5oxouQM2C`bwOk{ zE#LtQX$_WZwnIU_f#4%Z+UIk0aBp-Zq%DWjvefG#j7A4f@I4$yoy|C_-(JDv4Q@{; z4C342W6O!hATv*hVL2>bJxs~~M?^-VVJGx}R#~hW1<@eW=wt|LG*V4v$b=}$B5h7K z%GzMEMO_eKA9+1sAuS;X+coR|>3<6A?l%$~3~o~QMh%PTfLBiD8FDtRWVmCV8)IZ8 zIHPsVdOXWp`{=FCLf)=bY0ST6{6GIQ9(P@@2HX3;%x!P#2RI^Uv`*-OL$n@3MKhNP zzagm6NCi)(6QU@Kw3*qc>{Rmws;S=vAM6X(S$pygM6LuMRT5vzLzl29`|vw`Nh+-{ z7bd&|l783S9eCq@zHz2&f3vsj?z>TRcUyFQ58PoiqaYe|^;cmCYBbWcZbGbO+tx-i zejaByj>zMt@Iy9UFR;fSj}sgqC=pLtgcq;qV&1@5oO)`tMy4j3Kuk!ec|YWHJMh38 zaoX~<-}DI^J}|=Kc;6IlJR($Ss7BODJs33+X+}XbC{!{Xf*QfE0ZB5+1CmiPAmTL% z7sNol=nJAr`%jd|+`L==U!rr^%HVc6bIZ6+Zh_9DXGqDYg%uMM5?ah3_Te4S@A!uY z2-{5_1e0=Ty->W`B%1}apfslGq#lf#h%}=h0P2EJ(sYnVMC)E0u!GWYOOY=60u=-o zeNi+98_@xaVK$GT9c|hFgHrg=#H(*LXZTuPah;$LzHH@G7J9ZKu|DFL>G_Z0(2v%; z7G=JdCYYQ1hIG0ol4*uU2;B*XrbZ)m_y`9?Q4(o0 zuu*h>#q|p#LQD7=e}n~h1pbhaKn0~Qzd~7=*mN>M=>{bJO3Apy&YHFEVAFr-Gdu9x ze`8;r2gZO$WT-C)D2g_K5xEXTKWA%3K>*Z+Ov6i-)&@jT5^0;oM%AE68tN|kq6o$E zddNoF&hl}9P$oWw$=z!}O+iT1VL8<|MSp|;$W+bbh!-FHmpMD|#=U;kOvb~4Lp*b~Irze{ z_-L!`<~=o|A-dp>Ooy(rlr|s*r7=W`L`NIaNwm1rv0HM?*d$k7J3!DA?jrm_tV2eD zaCSm<(<%U6-9jA~S=-mPG{4KX^FK)V!B6jG*G|7r+kTTR@qH)4$@;Hy(!-@KKH92} zYt|>$jE3k!*XUr7Ycx_#?MOfpC6RWAr-D!SJF$+v1B7;lPrtYWF6iKYEUL9&ZH&Ix zSd2v{waz?NPbZ~?-zDwxH;7+a=%Z~PHUIG2YCLr`q!DvUm-a6@M8wAEEzyXOTxpWV zwTt`A#XQ^U&n+_9{R0HMz)guih!x=azYg3E)V;3&XkmiAcU`8wm3}WQuGG|_r;7MsNfTx|!x;=a1t z5M3BcVUZw;lu>))kPKKu8tJw)NYbced5^hRXInF98w=^*K;R~!KNfi#L*r_2-Hr^} zHXbu~i_5;JAN?kxdXq7J;YW7h^jmP+@6k;V4+@cz)_#SMuF$0J93o(6n)Q~t+7Mmn z8XgQ{jYi68EOST>%%l$|FBoCE!hV2ACfqpmN7lD;G$en#)S)fIAp)QNQl_lhX&A&+ zWX-R&?J2W)yC37>9eCecnRuimDA%^7iOzF>_YjHSG(D9@vJO?bDi6%Hl|};jV(Ice z)&Y!;nT@5m2gsmYQGGa;@W@S7&A1c#%f2f)ca+1bDziS(zZ=G_I_>qr#yU16pWB9S z4(HswkBmfmk|QIEoR2wJhsJ&M`8=UYMI{&-;w3eFK5pW$%_j<(?Ya7NsFJprsWvy`Lyg zMpe~3aLOm&@y85zVBrl}po*5Cy5UduS@bgLpE^X!cANdh+fdU%tkFmG(R2603d53w8z*~{0P_BZ8x+(TyK0Kpz`JK_&w z`Rksts2m}{yDz5gNe^7xIm4B*LiK@Prwp0}IC}X3haZ2iw1;K#4!p(T`g%qve_p@4)Q35ktA4Wpjop zQ(*V$fuj5)0KL2epU}k4=)OWUC0zU@O_=Ye_xmOHx6$V8IfJd%q(5_ru)dooK^@ne znYud!1LhFVsN+95?*B!?_^*)Rqkiw>o_A<{9Kr4sN>>4es`q-%P!bd9K0PpL|6cIR z`|{=X&%f{O`8I#d%^7T1lYagXaoVs^{A8#{!iH^%x)O+ zZy<0N(H}Ju7F8h)6lZ(+cti6 zo`t)=QRj*|gH7qs9hgabQpvG`S6jxxZWjhS8Zb0;1{AhFRs2?8Ie-6GnV9z(fcV3d zCYv#4$=rc3NQk}$gr)GRbsDh@ax6>9vF_6YUjMgd-`tLG_CE7|FFa1t)HLy0f}#Vk z^Gzh)5#FX{9CQo@X+#dS!Z}vt;MTURw?ys;ssQ#IE!Z~@xRK}&YoZDWFxFBscK}9v z8DvsLR_e-hHDQSBkz=`XT{v#2TYMkc_*8y>=w~J%Tk+MmzRW>J3D%lpbpwa)z+J|I z|IOK)!87Ki62c{+qAdd4if4;fOZ3lm^2cHah@`=Xoc{DW1MlAov4g0_X&-SGFdLT4 zl=thb9ZT+e|C=06Y~2YDzUSY%trzsU7#v&v!=EIVt!OLi` zT*51ddh(51xbtC*LeD93tiCcuzW@byfe0!wX7eh~gquhQH}PdhX#I4!0*7a&DnT)I z26)iQaF3y-1B6<^y~*y+`oAb=2&Jdvw1Yu3plM&G`5hwD{d$6s-1g(G*?WK7-8cMJ zUvr}J^vOMda;)A%(Jw&3T_A!=jM=6ky zQ?YuGV0G`x{JMp4+Zj-&?*>WFByDYqM@-j7_$%z`R!s<#@?Y5oW6s1~FI8ztns{|k z^b1h1oCotX5e4O%(hSo4Tgc#&F2Att!0iKs?j+ol*Ns_zkAzZ|;>(LlG{V0IVSlQb z$J~`?bM-yOXS*e5edSQe*wJ-4(JpBOO!3DM9X&wMA8wNCKVn-0gtxBxhSSLLL&Rud4WFc0Qid@fNEpDV ze>8dIqrJTs|G4cncVc7DHza0j2^zizBvn^liD@>`*meUYo+KP~8chk5{MGo=lm0)a WNcg|?Bv)Jj0000 diff --git a/cla-frontend-contributor-console/src/ionic/assets/img/lf_logo.png b/cla-frontend-contributor-console/src/ionic/assets/img/lf_logo.png deleted file mode 100644 index b3ef2503bef3aa5b850ba5321f5572e9bf3b848d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17484 zcmdtJbyQW+*EV_(B$N^aX;47A5u{6{5ka~;58bUGC`d>*DBazubW6iIbi<+R(A<5% z-+RZnf86(ecZ~Os?{f^tc5L@vYpyw;`OIgoz0Nm91xc(&B#%HK5SFym8zm6vAuR}m zRECZU+=-SRO$RO*c2XLSAP_4x;xCe0mWT^*li2C4x|6c4sgtXLg9%8?*w)a5N*Zin zW};+bVC-(+V5&P%{*`5_qqLFQ;+S(|i#(MB-iS%Ky8*TpT@9%<4ehr^{ zJGXn^miXlokjZ!(b@phAwLMZW44g}Ed_o#s&@3*xi@_$?kSpRRiAPMlc;jtqp^+ot zhlzvp9S#8x4Fyq9>&^x zoHehkIGgy&#C~*m9@E!Wi>1gWku!w>6{Nq7LW@MD2OWT^b5tt1e$1|_Kj~<6pLIIs z%^9$IPf)~~I4Q>g0vW;QpCSPhfd0~nNh7Z5YM&z!SJ4t+0O0DUiy`v>aia+@8U=Cn zI0<&Bx<{ zLpRHq7jwDcH=7mj5V@etRI7+?-Z(_Q*jkCC6vr<>wAaBv)E%W!gc9tra+EFg!FXo8tAmT4+6<) zt8D|)??dvDB9%!Mk$h4o*p%4wlH$X!^$a|Cu^T{);OE`r88>4Tg*K=pQtLMWeQ zI6Fg!w#17Ej!V z)w6yI2nHvrXo@F9Q>-MyWh@fHte}Pc^r?;4)&0ZVqiO+teCN+84K2L%3%vM7%Q@kv zE)av|iW=l=YlG7*)3sGf({+WobFn*Pw>xVeGquXh=%r2!K%EV8gldD7?PFIyAeqeb zgFVF%vvnJa^2h3^BYEJC)b2gvpKn64@gjVXRW}rQ<>u?ouXh^s5MeXfcZ+ALelhhe zf=zk7P;_gaXdFD=dG|PX_xa}Pu)8`~_nK1+Z{hd#szrRBtL|S091DhUya|5Y|C*`8 z)MS*@uPa+c!(zrtua`Re$DWz)V2R8>$2%OdW)C1eWg9dWpx=lNDvg#*aVOT}J;{&a zBt!(p!Kj)>^r=~;H}ghx_{51_{&~Z5P-C3?xkAImTZCIgZBEe;>*XIGfYnDh&K6Ga zYH`4W3@HKJmDFFD+&xo_YzNBs&9Z<0n18i2x(h0Nt3P9G#z9wG%E#Fy7;<#Of;rEbEu~)@OV9bHMg_2qtj`k){Ta$`#t_n<TkQ+_*ZtP#- z3Hu47^0l*_t>^R!8M^;TL%b@AhzHSB+50}dSKMvZUDZgP^}UX#{#x4q39_<7D=?3xzpf+uyBtN~~r&5+o9w*NFBo*)qj#hI#Zz zPZl7YyT#-WFwX0IMABUaL|?x9WyHBfGZZM4g07-hw{T8DcvaRv_tJW%B{rieCIbGc zcW)TH;)bc++!{Fv1svTK9T_#I2Yz&4xOW@A582up!S{3Wwgzs6iw=`%>n8pIFWreP zdk-o4PG9!kE)MkI-+G3vUq8=YD@j2Z;|JLJ9>`%Vv4*4X@9nLCQ8o^4ac@1DDvlb% z7|#8v5cIVl(aie+6ZAe&)jh|4asb|%v>$$^YvzH4c88RO-w5YxYy0n0Ei2Dh>xN86 zN*=U0a>oBN!VgW$UKttWg8=(nqj&$=)9~fO^NjvMn4uJagqEuzK*d|ujLY=j!bEPp zi-`bM{rKn1JevrV3Z|nQu%a710t(g<1_ONbxlgHH;$+#Sg~=Kq2tn^?`(q1(UT2`_ zql=TcCac`IW^p`no)Po5yAuyyF!Qo$Bp?!2_&$nw{;p446)0&CA@Z{on%f5XrG4e< zWs7dt$Kv^iqXkKW3V(11Xur_n1oz6&Ei5pxy6N5rj#V!}Zkm`L@-giT$-I-MJjXZ; zcg}I#14R2gBdT8xAVX7M*{;hNvZJQ`=e0_F0OG$aj_Oq~+iD37f{r=76z?p^*#U1; zC_F`O9W}(Y&Zn8!6&%zizbh$z>j^9v`=5cZyC0o?kS-iBAGt3SpwyZQK(44@f`$Nj zDr7lV9Mi5z_MP@W#+v{pN^urZnC=Xxh1+Lu<=$m!qcsrLQq}vHn(_s^H%E8W`?L@_ zPBkzLbV;PZdK-b+N(uMAL~e9AS^rB@ss%XqKFaGQ#7A|8n*#x7rI|&(x@6%{%5eAMgZkr1w^38024MF0*M^q zcC0V&cf*#c`u46!5nEsoBoFm_pJJ+qovtUHf z5AHUo;*~_c{g+p*4lY0(a)Oblrp1oxl`&=Oy8q)CeRqUo90AtMnAMg+Ur}%rV7MO$ z48(wf0o9KiP^<=|AKzyUl(nf5vLUq8)k-+Oe>^avnW zs^RO^&2`2$rxk+QzPjW)qjmSpjo@}#9H7iPAVOAi!i?Y6TeKYhqb1y$*tN<5&lLr@ z9BFb~jhzD{Ft6i3Ma;L+DFCqCi1f10hz`c;3k0_QrX&D{0}Sjj-sr7}Hy_{W2cEc_ zr@o>FKmw4IcirvUj<~d!J;fxnKNXSHqBQ`o{)JR}NNKTZJoy)ZRz+{JP0*|U*yMUI z(B=~BsN|Pt694yqe)*&!G%}>y;04-gpUob6EOZ^7mHCf|Bq-8TxovBxuQm`3m}D`P zsFm5nRLAZ|xv~Nl2uJE~bdt;z@B(R2>jknfOmbIR$g|}sk+z^?K`aAPQ_hTZxwz0E z!UCw+^%~X!A%gG`3gbJKV8lMLF!zD0d&cD)l=_Sp9m-aVF3#?vI)d>SQS|)(t)66) zs>0OH0~^{wX+SyQN?VgotFG5H`vQiE^>`y(K- zyyI?WBmLT9ew0AC16TP@quX1aMs?oV_FWoXCd|HiMVD(o6);|}Lt~{X^6a;dwh@=r zowPCs#F9^OXTxUbC*IZ?)0oPgoaB|}r_8sLmsc88g1*uZX2YjJ7`W`!sde#7Mzp;InbRRZjMD=t$a>=C8kCtotqpO85Gyb`++!?qx z+UTu>*HQNY#lNZu{qP0Y(=M#*5Oh~0yLG0%T`F)qU41!vlxS>+4b3-mL4fJEP81O{ z#ocxWl@%f~MK$=eVyF&M-aatSH6tT9yWsJUY9T?bfdYz)pgW={2SP%EY6dLt2dT3t z+u#gm1}px+$xQJ(+%v|h%bz*-k;`ItGu=LYcXRHTLaVWLh+82O;l4j<-hGcX(I)_) z+CUfL(odg5QzC%e?IN2>%C(&yYm2CEQsZ#)SGdQ8j1tr5s}AQkj;{`;HRvp$8C+BM zxJxIx2eP&_yCKkuZN!Tw!usonzKs9Y0EpLV()#PanJmJdial|VAD^c@B(WcQ-CbKu z5Y-GQw@TE6`(Qpr1T)+HGM$E~3^^^Kx#L96R~awkRqelt-q5Mk39QAwSf92UAyf4X0tuvEqg5T_Y3 z;N<<$Ot1LqMTE$wbSZ?dt5=F5yH$5#SK~%!5%#L86JkBJpH!=X{IXwzE9YjUJ+dHe ztFQD|SGa49=%+tEF^0W7IwI|}?!n#eanD)9qpEl z>J{-cbfI`IZ&I<`>2}3Ur)F2Y93EzZ(VVTh{!|GvWPT%ibB{n5C*1y*+Y&mHFW?`G*}e`MjA%HAnQ>>Y_$X%?!hB5ka- zJqMXfm@^t{ldPGRl-_V}MaWBYs}%4Z)$OyhHz{LI*D9`J z1F94uYVIiG?X*}2x>d&)Q5`X#aHOn^v;mM-R{R5!&m^7?V_d_KdhkZ7{WAipshMFP zWc-9O?QP++Hd6DzzI1M|wrt_ktes+t&-A?B*^XT;&dMe|AJPK*=sgR1%nx#ys|FPgYu0DFj%N7(mwXz>?&r%Lt=L1>@ZnNuO8GgwG2T>WN zoO$DJol1+H@MYRlo}8m0UHYkcT-OERq!k9-CX;; z&Xxn3>$i%XcZ(k`ymu80OF7QZfX-_KK5qs2T3yHh@Zr9cw(NG}`Fit6_nJrG^oOK} z&m>;MT@O?`4r_x;I!GLj_IykQnmV$_7~9Py4;|NYD`(>?*bmXZ%700AC4dBy4(}Q**O(A3OSH?SQWG%;wTvC z!Iu(!1K?D=W*ixh+OnFh**h`^rQ%MG)vMrX#4|kL$Nj97fg?j0T}=+zQPWOL3Mi~(J`|?X<_n&Vs z(vSI{Dm%zdNngCEp4Va85gYFvxt|tOT$ZE2+O6$SJOXCfPlu~)4-+q*bRuY}{EMdY zJoFMg+42z~BNkT0%+cR?DdAuo8FiGF9SV~#V zbb3|v5pQ2@6R?LCdXDl)2HISpDoBbLQ$U`4t5W+8IwPn*d1L%kSto}J)gbv@SBsBj zp~71hT)%KvSO634viG2Kvlfr-0~;`9Ms?Y?bLw z4cg#pGJRt%&d?~yBssbG#IfqqRi!Ddh1XjR7?+ZDPy3am0fUJ(R-Ys7hl&y{%Whlbc>zOJ$sp3*Ygqec;3 zotk?Yjw8PQ1zX&)A?$g)T``TDI63b-lB_=?)v#2a8h4p*IIne6?nf(4&U?|l#t%6T z-1~y1{H|fw+`s}l32Rp60ADG8g7*~si%2YK0HWB(v~QFc#p^hy)40)~VE%xFE_>Sb;i4%s{d1j4Y>iUrhQLZ86`nig38*hU>s+1%`*PD#`&TbrZk4Dr3RiGLwN{rPdekd1-1MesS15cxUZ z*nm|*1k9O~5*@3I9D@rio9x`AQYCi}5MWqXRink+%a~SUX>m~p&c7P2(vM`J*xAUp zURlDqmNEAp`x~nHWCf5)$N6~i%x|%gdPYX7hl0xM1|EaEbHw-ZR_+<`PV0)-dSnmIwtzx}?H|3PKhenZxZXvQy2&QX#^cEFzMzQ) zEv27JNgKO^a|WP#jAH8?k0!S#Ups=2f$HibHo5n!&c%!x%=>4(nf|!hlM=S|{;nNx z5(t{$oDT5WU(<9K(ieSDY=MjQR%dlO0dQRts&(8rZ|Y_Rh;e(?GyrHR8G1vN#$z2uZjzT zG@^=+_5(nFPt}!u(vU&Qh;EZoyefS3)G1GNj}X~ zy3;>gxo~-s^lx~AU=~3zF#wGk5-fsf^?A~n!9`cVY-=MYXx&f}OwUfF9kEZLK+x6S) z+ndcBUHOayz1#D>DnmbqSLFSF>#iiE3)@TTO2|<_PiKR)8)Yz*0#8oF=WU9^(N@`& z0g?xyzCc}F3%L#BVrcOVb@u*Us=6?~tR@B%e=vVX2!5Y}#TslMxY`r@;U>?7mF(~F z9?ZjZGdQhR9FL+VRd!)04|eW00b0MdFU(!l85xqC{c-W~wvF6h|1*CvZQYr)1%q^? z4Y;8|?zZXt`qh)35Y^viqXW}}{Dl9XpNRkGzx&)thX0q3)c<1^6zBopZbWYp+haVo zwuzH8H}6AkeR>3&^0s7l93j% zV~1p#Fz!BsMrw_W^k+_eyxr&>N;`ufl#dXuPtBrb?na?-n>a) zHWcV1+LQRA&}=_eT?Y?YgPtmCUcG@S4Nw|w&K2c(m zfUHu=3!Rw}Y4et3ye3U9nMZ=MKyQRhx=CQLq`!2z6cqedx<$NH=S|mBb2IH|O?>g2 zzSoF_wZiyM!~K6~A>ZUCag^fnpGy{#p?+lE?6b~ZI|#xAYDPgwuv8VCScyF99zBB-Q{x%!w z@!I;9bT*}a+*k=Z&c&}tmO9p@N-_%wt-kqSXvdiy&x*e?-A^wRvp6xtF2Gr6c2;Al zjq{=9*@dfh-X)99 z!1qSrvq;POLCg9OS;IyR^W414J|E#U#bPrbk-=rT)N*QW3J$_67qF@Mj`=722L^PV zNwFfnr?X}r_yW~v2j0_ECg|lp(~X7;4Net4Kf_W!jwrZmxGmV@Qo8kX-PlYO>+)I& zXFA!i9e9J-#s}Cl_G{E`5}|t+EMR{>Gm$#Im*E$zeW!VZ6)$n#oryZLoz^IfDFID# zdcM!VhPLWqyI}U@^-Zwe0fSS26845hTpANL_8?)(O|8V?a3uM)otL`eO=uV=8+ye8 zf60*7a>;mG|E7w&;L*i_b#}4qf(|qOL0KMKU2)b#hx?bkc#pSfdzMu5M|2n`u=?`z z_iER%8Yk>l?!=2omS^W0lSnN@UTGbZhADlDx9~V?y|shiT~p2Bf2@b`3*V6y%FyRbeUI5RN+L6blcBzF&%$CM`pa%!GuGm9; zWT0; zU606q9WX7r$kjf$+oFPllMZov=AXD9ef?wavy6upXZQ}pExAPLY|k(*0&*9ws^9PeO6G1)ij z5_YnLqSCY_TG$4?My7W;l*oZg^ig_?{J|E!&NqUPfBA~k`}pb?q@KvofUwxfOl{W(BoG?-7-2X_&>;B8Al@XvH`H+*i2bsRy)e z3tgW~lZZe6G`giUnreSO7;yH=XYFH4+|AQ8GnSEB205>(Cy*xGWI?^`x$j<7sUvG_ z&9X|PX)>@;s()xpWSU+}wXuI~SjBa0I zQz-KW)rF8>4i_R5no-K-S33Rq!o}t0)|tw^>(H@atZsEElJ9~e_=mae{A&x7uIBp_ z4-QA+!8fuA2dl!rz&e;XN>F2$fW4ayY&(~ z#BXJT$NFoUyHXa~O9`}jRLoV23hYf=;v)QRhaDMn>gobu7MFMRE<0i;hiwIWLrhs% zvz%NhAHyj!v+Kiv;%6ik88axgRks*;>^)Xg1%u0+b+sD)xF`Ub*&mk}JRZ_QpBphRV4TsYeq2 zSEbLV50~c$1`eGPis9=RDB~423P= z1l64fQ5hO~oc8`n@86^NDw$R_RcNDPoUnVj<`h%csY@IlO$`a0HbDfbQm__`-fJCK z{0US;qa8N^Xvi-}!D@>?Qj!marUka}9v`PBPugIK$_26{8J1;_4^znH&HL2D4z*(D zDpM9-SKcwU%)j`EW!S@i+JuWK(EXh1JQs|ye)GHvtO0K;FWt52^Q_Ok+m!J8)iI5l zX91-QYSvnfBwwo_B)<}}N{yjKEf%RfdqPTGmMdUDmC8SQ>bAjEhR<7D7YdudE)=xM zulzWe2WjIvDJ>out*-EhxY*FmFF3B;$Z)PotlXAl;O35(M}Ymra1 zDY}TJoe1Zd^nMwdr0yzC#rkU8Ap$JBgvz_lCe#>w>nmfq_USTcZ%aQ(BU19App;G_ z8((5IZS@yQd>pnHcP$Kkm7_@Bh^h3PiphXFAMme(covOxQ&Hy;LlDtOt(i`63fb!@Wue3mEIcU|S-@h3 zdCUld%!|&%KS`r+KEdHCj3;)=HbK=wz(y~+bf%vP61yqdEZO$zVrog!v`+UmqEf!e zOBwlPD(&ODON~YGcgT+sGhz_%iJ=a2JXlTB#HvFD1jp zN^>oqpz>q6qlC65?-VyTbNq_NYJdzxzbOf1R($T;$XqU`8M>r@6)Mo5PoVu%zJdSi z)7{#eeOa!tK_f*P4Z)ii2f`WpEad``ak%u2^PHKh&mlAOdq$qo1PHS#2d|-wED!Tf zsJQ6t1I%%IE6~~o6d=ra`)<0E*#6?3j_$xP<0)4RHn0uEEnK4V%$s6O- zcC=GRRTa}W(i!a*7Z=SC;?39|dgU$WG^TvjdA1}8D0jBSV}*R+73)|DL?bB^2|*gK zyLD;xnlzY>wAI1jsw>9Fng7wL(^wE-G{L9rkZ32TMjFzO7dBmSpHR_W7zC<6= zI4P}KW>Wj|#BgyY%EQ5W4?hC4x)eIH`jQkD8Oy(NEhcrG#4C_a`;3mG_BP41yPcqo z8DE&p2Nt0P4)vFqxm%1vp#p;RxiOkQPH3u%QVJKBPs)A|z?IB&6vk5xGbgU=ere{j z)x25=GF%jXFTXrzHE2{xYJlj1vv8xe!RyTfpx~=2jc2f?s>5Jsp-P-FL;oa&7N*2` zUfD*o?0k8SYW%Res+Iot0|L9Gr;NIzJo6#Wqb^P4%U=);w$pA)eTDi}RLU|mtT&!> zORhG2i%{f6XoDQX?`Y|vG>b$fJ+{Ft;Gv@n7ON$xu zcbj%HKSmC?We=T^)4(0=8AvKpwoTpMq8$a8`2+GZ3)z|mI`Z-uY5Dwbr(|E77RUQs zTu1SHI&I*zZ87frv@0F0G&q?mMjWP942}+44>j~qlG)eS;y6g*0f=g(HY5t~^@ih1 z<=%ShG}RbZQsqC&4{-PTnX@~DKITUU8Ad%%YsIse=`xbNx27!0t{)g=ywyyW;~)xY zys{&_f19=6mB;f+i^18sdTB_%nnoq0H4-y?@OD+u@~k&a?ZfK=$bFyo?As7b>6xImYhWuhuNq0vkz#bIkcA)ow$MUbIj zJ1)NSdXi9tGx&ZNOlgMxfi7QqBCjn_fN;1NhoVV_^U)?o1B?dSm^)KDu_#l}8i z*^arVM(do)_EXhL2@3WlUbu8aC+g;sN%{J^`WLcZD2>0an_ z&@zf%tCC&3aPMc6zsQ(@Jo4*?$dw*?7Vg;0bQLJfW?8fF)NXpN*=%9vP^&!&zMyEz z(=05|#S*;V&f9PleFYqyTj5MMJkG}GN-kOy#>mTIs0emN+5K6&H@yFMN$_BwJ;Q@% z%n?BvUop4SYmiiC8+GU?nw6`P2p;5tZ(L%ke-G48m;fgb-Fho!-Y?JDLK?)X@mp%c z0O9rc?LM2Sk&uv^8c(62=)GH=_xtf47p7x3pOM#fzH&AjfYk&n7cyad!RY(RJsr!heeC)vU*i>r$IiPU)U*TtoKB$n|tlz1#M3n-*iM&3L6eod0YguWX3=)<0CfvdU z(j}yZy)1avP0D>Nl$FkW)#ods-zEmE6ld?_<{gP}Z)z>U9=wfSrw|gymx)g}SkV)S% z7y2SS%Oa+kb)tbt?pGnt)lu-)tgI>WYd9ckjM$2QNK-^^HCB~=+gS#3;)*BJgBqdI zV+OC8`b^EO2sna~RC2kTLnzBgYk1%)n$~t)q*8oYKG>vl(UMwcl{*1Oz=>a>;+iY_ zhv44cJ0hx8K|vM9bP**ODIWq@nry@`_(L@lfQ4eM{&Kx4jiB|8*WLSWO*?-EYp{1z zm&ycy{m&Cz;mW&oPYN)48N4aYz?HbYGH4}-C>+vG)d1Yi-3hd&^d%#&;Bk+4o)k;XdTN0Z{2}_{2ELWCj7EqWu^E|wa_!ULIO382%-Pj~~$FXu2Tr0cm#`-1MWwQn8(8M@A z2KXgK^otXVS`Dc zV)6^35x8~WRO#qs5#BgruexrcFulsktkNSG!JeU|XlMxwBEHifN~kqSN@6XWGq!3L zR+mlLj%nrbw=QdMiw3V>Q{kNa8USrhH6`h!wCwKh$vHBGXug;!mou*Ko(Z0v15&F2=tq8VeTpBJvM>`!=t0!T899}T z7Z)r^BPVfNZ3D9O-`6~yfK$PA6cnaWX(#9B2=!pt&fOZ#O^D2w;u+{TKcRB*y$-jr zI0jPML0rU;H>Oj?B>9E#If+sHNkkaS~Hc?sM~cu{L?sL3yxf3hUEStKdv{3++ue zGisTL8s;$fVmy=;wH;vPyrdT1#B^gvT@}LyzM_8Pp`Y`IH(!jJGws6juuJOZIhpo7 zxtVPbIexOLj8D$i(iPljkJKRf;R(QXuo4|z$LtfDRR@#exASb+uyX-rr!!Y0L*^7F zmi#HDoNKo+LnL7&c0QCnK1#D?Uc=1!*pz(YBpDj3I5ba7=8tl!37qRr}yNv!QoqIxfmtwz?cDJMU&~F_0>wMMr!>_M*tLgr!mlwBv+ptZ~AYNm`i@Bp6L! zC82pJTD;3-hnt02>yW~b;GxTlx~}(t13V$0lt9bV+bX8U^-LLBhCgp&>`(;z+$W{T zx#NjAma8o`t994R8wN3Bx+s6v_q6ZS|FFcD)6e?olYcRP0YlzLfdtg}m*lY@FyOYi zj(J?|JWLid5|&!oDv*mXo&HEn@Y^lcMqcV0R@nr;#)_?Jg+gsh0T;z3|9C=h-`mY3 zqRwh+6uT0DVm0nhVx{}vHrBV6OAD>Dnmrr94^J{ypJP~eQrjm{7r{qO2anW-Kh>}Y z%L2pFQL~MX?Qz0ezjla1asniHxpGH~+NrF!_|kIa=e@$S`E|!7JP=~{<4>bRys;@J z3vdr%1q~E4P`MvJT%fzKv8KR>_qyA}Zw`zw@YXyd1Q#*Q*x=MUp?_U{`fu&6 z^mXMp2E{VRWIUQd@N(qXQ9^^2Z!VXC-8sX79q{9Zr^qx-MQ}pp80F8`W(N)W{cth! zAU3=`BvO&kOU)tt5@$O2hd{7w5#hCBj(9?5w!Ll9FHA&_9+@>5t1T^Q?U!;1=&zp2 zd_c)#1`hPQd`xvw=b?S%KMZX*f~(fC5)LJ*Y;q--l8=Xs?fGf<*7P|T)G9$smC6M# zY_uGl6tmcZ4x3@Z@rbl4jIE%ZUvJ!$Pu6fsb~_6qV0oHG;wI3W*)QQOlQ7^6Hn=g{ zTjW9BGa~gl)*WiQPOPVSGy8j9c6MQnC(c>ZsFc>ZEeTxZFgEF-n>7?G*mLetW37d< zl2)wD%L1KKxI4+=H}WOjcuTIJf^4*QnJ)_!WiOv#fYq`rYFqaua?zMrYd`p6iR#)Y z2`NSiRs>Gqwr!!M@EIPK%U0s4JzuzmCwFDg zGnOqCv-u1J`)-n#4Y8nF{G{d?DkTy(6M6-{H1Dt~B`EV>BjJ78dm@Ytx7(^$7Z|Da zd`wb_f#O8KEWmi&ECa^Rv0UGPHY726`$ztrGf|n;5|-L22lwbC0TsOXz9%HV3ZmOs zr*+w1uFFL2V)amuVmN{Cg$t%MD+un6PVp{n3|DrPb@8!vvHQ8uqR_0XK)!b@7(L4y z16YV}Xs0=NH4ZO+p4qtQ_e@pKp<7(vt8k>Aztx$-Qr8iEoLEAY&k>E9^N%XTiQj!H zr*@?@+?KxQ?8HNhI!lYLDmYx$OPCR38Y|Lh14xVU}SoP)=F(OSQM6x=6u)BUG17&5#_82i)(DLviX8zKVvf- z0ZkjbDA&=MN>r z`Q?g$_SQ7^z*XI?*0e3D{DNiCbLkf#_=KxpIa6j?@70`@Q3JuVqs zsiwSSiTatUmOOk8XY^K1dJ8oqwBy}kyk6WN0jdhfXYMx+`3VoO{fWF=lZ`Kws}W`Y zQaS!7ye3~K-*V%t!bF|K4&M8%d7RR#wgq$DB7;yNO`H+;Iu^d*iXw^hh1tj!#_d1> zo!?ao984xy`r92Pf@#qv^X`9+F)j9qDbvMlG&6K8uBKc zGag@K!H+Lox^~3Bz=UBcG&}HpWlJEJ*r+j9%@G$_Yh%3aWD6z+E%X4C91r2v`{hr@ ze{MjX{C?n4TJLdj7m_K_agNOedtlhLSf5pCe3i%3zNNyFqi=Z-$#I8@?i)NbkdrG; zkn`vtlCn#g2Tz~}ufFYRk>4cX((tK&xK4dZ!OOJW08=&;;4Uisoj6+6;xT4x zF^Y!X6`1f%l7roCWD>dj%sprPu%bkQRD>WxTO%cZSqVp=;OrWKPd1$0m-eC!a{5M3 zk{d%?mJ4AZ6U;Xh?F!dc{GrdrMNOrI?XlKahS*S4d~o~=;{W(m2kW#8Fy6MP$NQT6 zajwhjtQvL6tthC+nJ=`otIKxjK;?a(R+4Ue^q8d)# z_~P};sy0ew{l!-ny0*&&A;j{OL5;7kn9Pc7RQ3GZ7F*aTVs7P=s17m?^`fsc{STi- zXnW6MAm_J)ZW!$uD<1z$5zsW54Fik!Uc1gtTAv_|U%Gn{ohqFNVA;MjwJ>wNw$(is zbson4LQCos;%sryV-68Q}KIM8m2Y2Jfst_jyvBe{`7d>vD(psVB zlFPeH^}I36D98KYR;bEn32OO@tEt(#usoPJfQkNgh#a&0bJJOVGt(tI)5;N-h_dyx zncZF>_im}hh-cPth~k7ZPDOFfx2~A_r0YT%&j`X^`(lXc! zaMStDy}^AUm+@uz+ERmuCd5AwX)GryzH@fdxoQ{-B-)^$K~xLoEqTv$f$#agQT0g# zCX5{%&OlFysxiRi6ntC%S+C_t?!3fM@kCX$T+Kkyr+`S6u`=YA&{U^{pK2Nq@abch z*0F9g4bOL)<<{qup3_rOSnC!pOxm8FH4z7(QQ#x>Q~{q(c$GY20WKB2#J*Xt4IC~} zxty&FqCzqC!TQ})#)?Uars1nQOl37L%$;^yq*Hy_V$0NEUy?6`frD`)Y5vT!a58W5 zP&toOzun4y&fcAKr2cQ?%K?#FrO=~AxwGP1M@k$)w=(&>hN~0=O#561*PhpM|5ETd z`)+`hy+byE^O%eF9Z$Dr|BHfIAy|W1QVovGuc+!3BTT%2(-mW^PY+0EoIGzN^PQi7 z^>cA-jRf3yKZlcj{;~KxfBHvTp2XtMeRs8%_W)z2`z!bm#jD}`E_*&W^SFZV+3#+R zuvK6Q5?Bg>`YUOksc$<+PqKQ`}uJ+9vnTA?| zzL+am`}!2N4bGY8kv+S^Za&svD1-r$l8ljg^B zbg?jPdxB9j_p2tpZ<8WkzBa|1)U(1{HD_~VZnG9!a=C22o;!Pw#Cr4y7@pe7!5p+L zYI0NOR&n5ciu_hTxyV09O3EXC?6m@%TbT>KJ$5EjOpOt&pc=&F42&ry zf1qcOusyK9iECMgC%)d%4^+Yn*2|sO-I|Rp8V;=|wO1vE(?c=rpagH*X5e7X+j-h6 qI46#}Ev2{F+}WJ*;_V}L(4Nrmr5}g!O^6ds(r*>s6pQPB{=Wc>0AIZT diff --git a/cla-frontend-contributor-console/src/ionic/assets/logo/lfx-easycla.png b/cla-frontend-contributor-console/src/ionic/assets/logo/lfx-easycla.png deleted file mode 100644 index 2e226188ddc1cd06f637fb1c9d310a369f3ee412..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3905 zcma)$UW z9RyL7-a<#wfS`aM#e?(T{paRfti9fg{k}7MX3v`S#F!ZCaSL=976*z{duBdsUu0 zby9rvt{Sp;j#$X~SJ`>3Za7A`uxnZ0n2CHXiLSEih%AugsmezL9jxno-E=ZPH8(ZS zm5NLvJ&G(TQwa>;>zLhN@CDu5iH6yp$?SGETxMS zE&f&H_5V|U?{l&73zCv-kP$aYm&Oc4y-1gkL9EOPmymi=1s#C1AmRTP^uL+f(du7A z!^`Y-!&Xt(3By8a2UzZ2!EH-P;dSNpzF#?a+h<`rzwm)QRN3o9c*~d?(J0Y?s~_9f zZ`il1_dD%X{CJ)U5o_5J&;+-UYh}%o75}}nzwZ=&D0e{rV7Xc}*1T7@U7J5>v1+p? zq<^jrE5XA0t+xY9C#P)eb7c&=AmJZNp&xKUqo2t=YvkB? z9KX*36X_w9t6oY-OFAyMCVs^>{o%pw7J*QrC1<)KUy!|y65g9zak*Pi!{l57Db+i2 ztA?WjMO21zdpGOtd+5oe$3->J;5vw*JoATy55h-6>`|&ATpkt4qth3xcQSnKmxP*I z8K2*Jazt_UXsrXJ2Q>2R^mTE=7OE^WymPPgdNe;I6WhaVJ@C?1adeOWAn2bCdGC}; zb3=m?C1sD>0>u_(LVZEaALLQIPKD5(uQrbEYdrp+p1noO;ocYfS$RGlmatZwe8<0$ z1=HsmvM^i0qvHm8eTS{2acbGsxd&FXTd&x@t@uk@A>Zw|J0!oBq^kjGAfhv{k;{)g zhD|z7}SBFU3!E;$5Fq(;UQqgPMEA&cY zte|&>Kvg!L1ljn@KKy+VlO<(-Fa29VVi~f`2}LpihQ4n<$Xkt=KrW~pB+7`T{i4HJ zUHkE!wO4MiLLAPiS^-e5X}y5Su?E}@kqT>XJf=;R6WD|yKN)NoB>P7E-IiRuhsCSM z`c}XbLB$<@iKyZnNz1|^6r{{EOKr4DFSo~ozTEVeC;dd%{s5`U z)O3}d`JjdIdS6C)Ge$sT8mEByM2=IZ8MUjExg;^ivNR^HZpJ-gZ+a)qyJ1V#WvQKwox;yud`Qd68^^N9abg1a_x*hG~4rcSeyn6^HfvW=&u8;?l+^IOBx(O*h89=>7*J;}gwi5pw#Yl(V+MRK%2 zYx&NzQJ)7<4j${t*-QYlKTjN*^hF17w_7ty?|eB2yd!<|q!;Z8=2sc51gzZkaX$AH zb~bDq(S(wx&6zQ!5-5lf#S=-CVnX&fyy zHR4R_<(Lyz?F!wRUv}xtp>SRt?zdhb)zVl+iz+vDfO7l6G)FT`@MmgoXbt&{BoKl0d2FJU2d@Fsh7Ja z6BW)OO8+g{E{Kr$BeTW9oXW6q(IzpI)XUlQI?eaebg^7P*6=+-O-mWTWg?xi@s%0rAR&KiJwXAh zc{Fx-so7jId*>)#ruKZCJ>zoI{YGcanhFO6-HNVeEfT||DyQUhTi8^`h!Zv)xA3jn zBfj|~B6vJ{n*MsJp20SB8~4sH+wz;HGtc1Au?Cl420a5g;<5R7n|;A_>I?(XpV>2V zqOxX0Zam-KIO3+?-Aq@mg3_^(VsMzhDK^p%bLw0LccO|*8aMi^Zmr|W;}$_sN;LRnw|)~xu_JupzH8lL&19fgZXb+jP5D2tiYWe$fO*| z<9gr4DqvFv>NrOHF8auhE$}R<_B8bFM8J*s1q)3`Q{Afwi=&KB-BB>FN-h8!KdW%B ze9ZTNkgEfB9#O7!9P#N>8>LT6Y1vc27XamNyy__%Ay^!S95ebPtF0?h?5kRJTP_vt z>C?SYJ?0Xc*O+wuGMW~eVkGLNE;Fnc^CEc0pKa@9NF$mf1izrpG4Om#*#(7lobxAa z_}uij23(jOKcQYt>~>65CUo&>Vx^1rjb`hF1E!ri3MYZ!#+AKne%#XTl=Vf$9%mlExWCp=8Vr^1?-o6aIc?6XDTDzLkRA!`dkba`4 zDcQg;kty6j1H$3>oVt`CR`(6ZCM8>+U0uYbsHuyxuMhJgyud#VgqH31rn`~N zWe|9vPH_RnU3g*8c|4t~QXVQrF{_Brz?x-txTk%8t6w9g$qdugW#WeaOikUFPITM2 zH^4-?oUhg*jp<70Y%I0L3{rhWEwvlL9L@su)c0+;^WG?Qx!cMb;kri5e)PQ-f@ zUyAz-&fzii&y)1(XOcMh<`oq>idCrcf*xi=jab%rtGn{ueMP;BV$)O?hEfFH#~`+A z*AU@(RL9o%+DITkIpt*<&sRt%+b1$nQxcea&wIGoxM_RiD0TN3k>z!_Tig|3xN7iD zwwls6md(OpPA>3JYQdh9-^s#TGRY~-u3Mm9wAW~Ra z7K~#ax3r%I1h$n$ki~W`=2cNz*jGY{kb1f8j(`WWRFZ9_t znco%VYH3bKxz%`NXdPBX_fYjy%Hf=u8YsN|e!d&RPwicg8dS*^1ZYcZ?XH!+r`!2M zgLGkZqvPL`>X1RZMve!@b4RYG+P&sk+hK0He7VRLo;2SGgS;{%x?~bDo_yB=l}gZWN4yIpRB37B-LvRyw9$ z^z`&x-=2zm1FNEpDhioWSEYF><{|IifC$3@WzGF-@Q=K)?(=Cub3$-h)s~fn{N>qf z#rG?N#PV_Z<^zLCWD7ahOc8eJwku7iKmH;J+e;_U8IeE2Zm|8=nLPUbuNA}n7Rir;|oZasYa mh2X#Z$P@Kn82Lvuc3FK08?Dq#kX=4wVmg$zu~r4rF7iJxR8sK( diff --git a/cla-frontend-contributor-console/src/ionic/claenv.d.ts b/cla-frontend-contributor-console/src/ionic/claenv.d.ts deleted file mode 100644 index 128b22c6c..000000000 --- a/cla-frontend-contributor-console/src/ionic/claenv.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -declare module '*.json' { - const value: any; - export default value; -} diff --git a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.html b/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.html deleted file mode 100644 index 1eb2ffb75..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.module.ts b/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.module.ts deleted file mode 100644 index 867e3dd9d..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicModule } from 'ionic-angular'; -import { GetHelpComponent } from './get-help'; - -@NgModule({ - declarations: [GetHelpComponent], - imports: [IonicModule], - exports: [GetHelpComponent] -}) -export class GetHelpComponentModule {} diff --git a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.scss b/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.scss deleted file mode 100644 index 5cf5769f3..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.scss +++ /dev/null @@ -1,21 +0,0 @@ -.sub-header { - position: absolute; - padding: 0.8rem 1rem; - right: 0; - background-color: #4c79b6; - width: 100%; -} - -button.help-btn { - background-color: #ffa400; - padding: 8px 16px; - color: #ffffff; - font-size: 12px; - border-radius: 200px; - cursor: pointer; - - a { - text-decoration: none; - color: white; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.ts b/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.ts deleted file mode 100644 index 8a0a02224..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/get-help/get-help.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'get-help', - templateUrl: 'get-help.html' -}) -export class GetHelpComponent { - - constructor() { - } - -} diff --git a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.html b/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.html deleted file mode 100644 index fd936e222..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.html +++ /dev/null @@ -1,3 +0,0 @@ -

        - -
        diff --git a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.module.ts b/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.module.ts deleted file mode 100644 index 6d2f929eb..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicModule } from 'ionic-angular'; -import { LoadingSpinnerComponent } from './loading-spinner'; - -@NgModule({ - declarations: [LoadingSpinnerComponent], - imports: [IonicModule], - exports: [LoadingSpinnerComponent] -}) -export class LoadingSpinnerComponentModule {} diff --git a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.scss b/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.scss deleted file mode 100644 index 88a396898..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.scss +++ /dev/null @@ -1,8 +0,0 @@ -loading-spinner { - .container { - position: relative; - margin: 1.5rem auto; - width: 28px; - height: 28px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.ts b/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.ts deleted file mode 100644 index 9052aadb4..000000000 --- a/cla-frontend-contributor-console/src/ionic/components/loading-spinner/loading-spinner.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Input, Component } from '@angular/core'; - -@Component({ - selector: 'loading-spinner', - templateUrl: 'loading-spinner.html' -}) -export class LoadingSpinnerComponent { - @Input('loading') - private loading: boolean; - - constructor() { - this.loading = true; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/constants/general.ts b/cla-frontend-contributor-console/src/ionic/constants/general.ts deleted file mode 100644 index 8592e0008..000000000 --- a/cla-frontend-contributor-console/src/ionic/constants/general.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const generalConstants = { - // Footer constants. - getHelpURL: 'https://jira.linuxfoundation.org/plugins/servlet/theme/portal/4/create/143', - acceptableUsePolicyURL: 'https://communitybridge.dev.platform.linuxfoundation.org/acceptable-use', - serviceSpecificTermsURL: 'https://communitybridge.dev.platform.linuxfoundation.org/service-terms', - platformUseAgreementURL: 'https://communitybridge.dev.platform.linuxfoundation.org/platform-use-agreement', - privacyPolicyURL: 'https://www.linuxfoundation.org/privacy/', - // End footer constants - linuxFoundationIdentityURL: 'https://identity.linuxfoundation.org/', - createTicketURL: 'https://jira.linuxfoundation.org/servicedesk/customer/portal/4', - easyCLAHelpURL: 'https://lf-docs-linux-foundation.gitbook.io/easycla/getting-started/easycla-faqs', - githubEmailURL: 'https://github.com/settings/emails', - easyCLADocURL: 'https://docs.linuxfoundation.org/lfx/easycla', - USER_MODEL: 'userModel', - PROJECT_MODEL: 'projectModel' - -} diff --git a/cla-frontend-contributor-console/src/ionic/declarations.d.ts b/cla-frontend-contributor-console/src/ionic/declarations.d.ts deleted file mode 100755 index 816172117..000000000 --- a/cla-frontend-contributor-console/src/ionic/declarations.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -/* - Declaration files are how the Typescript compiler knows about the type information(or shape) of an object. - They're what make intellisense work and make Typescript know all about your code. - - A wildcard module is declared below to allow third party libraries to be used in an app even if they don't - provide their own type declarations. - - To learn more about using third party libraries in an Ionic app, check out the docs here: - http://ionicframework.com/docs/v2/resources/third-party-libs/ - - For more info on type definition files, check out the Typescript docs here: - https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html -*/ -declare module '*'; diff --git a/cla-frontend-contributor-console/src/ionic/decorators/restricted.ts b/cla-frontend-contributor-console/src/ionic/decorators/restricted.ts deleted file mode 100644 index 208754ee3..000000000 --- a/cla-frontend-contributor-console/src/ionic/decorators/restricted.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -export function Restricted(restrictions: any) { - return function (target: Function) { - target.prototype.ionViewCanEnter = function () { - return true; - }; - }; -} diff --git a/cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.module.ts b/cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.module.ts deleted file mode 100644 index 632aa6cfc..000000000 --- a/cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { LoadingDisplayDirective } from './loading-display'; -@NgModule({ - declarations: [LoadingDisplayDirective], - exports: [LoadingDisplayDirective] -}) -export class LoadingDisplayDirectiveModule {} diff --git a/cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.ts b/cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.ts deleted file mode 100644 index 38dbb5d3a..000000000 --- a/cla-frontend-contributor-console/src/ionic/directives/loading-display/loading-display.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Directive, ElementRef, Renderer2, Input, OnChanges, SimpleChange } from '@angular/core'; - -/** - * Generated class for the LoadingDisplayDirective directive. - * - * See https://angular.io/docs/ts/latest/api/core/index/DirectiveMetadata-class.html - * for more info on Angular Directives. - */ -@Directive({ - selector: '[loading-display]' // Attribute selector -}) -export class LoadingDisplayDirective implements OnChanges { - @Input('loading-display') loadingDisplay: any; - - constructor(public element: ElementRef, public renderer: Renderer2) {} - ngOnInit() { - this.renderer.addClass(this.element.nativeElement, 'loading-display-initial'); - } - - ngOnChanges(changes: { [propertyName: string]: SimpleChange }) { - if (changes['loadingDisplay'] && !this.loadingDisplay) { - this.renderer.addClass(this.element.nativeElement, 'loading-display-loaded'); - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/index.html b/cla-frontend-contributor-console/src/ionic/index.html deleted file mode 100755 index 7598ff174..000000000 --- a/cla-frontend-contributor-console/src/ionic/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - EasyCLA - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.html b/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.scss b/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.ts b/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.ts deleted file mode 100644 index ac768eca3..000000000 --- a/cla-frontend-contributor-console/src/ionic/layout/cla-footer/cla-footer.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { ClaService } from '../../services/cla.service'; -import { generalConstants } from '../../constants/general'; - -@Component({ - selector: 'lfx-footer', - templateUrl: 'cla-footer.html' -}) -export class ClaFooter { - version: any; - releaseDate: any; - helpURL: string = generalConstants.getHelpURL; - acceptableUsePolicyURL: string = generalConstants.acceptableUsePolicyURL; - serviceSpecificTermsURL: string = generalConstants.serviceSpecificTermsURL; - platformUseAgreementURL: string = generalConstants.platformUseAgreementURL; - privacyPolicyURL: string = generalConstants.privacyPolicyURL; - documentationURL: string = generalConstants.easyCLADocURL; - constructor( - public claService: ClaService, - ) { - this.getReleaseVersion(); - } - - getReleaseVersion() { - this.claService.getReleaseVersion().subscribe((data) => { - this.version = data.version; - this.releaseDate = data.buildDate; - }) - } -} diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html b/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html deleted file mode 100644 index 0ff467fe9..000000000 --- a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - {{title}} - - - - - - - {{title}} - - diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.scss b/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.scss deleted file mode 100644 index cb8efa20a..000000000 --- a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.scss +++ /dev/null @@ -1,39 +0,0 @@ -.cla-header { - margin-top: 50px; - transition: margin 0.3s; - &.expanded { - margin-top: 105px; - } - - &.without-lfx-header { - margin-top: 0; - } - - .toolbar { - color: #7b7b7b; - background-color: white; - padding: 4px 25px; - height: 65px; - - &.with-lfx-header { - height: 55px; - } - - ion-icon { - color: #7b7b7b; - line-height: 6px; - - &:before { - font-size: 19px; - } - } - } - - .navbar-logo { - position: absolute; - left: calc(50% - 20px); - margin: -30px 0 -18px -138px; - width: 300px; - padding: 1rem; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.ts b/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.ts deleted file mode 100644 index cea0a74d5..000000000 --- a/cla-frontend-contributor-console/src/ionic/layout/cla-header/cla-header.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { Events, NavController } from 'ionic-angular'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@Component({ - selector: 'cla-header', - templateUrl: 'cla-header.html' -}) -export class ClaHeader { - @Input() title = ''; - @Input() hasShowBackBtn = false; - @Output() onToggle: EventEmitter = new EventEmitter(); - hasExpanded: boolean = true; - - constructor( - public navCtrl: NavController, - ) { } - - onToggled() { - this.hasExpanded = !this.hasExpanded; - this.onToggle.emit(this.hasExpanded); - } - - - backToProjects() { - this.navCtrl.pop(); - } - -} - diff --git a/cla-frontend-contributor-console/src/ionic/layout/layout.module.ts b/cla-frontend-contributor-console/src/ionic/layout/layout.module.ts deleted file mode 100644 index 74ca22dfc..000000000 --- a/cla-frontend-contributor-console/src/ionic/layout/layout.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { ClaFooter } from './cla-footer/cla-footer'; -import { IonicModule } from 'ionic-angular'; -import { ClaHeader } from './cla-header/cla-header'; -import { lfxHeader } from './lfx-header/lfx-header'; -import { GetHelpComponentModule } from '../components/get-help/get-help.module'; - -@NgModule({ - declarations: [ClaFooter, ClaHeader, lfxHeader], - imports: [IonicModule, GetHelpComponentModule], - exports: [ClaFooter, ClaHeader] -}) -export class LayoutModule { } diff --git a/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.html b/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.scss b/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.ts b/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.ts deleted file mode 100644 index 1efab8a34..000000000 --- a/cla-frontend-contributor-console/src/ionic/layout/lfx-header/lfx-header.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'lfx-header', - templateUrl: 'lfx-header.html' -}) - -export class lfxHeader { - @Input() expanded; - @Output() toggled: EventEmitter = new EventEmitter(); -} diff --git a/cla-frontend-contributor-console/src/ionic/manifest.json b/cla-frontend-contributor-console/src/ionic/manifest.json deleted file mode 100755 index 6165b5ef6..000000000 --- a/cla-frontend-contributor-console/src/ionic/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "cla-console-app", - "short_name": "cla-console-app", - "start_url": "index.html", - "display": "standalone", - "icons": [{ - "src": "assets/icon/favicon.png", - "sizes": "512x512", - "type": "image/png" - }], - "background_color": "#4e8ef7", - "theme_color": "#4e8ef7" -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.html deleted file mode 100644 index c58ab7455..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - Request Access - - - - - - - - -
        - - - -

        {{serverError}}

        -
        -
        - - -

        Please send an E-Mail to your CLA's manager to set up a Corporate CLA account and add you to their - approved list.

        -
        -
        - - - Company Name*: - - -

        Enter your company name.

        - -

        A valid name is required - at least 3 characters are expected.

        -
        -
        - - - - CLA Manager Name*: - - -

        Enter your CLA manager's name.

        - -

        A valid name is required - at least 3 characters are expected.

        -
        -
        - - - CLA Manager E-Mail*: - - -

        Enter CLA Manager E-Mail.

        - -

        A valid email address is required.

        -
        -
        - - - Your E-mail* - - {{ useremail }} - - -

        Select the E-Mail address associated with this company.

        - -

        A valid email address is required.

        -
        -
        - - - Your Name*: - - -

        Enter your name.

        - -

        A valid name is required - at least 3 characters are expected.

        -
        -
        -
        -
        -
        -
        - - - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.module.ts deleted file mode 100644 index 7c24563f3..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaCompanyAdminSendEmailModal } from './cla-company-admin-send-email-modal'; - -@NgModule({ - declarations: [ClaCompanyAdminSendEmailModal], - imports: [IonicPageModule.forChild(ClaCompanyAdminSendEmailModal)], - entryComponents: [ClaCompanyAdminSendEmailModal] -}) -export class ClaCompanyAdminSendEmailModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.scss deleted file mode 100644 index b822565ab..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.scss +++ /dev/null @@ -1,26 +0,0 @@ -cla-company-admin-send-email-modal { - .field-notice { - font-size: 1.2rem; - color: #999; - text-align: right; - } - .toolbar-title .member-company { - font-weight: normal; - } - - .error { - color: red; - font-size: 12px; - } - - .input-forward-button { - margin-top: 2.6rem; - } - - .field-description { - margin-top: 0.3rem; - padding-left: 16px; - font-size: 1.2rem; - color: #999; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.ts deleted file mode 100644 index ddf0f4c40..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-send-email-modal/cla-company-admin-send-email-modal.ts +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component, ViewChild } from '@angular/core'; -import { AlertController, IonicPage, ModalController, NavParams, ViewController } from 'ionic-angular'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { EmailValidator } from '../../validators/email'; -import { ClaService } from '../../services/cla.service'; -import { Content } from 'ionic-angular'; -import { generalConstants } from '../../constants/general'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId/invite-company-admin' -}) -@Component({ - selector: 'cla-company-admin-send-email-modal', - templateUrl: 'cla-company-admin-send-email-modal.html' -}) -export class ClaCompanyAdminSendEmailModal { - projectId: string; - companyId: string; - companyName: string; - userId: string; - authenticated: boolean; // true if coming from gerrit/corporate - userEmails: Array; - form: FormGroup; - serverError: string = ''; - isSendClicked = false; - @ViewChild('pageTop') pageTop: Content; - - constructor( - public navParams: NavParams, - public modalCtrl: ModalController, - public viewCtrl: ViewController, - public alertCtrl: AlertController, - private formBuilder: FormBuilder, - private claService: ClaService - ) { - this.userEmails = []; - this.projectId = navParams.get('projectId'); - // May be empty - this.companyId = navParams.get('companyId'); - // May be empty - this.companyName = navParams.get('companyName'); - this.userId = navParams.get('userId'); - this.authenticated = navParams.get('authenticated'); - this.form = formBuilder.group({ - company_name: ['', Validators.compose([Validators.required, Validators.minLength(3)])], - contributor_name: ['', Validators.compose([Validators.required, Validators.minLength(3)])], - contributor_email: ['', Validators.compose([Validators.required, EmailValidator.isValid])], - cla_manager_name: ['', Validators.compose([Validators.required, Validators.minLength(3)])], - cla_manager_email: ['', Validators.compose([Validators.required, EmailValidator.isValid])], - }); - } - - ngOnInit() { - this.getUserEmails(); - } - - getUserEmails() { - const user = JSON.parse(localStorage.getItem(generalConstants.USER_MODEL)); - if (user) { - this.userEmails = user.user_emails || []; - if (user.lf_email && this.userEmails.indexOf(user.lf_email) == -1) { - this.userEmails.push(user.lf_email); - } - } else { - console.warn('Unable to retrieve user.'); - } - } - - dismiss() { - this.viewCtrl.dismiss(); - } - - emailSent() { - let alert = this.alertCtrl.create({ - title: 'E-Mail Sent!', - subTitle: 'An E-Mail has been sent. Please wait for your CLA Manager to add you to your company approved list.', - buttons: ['Dismiss'] - }); - alert.present(); - } - - submit() { - this.isSendClicked = true; - if (this.form.valid) { - this.claService.getProject(this.projectId).subscribe((response) => { - // Instead of creating a company we need to send email to CLA Manager. - this.sendRequest(response); - }); - } - } - - sendRequest(project) { - this.serverError = ''; - let data = { - contributorName: this.form.value.contributor_name, - contributorEmail: this.form.value.contributor_email, - claManagerName: this.form.value.cla_manager_name, - claManagerEmail: this.form.value.cla_manager_email, - projectName: project.project_name, - companyName: this.form.value.company_name, - version: 'v1' - }; - - this.claService.postEmailToCompanyAdmin(this.userId, data).subscribe( - (response) => { - this.isSendClicked = false; - this.emailSent(); - this.dismiss(); - }, - (exception) => { - this.isSendClicked = false; - const errorObj = JSON.parse(exception._body); - if (errorObj) { - this.serverError = errorObj.Message; - this.pageTop.scrollToTop(); - } - } - ); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.html deleted file mode 100644 index 947b23715..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - Verify Your Permission of Access - - - - - - - - -
        -
        -

        - - Are You a CLA Manager? -

        - -

        - Can you manage CLAs on behalf of your company? Are you authorized to approve the people who contribute on behalf - of your company? -

        - - - -
        -
        -
        - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.module.ts deleted file mode 100644 index e11dba4af..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaCompanyAdminYesnoModal } from './cla-company-admin-yesno-modal'; - -@NgModule({ - declarations: [ClaCompanyAdminYesnoModal], - imports: [ - // ClipboardModule, - IonicPageModule.forChild(ClaCompanyAdminYesnoModal) - ], - entryComponents: [ClaCompanyAdminYesnoModal] -}) -export class ClaCompanyAdminYesnoModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.scss deleted file mode 100644 index 3943f4c48..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.scss +++ /dev/null @@ -1,25 +0,0 @@ -cla-company-admin-yesno-modal { - .content-container { - max-width: 500px; - margin: 0 auto; - } - - .clearfix { - padding-top: 1px; - &::after { - content: ''; - display: block; - clear: both; - } - ion-icon { - margin-right: 1rem; - margin-bottom: 1rem; - } - [icon-left] { - float: left; - } - [icon-large] { - font-size: 9rem; - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.ts deleted file mode 100644 index eb1627155..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-company-admin-yesno-modal/cla-company-admin-yesno-modal.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { NavParams, ViewController, ModalController, IonicPage } from 'ionic-angular'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId/admin-yesno' -}) -@Component({ - selector: 'cla-company-admin-yesno-modal', - templateUrl: 'cla-company-admin-yesno-modal.html' -}) -export class ClaCompanyAdminYesnoModal { - projectId: string; - companyId: string; - companyName: string; - userId: string; - authenticated: boolean; //true if coming from gerrit/corporate - consoleLink: string; - - constructor( - public navParams: NavParams, - public viewCtrl: ViewController, - public modalCtrl: ModalController - ) { - this.projectId = navParams.get('projectId'); - // May be empty - this.companyId = navParams.get('companyId') || ''; - // May be empty - this.companyName = navParams.get('companyName') || ''; - this.userId = navParams.get('userId'); - this.authenticated = navParams.get('authenticated'); - this.consoleLink = EnvConfig['corp-console-link']; - } - - dismiss() { - this.viewCtrl.dismiss(); - } - - openCompanyAdminConsoleLink() { - window.open(this.consoleLink, '_blank'); - } - - openCompanyAdminSendEmail() { - let modal = this.modalCtrl.create('ClaCompanyAdminSendEmailModal', { - projectId: this.projectId, - companyId: this.companyId, - companyName: this.companyName, - userId: this.userId, - authenticated: this.authenticated - }); - modal.present(); - this.dismiss(); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.html deleted file mode 100644 index d53c4e7c3..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - Request Company Access to {{trimCharacter(company.company_name, 20)}} - - - - - - - - - -
        - - - -

        After you click send you will have to wait for {{ company.company_name }} CLA Manager to add you to their - approved list before you can complete your employee contributor CLA process. -

        -
        -
        - - - - - - Please select how you want to contact CLA Manager* - - - Select a CLA Manager from a list of options - - - - Enter CLA Manager Directly - - - -

        * Selecting an option is required.

        -
        -
        - -
        -
        - - - {{managers.length > 1 ? 'Select ': ''}}CLA Manager for {{ company.company_name }} - - - - {{ manager.username }} / {{ manager.lfEmail }} - - - - - - - - - - Enter a CLA Manager's Name and Email - - - CLA Manager Name: - - -

        - Type your CLA manager's name. - - * CLA manager's name is required. - -

        - - - CLA Manager E-Mail: - - -

        - Type an E-Mail. - - * A valid email address is required. - -

        -
        -
        - - - - Enter Your Name - - - - Your Name: - - -

        Add your name to help identify you to your CLA Manager. - - * Name is required - -

        -
        -
        - - - {{userEmails.length > 1 ? 'Select ': ''}}Your Email to Authorize - - - - {{ email }} - - - -

        - Select the email address attached to your account that you would like your - company's CLA Manager to approve. - - * A valid email address is required - -

        - -
        -
        - - - - Enter Message - - - - Message: - - -

        Explain to your CLA Manager who you are and why you would like to contribute to - {{project.project_name}} as an - employee of {{ company.company_name}}. - - * Message is required - -

        -
        -
        -
        -
        -
        - - - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.module.ts deleted file mode 100644 index 25a4de425..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaEmployeeRequestAccessModal } from './cla-employee-request-access-modal'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; - -@NgModule({ - declarations: [ClaEmployeeRequestAccessModal], - imports: [LoadingSpinnerComponentModule, IonicPageModule.forChild(ClaEmployeeRequestAccessModal)], - entryComponents: [ClaEmployeeRequestAccessModal] -}) -export class ClaEmployeeRequestAccessModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.scss deleted file mode 100644 index 08c6f8674..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.scss +++ /dev/null @@ -1,32 +0,0 @@ -cla-employee-request-access-modal { - .field-notice { - font-size: 1.2rem; - color: #999; - text-align: right; - } - .toolbar-title .member-company { - font-weight: normal; - } - - .input-forward-button { - margin-top: 2.6rem; - } - - .field-description { - margin-top: 0.3rem; - padding-left: 16px; - font-size: 1.2rem; - color: #999; - } - - .company-title { - font-size: 18px; - .name { - color: #4c79b6; - } - } - - .error-message { - color: #ff0000; - } -} \ No newline at end of file diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.ts deleted file mode 100644 index 95d88ed2d..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-employee-request-access-modal/cla-employee-request-access-modal.ts +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { AlertController, IonicPage, ModalController, NavParams, ViewController } from 'ionic-angular'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { EmailValidator } from '../../validators/email'; -import { ClaService } from '../../services/cla.service'; -import { generalConstants } from '../../constants/general'; - -@IonicPage({ - segment: 'cla/project/:projectId/repository/:repositoryId/user/:userId/employee/company/contact' -}) -@Component({ - selector: 'cla-employee-request-access-modal', - templateUrl: 'cla-employee-request-access-modal.html' -}) -export class ClaEmployeeRequestAccessModal { - project: any; - projectId: string; - userId: string; - companyId: string; - company: any; - authenticated: boolean; - cclaSignature: any; - managers: any; - formErrors: any[]; - - userEmails: Array = []; - - form: FormGroup; - submitAttempt: boolean = false; - currentlySubmitting: boolean = false; - loading: any; - showManagerSelectOption: boolean; - showManagerEnterOption: boolean; - - constructor( - public navParams: NavParams, - public modalCtrl: ModalController, - public viewCtrl: ViewController, - public alertCtrl: AlertController, - private formBuilder: FormBuilder, - private claService: ClaService - ) { - this.getDefaults(); - this.loading = true; - this.project = {}; - this.company = {}; - - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - this.companyId = navParams.get('companyId'); - this.authenticated = navParams.get('authenticated'); - - this.form = formBuilder.group({ - user_email: ['', Validators.compose([Validators.required, EmailValidator.isValid])], - user_name: ['', Validators.compose([Validators.required, Validators.minLength(3)])], - message: ['', Validators.compose([Validators.required])], - recipient_name: [''], - recipient_email: [''], - manager: [''], - managerOptions: ['', Validators.compose([Validators.required])] - }); - this.managers = []; - this.formErrors = []; - } - - saveManagerOption() { - const option = this.form.value.managerOptions; - if (option === 'select manager') { - this.showManagerSelectOption = true; - this.showManagerEnterOption = false; - this.resetFormValues('recipient_name'); - this.resetFormValues('recipient_email'); - this.form.controls['recipient_name'].clearValidators(); - this.form.controls['recipient_email'].clearValidators(); - } else if (option === 'enter manager') { - this.showManagerSelectOption = false; - this.showManagerEnterOption = true; - if (this.managers.length > 1) { - this.resetFormValues('manager'); - } - this.form.controls['recipient_name'].setValidators(Validators.compose([Validators.required])); - this.form.controls['recipient_email'].setValidators(Validators.compose([Validators.required, EmailValidator.isValid])); - } - this.form.controls['recipient_name'].updateValueAndValidity(); - this.form.controls['recipient_email'].updateValueAndValidity(); - } - - resetFormValues(value) { - return this.form.controls[value].reset(); - } - - getCLAManagerDetails(managerId) { - return this.managers.find((manager) => { - return (manager.userID === managerId); - }); - } - - getDefaults() { - this.userEmails = []; - } - - ngOnInit() { - this.project = JSON.parse(localStorage.getItem(generalConstants.PROJECT_MODEL)); - this.getUserEmails(); - this.getCompany(); - this.getProjectSignatures(); - } - - getUserEmails() { - const user = JSON.parse(localStorage.getItem(generalConstants.USER_MODEL)); - if (user) { - // For Gerrit user user_emails is always null and only have a LF_email. - this.userEmails = user.user_emails || []; - - if (user.lf_email && this.userEmails.indexOf(user.lf_email) == -1) { - this.userEmails.push(user.lf_email); - } - - if (this.userEmails.length === 1) { - this.form.controls['user_email'].setValue(user.user_emails[0]); - } - } - } - - getCompany() { - this.claService.getCompany(this.companyId).subscribe((response) => { - this.company = response; - }); - } - - insertAndSortManagersList(manager) { - this.managers.push(manager); - this.managers.sort((first, second) => { - return first.username.toLowerCase() - second.username.toLowerCase(); - }); - } - - getProjectSignatures() { - // Get CCLA Company Signatures - should just be one - this.loading = true; - this.claService.getCompanyProjectSignatures(this.companyId, this.projectId).subscribe( - (response) => { - this.loading = false; - if (response.signatures) { - let cclaSignatures = response.signatures.filter((sig) => sig.signatureType === 'ccla'); - if (cclaSignatures.length) { - this.cclaSignature = cclaSignatures[0]; - if (this.cclaSignature.signatureACL != null) { - if (this.cclaSignature.signatureACL.length === 1) { - this.form.controls['manager'].setValue(this.cclaSignature.signatureACL[0].userID); - } - for (let manager of this.cclaSignature.signatureACL) { - this.insertAndSortManagersList({ - userID: manager.userID, - username: manager.username, - lfEmail: manager.lfEmail - }); - } - } - } - } - }, - (exception) => { - this.loading = false; - } - ); - } - - // ContactUpdateModal modal dismiss - dismiss() { - this.viewCtrl.dismiss(); - } - - submit() { - this.submitAttempt = true; - this.currentlySubmitting = true; - this.formErrors = []; - - if (!this.form.valid) { - this.getFormValidationErrors(); - this.currentlySubmitting = false; - return; - } - - let managerEmail = ''; - let managerUsername = ''; - - // 'select manager' or 'enter manager' - if (this.form.value.manager && this.form.value.managerOptions === 'select manager') { - managerEmail = this.getCLAManagerDetails(this.form.value.manager).lfEmail; - managerUsername = this.getCLAManagerDetails(this.form.value.manager).username; - } else { - managerEmail = this.form.value.recipient_email; - managerUsername = this.form.value.recipient_name; - } - - let data = { - contributorId: this.userId, - contributorName: this.form.value.user_name, - contributorEmail: this.form.value.user_email, - message: this.form.value.message, - recipientName: managerUsername, - recipientEmail: managerEmail, - }; - - this.claService.requestToBeOnCompanyApprovedList(this.userId, this.companyId, this.projectId, data) - .subscribe((response) => { - this.loading = true; - this.emailSent(); - }, (error) => { - this.loading = true; - this.emailSentError(error); - }); - } - - emailSent() { - this.loading = false; - let message = this.authenticated - ? "Thank you for contacting your company's administrators. Once the CLA is signed and you are authorized, please navigate to the Agreements tab in the Gerrit Settings page and restart the CLA signing process" - : "Thank you for contacting your company's administrators. Once the CLA is signed and you are authorized, you will have to complete the CLA process from your existing pull request."; - let alert = this.alertCtrl.create({ - title: 'E-Mail Successfully Sent!', - subTitle: message, - buttons: ['Dismiss'] - }); - alert.onDidDismiss(() => this.dismiss()); - alert.present(); - } - - emailSentError(error) { - this.loading = false; - let message = `The request already exists for you. Please ask the CLA Manager to log into the EasyCLA Corporate Console and authorize you using one of the available methods.`; - let alert = this.alertCtrl.create({ - title: 'Problem Sending Request', - subTitle: message, - buttons: ['Dismiss'] - }); - alert.onDidDismiss(() => this.dismiss()); - alert.present(); - } - - getFormValidationErrors() { - let message; - Object.keys(this.form.controls).forEach((key) => { - const controlErrors = this.form.get(key).errors; - if (controlErrors != null) { - Object.keys(controlErrors).forEach((keyError) => { - switch (key) { - case 'managerOptions': - message = `*Selecting an Option for Entering a CLA Manager is ${keyError}`; - break; - case 'user_name': - message = `*User Name Field is ${keyError}`; - break; - case 'user_email': - message = `*Email Authorize Field is ${keyError}`; - break; - case 'recipient_email': - message = `*Receipent Email Field is ${keyError}`; - break; - case 'message': - message = `*Message Field is ${keyError}`; - break; - - default: - message = `Check Fields for errors`; - } - this.formErrors.push({ - message - }); - }); - } - }); - } - - trimCharacter(text, length) { - return text.length > length ? text.substring(0, length) + '...' : text; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.html deleted file mode 100644 index cd1f3e004..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - Corporate CLA - - - - - - - - -
        - -
        - -

        Corporate CLA

        -

        - Directions on who signs this and what happens. - The link will direct user to CLA corporate console. -

        -
        -
        - - -
        -
        - - - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.module.ts deleted file mode 100644 index 33966d9a4..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaNewCompanyModal } from './cla-new-company-modal'; - -// import { ClipboardModule } from 'ngx-clipboard'; - -@NgModule({ - declarations: [ClaNewCompanyModal], - imports: [ - // ClipboardModule, - IonicPageModule.forChild(ClaNewCompanyModal) - ], - entryComponents: [ClaNewCompanyModal] -}) -export class ClaNewCompanyModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.scss deleted file mode 100644 index 783aea051..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.scss +++ /dev/null @@ -1,60 +0,0 @@ -cla-new-company-modal { - .content-container { - max-width: 500px; - margin: 0 auto; - } - - .clearfix { - padding-top: 1px; - &::after { - content: ''; - display: block; - clear: both; - } - ion-icon { - margin-right: 1rem; - margin-bottom: 1rem; - } - [icon-left] { - float: left; - } - [icon-large] { - font-size: 9rem; - } - } - - .simple-sharing-link-management { - .link-management-container { - .link-management-text-container { - background-color: #f1f1f1; - border: 1px solid #d8d8d8; - .link-management-text { - border-right: 1px solid #d8d8d8; - padding: 1rem 2rem; - .link-management-label { - } - } - .link-management-copy-link-button { - padding: 0; - overflow: hidden; - button { - margin: 0; - width: 100%; - } - } - } - .link-management-url-container { - border: 1px solid #d8d8d8; - border-top-width: 0; - padding: 1rem; - .link-management-url-input { - border: none; - width: 100%; - } - .link-management-url-text-area { - display: none; - } - } - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.ts deleted file mode 100644 index f65b569c1..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-new-company-modal/cla-new-company-modal.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component, ElementRef, ViewChild } from '@angular/core'; -import { NavParams, ViewController, IonicPage } from 'ionic-angular'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@IonicPage({ - segment: 'cla/project/:projectId/new-company' -}) -@Component({ - selector: 'cla-new-company-modal', - templateUrl: 'cla-new-company-modal.html' -}) -export class ClaNewCompanyModal { - projectId: string; - repositoryId: string; - userId: string; - consoleLink: string; - - @ViewChild('textArea') textArea: ElementRef; - - constructor( - public navParams: NavParams, - public viewCtrl: ViewController - ) { - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - this.getDefaults(); - } - - getDefaults() { - this.consoleLink = EnvConfig['corp-console-link']; - } - - dismiss() { - this.viewCtrl.dismiss(); - } - - openConsoleLink() { - window.open(this.consoleLink, '_blank'); - } - - copyText() { - let copyTextarea = this.textArea.nativeElement; - copyTextarea.select(); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.html deleted file mode 100644 index f77aaeada..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - Next Step - - - - - - - - - - - -

        Determining Your Next Step

        -
        -
        -

        You need to sign an ICLA

        -

        {{ project.project_name }} requires contributors covered by a corporate CLA to also sign an individual CLA. Click the button below to sign an individual CLA.

        - - -
        -
        -

        - You are done! -

        -

        You've completed the CLA steps necessary to contribute. You can now return to writing awesome stuff.

        -

        - If you had a pull request in process you may need to refresh the page to see the updated checks.

        -

        - If you were logged in to your Gerrit Instance, you may need to log out and sign-in again.

        - -

        - You may close this browser tab now and return to your repository page. -

        -
        -
        -
        -
        -
        -
        diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.module.ts deleted file mode 100644 index fe1131778..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaNextStepModal } from './cla-next-step-modal'; - -@NgModule({ - declarations: [ClaNextStepModal], - imports: [IonicPageModule.forChild(ClaNextStepModal)], - entryComponents: [ClaNextStepModal] -}) -export class ClaNextStepModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.scss deleted file mode 100644 index 5665a9b16..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.scss +++ /dev/null @@ -1,14 +0,0 @@ -cla-next-step-modal { - .field-notice { - font-size: 1.2rem; - color: #999; - text-align: right; - } - .toolbar-title .member-company { - font-weight: normal; - } - - .input-forward-button { - margin-top: 2.6rem; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.ts deleted file mode 100644 index a5e33da37..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-next-step-modal/cla-next-step-modal.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { NavController, NavParams, ViewController, IonicPage } from 'ionic-angular'; -import { ClaService } from '../../services/cla.service'; - -@IonicPage({ - segment: 'cla-next-step-modal' -}) -@Component({ - selector: 'cla-next-step-modal', - templateUrl: 'cla-next-step-modal.html', - providers: [] -}) -export class ClaNextStepModal { - projectId: string; - userId: string; - project: any; - signature: any; - userIsDone: boolean; - loading: any; - signingType: string; // "Gerrit" / "Github" - - constructor( - public navCtrl: NavController, - public navParams: NavParams, - public viewCtrl: ViewController, - private claService: ClaService - ) { - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - this.project = navParams.get('project'); - this.signature = navParams.get('signature'); - this.signingType = navParams.get('signingType'); - this.getDefaults(); - } - - getDefaults() { - this.loading = { - icla: true - }; - } - - ngOnInit() { - let requiresIcla = this.project.project_ccla_requires_icla_signature; - if (!requiresIcla) { - this.userIsDone = true; - this.loading.icla = false; - } else { - this.claService.getLastIndividualSignature(this.userId, this.projectId).subscribe((response) => { - if (response === null) { - // User has no icla, they need one - this.userIsDone = false; - } else { - // get whether icla is up to date - if (response.requires_resigning) { - this.userIsDone = false; - } else { - this.userIsDone = true; - } - } - this.loading.icla = false; - }); - } - } - - dismiss() { - this.viewCtrl.dismiss(); - } - - openIclaPage() { - this.navCtrl.push('ClaIndividualPage', { - projectId: this.projectId, - userId: this.userId - }); - } - - openGerritIclaPage() { - this.claService.getProjectGerrits(this.projectId).subscribe((gerrits) => { - if (gerrits.length) { - // picking the first Gerrit Instance will suffice in supplying a Gerrit ID, - // since all Gerrit Instances in the response will be under the same CLA Group. - let gerrit = gerrits[0]; - this.navCtrl.push('ClaGerritIndividualPage', { - gerritId: gerrit.gerrit_id - }); - } - }); - } - - gotoRepo() { - window.open(this.signature.signature_return_url, '_self'); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.html deleted file mode 100644 index 3accec00e..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Select Company - - - - - - - - - - - - -
        - - - Search: - - - - - -
        - - - - - - - - - - -
        - - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.module.ts deleted file mode 100644 index 5517c1d71..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaSelectCompanyModal } from './cla-select-company-modal'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; -import { LoadingDisplayDirectiveModule } from '../../directives/loading-display/loading-display.module'; - -@NgModule({ - declarations: [ClaSelectCompanyModal], - imports: [ - LoadingSpinnerComponentModule, - LoadingDisplayDirectiveModule, - IonicPageModule.forChild(ClaSelectCompanyModal) - ], - entryComponents: [ClaSelectCompanyModal] -}) -export class ClaSelectCompanyModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.scss deleted file mode 100644 index c712c5759..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.scss +++ /dev/null @@ -1,14 +0,0 @@ -cla-select-company-modal { - .field-notice { - font-size: 1.2rem; - color: #999; - text-align: right; - } - .toolbar-title .member-company { - font-weight: normal; - } - - .input-forward-button { - margin-top: 2.6rem; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.ts deleted file mode 100644 index 5eb083a39..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-select-company-modal/cla-select-company-modal.ts +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { IonicPage, ModalController, NavController, NavParams, ViewController } from 'ionic-angular'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { ClaService } from '../../services/cla.service'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId/employee/company' -}) -@Component({ - selector: 'cla-select-company-modal', - templateUrl: 'cla-select-company-modal.html', - providers: [] -}) -export class ClaSelectCompanyModal { - form: FormGroup; - loading: any; - projectId: string; - repositoryId: string; - userId: string; - selectCompanyModalActive: boolean = false; - authenticated: boolean; - signature: string; - companies: any; - companiesFiltered: any; - - constructor( - public navCtrl: NavController, - public navParams: NavParams, - public viewCtrl: ViewController, - private modalCtrl: ModalController, - public formBuilder: FormBuilder, - private claService: ClaService - ) { - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - this.authenticated = navParams.get('authenticated'); - this.getDefaults(); - this.form = formBuilder.group({ - search: ['', Validators.compose([Validators.required])] - }); - } - - getDefaults() { - this.loading = { - companies: true, - activateSpinner: false - }; - this.companies = []; - } - - ngOnInit() { - this.getCompanies(); - } - - dismiss() { - this.viewCtrl.dismiss(); - } - - getCompanies() { - this.loading.companies = true; - this.claService.getAllCompanies().subscribe((response) => { - this.loading.companies = false; - if (response) { - // Cleanup - Remove any companies that don't have a name - this.companies = response.filter((company) => { - return company.company_name && company.company_name.trim().length > 0; - }); - - // Reset our filtered search - this.form.value.search = ''; - this.companiesFiltered = this.companies; - } - }); - } - - openClaEmployeeCompanyConfirmPage(company) { - // set loading spinner to true when a company is selected - this.loading.activateSpinner = true; - if (this.selectCompanyModalActive) { - return false; - } - this.selectCompanyModalActive = true; - - let data = { - project_id: this.projectId, - company_id: company.company_id, - user_id: this.userId - }; - - this.claService.postCheckAndPreparedEmployeeSignature(data).subscribe((response) => { - /* - Before an employee begins the signing process, ensure that - 1. The given project, company, and user exists - 2. The company signatory has signed the CCLA for their company. - 3. The user is included as part of the approved list of the CCLA that the company signed. - the CLA service will throw an error if any of the above is false. - */ - this.loading.activateSpinner = false; - let errors = response.hasOwnProperty('errors'); - console.log(`errors: ${errors}`); - this.selectCompanyModalActive = false; - if (errors) { - if (response.errors.hasOwnProperty('missing_ccla')) { - // When the company does NOT have a CCLA with the project: {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} - console.log(`errors - missing_ccla: ${response}`); - //this.openClaSendClaManagerEmailModal(company); - this.openClaCompanyAdminYesnoModal(company); - } - - if (response.errors.hasOwnProperty('ccla_approval_list')) { - console.log(`errors - ccla_approval_list: ${response}`); - // When the user is not whitelisted with the company: return {'errors': {'ccla_approval_list': 'No user email authorized for this ccla'}} - this.openClaEmployeeCompanyTroubleshootPage(company); - return; - } - } else { - // No Errors, expect normal signature response - this.signature = response; - this.navCtrl.push('ClaEmployeeCompanyConfirmPage', { - projectId: this.projectId, - repositoryId: this.repositoryId, - userId: this.userId, - companyId: company.company_id, - signingType: 'Github' - }); - } - }); - } - - openClaSendClaManagerEmailModal(company) { - let modal = this.modalCtrl.create('ClaSendClaManagerEmailModal', { - projectId: this.projectId, - userId: this.userId, - companyId: company.company_id, - authenticated: this.authenticated - }); - modal.present(); - } - - openClaCompanyAdminYesnoModal(company) { - let modal = this.modalCtrl.create('ClaCompanyAdminYesnoModal', { - projectId: this.projectId, - companyId: company == null ? '' : company.company_id, - companyName: company == null ? '' : company.company_name, - userId: this.userId, - authenticated: false // Github users are not authenticated. - }); - modal.present(); - this.dismiss(); - } - - openClaEmployeeCompanyTroubleshootPage(company) { - this.navCtrl.push('ClaEmployeeCompanyTroubleshootPage', { - projectId: this.projectId, - repositoryId: this.repositoryId, - userId: this.userId, - companyId: company.company_id, - gitService: 'GitHub' - }); - } - - onSearch() { - const searchTerm = this.form.value.search; - if (searchTerm === '') { - this.companiesFiltered = this.companies; - } else { - this.companiesFiltered = this.companies.filter((a) => { - return a.company_name.toLowerCase().includes(searchTerm.toLowerCase()); - }); - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.html b/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.html deleted file mode 100644 index 5f28a18ac..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - {{trimCharacter(company.company_name,20)}} - has not signed a CCLA for - {{trimCharacter(project.project_name,10)}} - - - - - - - - -
        - - - -

        Please check the fields below for errors.

        -
        -
        - - -

        Your company {{ company.company_name }} has not signed a Corporate CLA yet. Would - you like to send an E-Mail Notification to the CLA Manager to sign the Corporate CLA?

        -
        - - CCLA Approval List request already exists for you. - -
        - - - - Enter Your Name - - - - Your Name: - - -

        Add your name to help identify you to your CLA Manager. - - * Name is required - -

        -
        -
        - - - - - Email to Authorize - - {{ email }} - - -

        Select the email address attached to your account that you would like your - company's CLA Manager to approve.

        - -

        * A valid email address is required.

        -
        -
        -
        -
        -
        -
        - - - - - - - - - diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.module.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.module.ts deleted file mode 100644 index 9a20c1da1..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaSendClaManagerEmailModal } from './cla-send-cla-manager-email-modal'; - -@NgModule({ - declarations: [ClaSendClaManagerEmailModal], - imports: [IonicPageModule.forChild(ClaSendClaManagerEmailModal)], - entryComponents: [ClaSendClaManagerEmailModal] -}) -export class ClaSendClaManagerEmailModalModalModule {} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.scss b/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.scss deleted file mode 100644 index b10ba483f..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.scss +++ /dev/null @@ -1,34 +0,0 @@ -cla-send-cla-manager-email-modal { - .field-notice { - font-size: 1.2rem; - color: #999; - text-align: right; - } - .toolbar-title .member-company { - font-weight: normal; - } - - .input-forward-button { - margin-top: 2.6rem; - } - - .field-description { - margin-top: 0.3rem; - padding-left: 16px; - font-size: 1.2rem; - color: #999; - } - - .name { - color: #4c79b6; - } - - .error { - color: red; - } - - .toolbar-title { - font-size: 15px !important; - font-weight: bold; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.ts b/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.ts deleted file mode 100644 index a5c0eb732..000000000 --- a/cla-frontend-contributor-console/src/ionic/modals/cla-send-cla-manager-email-modal/cla-send-cla-manager-email-modal.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { AlertController, IonicPage, NavParams, ViewController } from 'ionic-angular'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { EmailValidator } from '../../validators/email'; -import { ClaService } from '../../services/cla.service'; -import { generalConstants } from '../../constants/general'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId/employee/company/contact' -}) -@Component({ - selector: 'cla-send-cla-manager-email-modal', - templateUrl: 'cla-send-cla-manager-email-modal.html' -}) -export class ClaSendClaManagerEmailModal { - projectId: string; - userId: string; - companyId: string; - authenticated: boolean; - hasRequestError: boolean = false; - company: any; - project: any; - userEmails: Array; - form: FormGroup; - submitAttempt: boolean = false; - currentlySubmitting: boolean = false; - - constructor( - public navParams: NavParams, - public viewCtrl: ViewController, - public alertCtrl: AlertController, - private formBuilder: FormBuilder, - private claService: ClaService - ) { - this.getDefaults(); - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - this.companyId = navParams.get('companyId'); - this.authenticated = navParams.get('authenticated'); - this.form = formBuilder.group({ - email: ['', Validators.compose([Validators.required, EmailValidator.isValid])], - user_name: ['', Validators.compose([Validators.required, Validators.minLength(3)])], - message: [''] - }); - } - - getDefaults() { - this.userEmails = []; - this.company = { - company_name: '' - }; - this.project = { - project_name: '' - }; - } - - ngOnInit() { - this.project = JSON.parse(localStorage.getItem(generalConstants.PROJECT_MODEL)); - this.getUserEmails(); - this.getCompany(); - } - - getUserEmails() { - const user = JSON.parse(localStorage.getItem(generalConstants.USER_MODEL)); - if (user) { - this.userEmails = user.user_emails || []; - if (user.lf_email && this.userEmails.indexOf(user.lf_email) == -1) { - this.userEmails.push(user.lf_email); - } - } else { - console.error('Unable to retrieve user.'); - } - } - - getCompany() { - this.claService.getCompany(this.companyId).subscribe((response) => { - this.company = response; - }); - } - - dismiss() { - this.viewCtrl.dismiss(); - } - - submit() { - this.hasRequestError = false; - this.submitAttempt = true; - this.currentlySubmitting = true; - if (!this.form.valid) { - this.currentlySubmitting = false; - return; - } - - const data = { - userId: this.userId, - userName: this.form.value.user_name, - userEmail: this.form.value.email, - }; - - this.claService.postCCLAWhitelistRequest(this.companyId, this.projectId, data).subscribe( - () => { - this.emailSent(); - }, - (exception) => { - this.hasRequestError = true; - } - ); - } - - emailSent() { - let alert = this.alertCtrl.create({ - title: 'E-Mail Successfully Sent!', - subTitle: - 'Thank you for contacting your CLA Manager. Once you are authorized, you will have to complete the CLA process from your existing pull request.', - buttons: ['Dismiss'] - }); - alert.onDidDismiss(() => this.dismiss()); - alert.present(); - } - - trimCharacter(text, length) { - return text.length > length ? text.substring(0, length) + '...' : text; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.html b/cla-frontend-contributor-console/src/ionic/pages/auth/auth.html deleted file mode 100644 index 220ffc388..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - -
        -
        {{message}}
        - -
        \ No newline at end of file diff --git a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.module.ts b/cla-frontend-contributor-console/src/ionic/pages/auth/auth.module.ts deleted file mode 100644 index b9b2107f7..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { LayoutModule } from '../../layout/layout.module'; -import { AuthPage } from './auth'; - -@NgModule({ - declarations: [], - imports: [IonicPageModule.forChild(AuthPage), LayoutModule] -}) -export class AuthPageModule { } diff --git a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.scss b/cla-frontend-contributor-console/src/ionic/pages/auth/auth.scss deleted file mode 100644 index 858f6d458..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.scss +++ /dev/null @@ -1,38 +0,0 @@ -page-auth { - .loader { - margin: auto; - margin-top: 200px; - border: 6px solid #f3f3f3; - border-radius: 50%; - border-top: 6px solid #003764; - width: 100px; - height: 100px; - -webkit-animation: spin 2s linear infinite; /* Safari */ - animation: spin 2s linear infinite; - } - - .message { - margin-top: 200px; - text-align: center; - font-size: 18px; - } - - /* Safari */ - @-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - } - } - - @keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.ts b/cla-frontend-contributor-console/src/ionic/pages/auth/auth.ts deleted file mode 100644 index 19f4d11fb..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/auth/auth.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { AfterViewInit, Component, OnInit } from '@angular/core'; -import { NavController } from 'ionic-angular'; -import { AuthService } from '../../services/auth.service'; -import { LfxHeaderService } from '../../services/lfx-header.service'; - -/** - * Generated class for the AuthPage page. - * - * See https://ionicframework.com/docs/components/#navigation for more info on - * Ionic pages and navigation. - */ - -@Component({ - selector: 'page-auth', - templateUrl: 'auth.html' -}) -export class AuthPage implements AfterViewInit { - projectId: string; - claType: string; - message: string; - - constructor( - public navCtrl: NavController, - public authService: AuthService, - private lfxHeaderService: LfxHeaderService - ) { - this.projectId = localStorage.getItem('projectId'); - this.claType = localStorage.getItem('gerritClaType'); - } - - ngAfterViewInit() { - this.authService.redirectRoot.subscribe((target) => { - this.redirect(target); - }); - - this.authService.userProfile$.subscribe(user => { - if (user !== undefined) { - if (user) { - this.lfxHeaderService.setUserInLFxHeader(); - if (this.claType == 'ICLA') { - window.history.replaceState(null, null, window.location.pathname); - this.navCtrl.setRoot('ClaGerritIndividualPage', { projectId: this.projectId }); - } else if (this.claType == 'CCLA') { - window.history.replaceState(null, null, window.location.pathname); - this.navCtrl.setRoot('ClaGerritCorporatePage', { projectId: this.projectId }); - } else { - setTimeout(() => { - this.message = 'Invalid URL. Please verify you follow the right steps.'; - }, 1500); - } - } else { - this.navCtrl.setRoot('LoginPage'); - } - } - }); - } - - redirect(target) { - this.lfxHeaderService.setUserInLFxHeader(); - if (this.claType == 'ICLA') { - window.history.replaceState(null, null, window.location.pathname); - this.navCtrl.setRoot('ClaGerritIndividualPage', { projectId: this.projectId }); - } else if (this.claType == 'CCLA') { - window.history.replaceState(null, null, window.location.pathname); - this.navCtrl.setRoot('ClaGerritCorporatePage', { projectId: this.projectId }); - } else { - window.open(`${window.location.origin}` + '#' + target, '_self'); - } - } - - onClickToggle(toggle) { - - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.html b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.html deleted file mode 100644 index b83dcc394..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.html +++ /dev/null @@ -1,55 +0,0 @@ - - - -
        - - - - {{ project.name }} Logo - - -
        {{ project.project_name }}
        -
        -
        -
        - - - - - - - - Confirmation of Association with {{ company.company_name }} - -
        - - -

        - I hereby confirm that I am still affiliated with the company: {{ company.company_name }}. -

        -
        - -
        - -

        * You must agree in order to submit this form.

        -
        - - - -

        An error occurred while confirming your association with {{ company.company_name }}. - Error is: {{ errorMessage }}. Please contact the EasyCLA Help Desk at: {{ helpDeskLink }}

        -
        -
        -
        -
        -
        - -
        -
        -
        - -
        diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.module.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.module.ts deleted file mode 100644 index 245e08860..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaEmployeeCompanyConfirmPage } from './cla-employee-company-confirm'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; -import { LayoutModule } from '../../layout/layout.module'; -@NgModule({ - declarations: [ClaEmployeeCompanyConfirmPage], - imports: [LoadingSpinnerComponentModule, IonicPageModule.forChild(ClaEmployeeCompanyConfirmPage), LayoutModule], - entryComponents: [ClaEmployeeCompanyConfirmPage] -}) -export class ClaEmployeeCompanyConfirmPageModule { } diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.scss b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.scss deleted file mode 100644 index dd5cd6c46..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.scss +++ /dev/null @@ -1,13 +0,0 @@ -cla-employee-company-confirm { - loading-spinner.submit { - .container { - display: inline-block; - vertical-align: middle; - margin: 0; - } - } - - .project-title { - font-size: 25px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.ts deleted file mode 100644 index 26f85d2c0..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-confirm/cla-employee-company-confirm.ts +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { NavController, NavParams, IonicPage, ModalController } from 'ionic-angular'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { CheckboxValidator } from '../../validators/checkbox'; -import { ClaService } from '../../services/cla.service'; -import { generalConstants } from '../../constants/general'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@IonicPage({ - segment: 'project/:projectId/user/:userId/employee/company/:companyId/confirm' -}) -@Component({ - selector: 'cla-employee-company-confirm', - templateUrl: 'cla-employee-company-confirm.html' -}) -export class ClaEmployeeCompanyConfirmPage { - projectId: string; - repositoryId: string; - userId: string; - companyId: string; - signingType: string; // used to differentiate Github/Gerrit Users - user: any; - project: any; - company: any; - signature: any; - - form: FormGroup; - submitAttempt: boolean = false; - currentlySubmitting: boolean = false; - errorMessage: string = null; - helpDeskLink: URL = new URL(generalConstants.getHelpURL); - expanded: boolean = true; - - constructor( - public navCtrl: NavController, - private modalCtrl: ModalController, - public navParams: NavParams, - private formBuilder: FormBuilder, - private claService: ClaService - ) { - this.projectId = navParams.get('projectId'); - this.repositoryId = navParams.get('repositoryId'); - this.userId = navParams.get('userId'); - this.companyId = navParams.get('companyId'); - this.signingType = navParams.get('signingType'); - - this.getDefaults(); - - this.form = formBuilder.group({ - agree: [false, Validators.compose([CheckboxValidator.isChecked])] - }); - } - - getDefaults() { - this.project = { - project_name: '' - }; - this.company = { - company_name: '' - }; - this.user = { - user_name: '' - }; - this.errorMessage = null; - this.currentlySubmitting = false; - } - - ngOnInit() { - this.user = JSON.parse(localStorage.getItem(generalConstants.USER_MODEL)); - this.project = JSON.parse(localStorage.getItem(generalConstants.PROJECT_MODEL)); - this.getCompany(this.companyId); - } - - getCompany(companyId) { - this.claService.getCompany(companyId).subscribe((response) => { - this.company = response; - }); - } - - submit() { - // Reset our status and error messages - this.submitAttempt = true; - this.errorMessage = null; - this.currentlySubmitting = true; - - if (!this.form.valid) { - this.currentlySubmitting = false; - return; - } - - let signatureRequest = { - project_id: this.projectId, - company_id: this.companyId, - user_id: this.userId, - return_url_type: this.signingType //"Gerrit" / "Github" - }; - this.claService.postEmployeeSignatureRequest(signatureRequest).subscribe((response) => { - this.currentlySubmitting = false; - - let errors = response.hasOwnProperty('errors'); - if (errors) { - this.errorMessage = response.errors; - - if (response.errors.hasOwnProperty('ccla_approval_list')) { - // When the user is not whitelisted with the company: return {'errors': {'ccla_approval_list': 'No user email authorized for this ccla'}} - this.openClaEmployeeCompanyTroubleshootPage(); - return; - } - - if (response.errors.hasOwnProperty('missing_ccla')) { - // When the company does NOT have a CCLA with the project: {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} - // The user shouldn't get here if they are using the console properly - return; - } - } else { - // No Errors, expect normal signature response - this.errorMessage = null; - this.signature = response; - this.openClaNextStepModal(); - } - }); - } - - openClaNextStepModal() { - let modal = this.modalCtrl.create('ClaNextStepModal', { - projectId: this.projectId, - userId: this.userId, - project: this.project, - signature: this.signature, - signingType: this.signingType - }); - modal.present(); - } - - openClaEmployeeCompanyTroubleshootPage() { - this.navCtrl.push('ClaEmployeeCompanyTroubleshootPage', { - projectId: this.projectId, - repositoryId: this.repositoryId, - userId: this.userId, - companyId: this.companyId - }); - } - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.html b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.html deleted file mode 100644 index b6eb53f2f..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - -
        -
        - - - - {{ project.project_name }} Logo - - -

        {{ company.company_name }}

        -

        Unfortunately, you are not yet approved by the Corporate CLA of {{ company.company_name }} - on {{ project.project_name }}.

        -
        -
        -
        -
        - - - - - - - - If you feel you are receiving this message in error, please try the following steps: - - -

        - {{ company.company_name }} has whitelisted the following GitHub Organization(s), and all public - members therein will be approved to contribute: -

        -
          -
        • {{ org }}
        • -
        - -

        - - If you are currently a member of an Org listed above, but haven't publicized it, please - - change your GitHub settings to make this public, and then come back to EasyCLA and select - "Corporate" > {{ company.company_name }} again. You should be all set! -

        - -

        - If you are not a member of an Org listed above, please ask your GitHub administrator to add you (and - then set your membership to public). -

        - -

        - Make sure your employee email is verified in your GitHub account settings. -

        - -

        Go to GitHub and make sure your employee email address is associated with your GitHub account. Then - restart this process from the PR status message. Feel free to close this page. -

        - - - - - -

        - Ask your CLA Manager to approved your GitHub Username, rather than Email when you contact them - below. -

        -

        - Try making your GitHub email address public. -

        -
        -
        -
        -
        -
        - - - - - Contact the CLA Manager to be approved under their signed Corporate CLA. - - -

        You must be authorized under a signed Contributor License Agreement. You are contributing on behalf - of your work for a company. Contact your CLA manager to request authorization.

        - -
        -
        -
        -
        -
        -
        -
        - -
        diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.module.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.module.ts deleted file mode 100644 index 9e8fb37fc..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaEmployeeCompanyTroubleshootPage } from './cla-employee-company-troubleshoot'; -import { LayoutModule } from '../../layout/layout.module'; - -@NgModule({ - declarations: [ClaEmployeeCompanyTroubleshootPage], - imports: [IonicPageModule.forChild(ClaEmployeeCompanyTroubleshootPage), LayoutModule], - entryComponents: [ClaEmployeeCompanyTroubleshootPage] -}) -export class ClaEmployeeCompanyTroubleshootPageModule { } diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.scss b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.scss deleted file mode 100644 index b01ebed9d..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.scss +++ /dev/null @@ -1,6 +0,0 @@ -cla-employee-company-troubleshoot { - - @media (min-width: 768px) and (max-width: 1200px) { - padding: 16px 50px 162px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.ts deleted file mode 100644 index 835759fcf..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-employee-company-troubleshoot/cla-employee-company-troubleshoot.ts +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { IonicPage, ModalController, NavParams } from 'ionic-angular'; -import { ClaService } from '../../services/cla.service'; -import { ClaSignatureModel } from '../../../../../cla-frontend-corporate-console/src/ionic/models/cla-signature'; -import { generalConstants } from '../../constants/general'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId/employee/company/:companyId/troubleshoot' -}) -@Component({ - selector: 'cla-employee-company-troubleshoot', - templateUrl: 'cla-employee-company-troubleshoot.html' -}) -export class ClaEmployeeCompanyTroubleshootPage { - loading: any; - projectId: string; - repositoryId: string; - userId: string; - companyId: string; - authenticated: boolean; - cclaSignature: any; - project: any; - company: any; - gitService: string; - expanded: boolean = true; - - constructor( - private modalCtrl: ModalController, - public navParams: NavParams, - private claService: ClaService - ) { - this.getDefaults(); - this.projectId = navParams.get('projectId'); - this.repositoryId = navParams.get('repositoryId'); - this.userId = navParams.get('userId'); - this.companyId = navParams.get('companyId'); - this.gitService = navParams.get('gitService'); - this.authenticated = navParams.get('authenticated'); - } - - getDefaults() { - this.loading = {}; - this.project = { - project_name: '', - logoUrl: '' - }; - this.company = { - company_name: '' - }; - this.cclaSignature = new ClaSignatureModel(); - } - - ngOnInit() { - this.getProject(); - this.getCompany(); - this.getProjectSignatures(); - } - - getProject() { - this.project = JSON.parse(localStorage.getItem(generalConstants.PROJECT_MODEL)); - this.loading.projects = false; - } - - getCompany() { - this.loading.companies = true; - this.claService.getCompany(this.companyId).subscribe((response) => { - this.loading.companies = true; - this.company = response; - }); - } - - getProjectSignatures() { - // Get CCLA Company Signatures - should just be one - this.loading.signatures = true; - this.claService.getCompanyProjectSignatures(this.companyId, this.projectId).subscribe( - (response) => { - this.loading.signatures = false; - if (response.signatures) { - let cclaSignatures = response.signatures.filter((sig) => sig.signatureType === 'ccla'); - if (cclaSignatures.length) { - this.cclaSignature = cclaSignatures[0]; - // Sort the values - if (this.cclaSignature.githubOrgWhitelist) { - const sortedList: string[] = this.cclaSignature.githubOrgWhitelist.sort((a, b) => { - return a.trim().localeCompare(b.trim()); - }); - // Remove duplicates - set doesn't allow dups - this.cclaSignature.githubOrgWhitelist = Array.from(new Set(sortedList)); - } - } - } - }, - (exception) => { - this.loading.signatures = false; - } - ); - } - - openGitServiceEmailSettings() { - window.open(generalConstants.githubEmailURL, '_blank'); - } - - openClaEmployeeRequestAccessModal() { - let modal = this.modalCtrl.create('ClaEmployeeRequestAccessModal', { - projectId: this.projectId, - repositoryId: this.repositoryId, - userId: this.userId, - companyId: this.companyId, - authenticated: this.authenticated - }); - modal.present(); - } - - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.html b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.html deleted file mode 100644 index 84b67dd04..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.html +++ /dev/null @@ -1,43 +0,0 @@ - - - -
        - -
        {{errorMessage}}
        - - - - - -
        - - - - - - -
        {{ company.company_name }}
        -
        - - - - - - -
        - - - -
        diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.module.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.module.ts deleted file mode 100644 index 0a421b62a..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaGerritCorporatePage } from './cla-gerrit-corporate'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; -import { LoadingDisplayDirectiveModule } from '../../directives/loading-display/loading-display.module'; -import { LayoutModule } from '../../layout/layout.module'; - -@NgModule({ - declarations: [ClaGerritCorporatePage], - imports: [ - LoadingSpinnerComponentModule, - LoadingDisplayDirectiveModule, - IonicPageModule.forChild(ClaGerritCorporatePage), - LayoutModule - ], - entryComponents: [ClaGerritCorporatePage] -}) -export class ClaGerritCorporatePageModule { } diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.scss b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.scss deleted file mode 100644 index 874879bb1..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.scss +++ /dev/null @@ -1,28 +0,0 @@ -.page-content { - .error { - font-size: 18px; - color: red; - text-align: center; - padding-top: 15px; - } - - .search-box { - margin-left: 10px; - .header { - font-weight: bold; - } - } - - .table-view { - height: 54vh; - overflow-y: auto; - - td { - cursor: pointer; - } - } - - @media (min-width: 768px) and (max-width: 1200px) { - padding: 16px 50px 162px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.ts deleted file mode 100644 index 9ce1782c9..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-corporate/cla-gerrit-corporate.ts +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { NavController, NavParams, ViewController, ModalController, IonicPage } from 'ionic-angular'; -import { ClaService } from '../../services/cla.service'; -import { AuthService } from '../../services/auth.service'; -import { Restricted } from '../../decorators/restricted'; -import { generalConstants } from '../../constants/general'; - -@Restricted({ - roles: ['isAuthenticated'] -}) -@IonicPage({ - segment: 'cla/gerrit/project/:projectId/corporate' -}) -@Component({ - selector: 'cla-gerrit-corporate', - templateUrl: 'cla-gerrit-corporate.html', - providers: [] -}) -export class ClaGerritCorporatePage { - loading: any; - projectId: string; - userId: string; - signature: string; - companies: any; - filteredCompanies: any; - expanded: boolean = true; - errorMessage: string = null; - searchTimer = null; - - constructor( - public navCtrl: NavController, - public navParams: NavParams, - public viewCtrl: ViewController, - private modalCtrl: ModalController, - private claService: ClaService, - private authService: AuthService, - ) { - this.projectId = navParams.get('projectId'); - this.getDefaults(); - localStorage.setItem('projectId', this.projectId); - localStorage.setItem('gerritClaType', 'CCLA'); - } - - getDefaults() { - this.loading = { - companies: true - }; - this.companies = []; - this.filteredCompanies = []; - } - - ngOnInit() { - this.authService.userProfile$.subscribe(user => { - if (user !== undefined) { - if (user) { - this.getProject(); - } else { - this.redirectToLogin(); - } - } - }); - } - - redirectToLogin() { - this.navCtrl.setRoot('LoginPage'); - } - - getCompanies() { - this.claService.getAllCompanies().subscribe((response) => { - if (response) { - this.companies = response; - this.filteredCompanies = this.companies; - } - this.loading.companies = false; - }); - } - - getUserInfo() { - // retrieve userInfo from auth0 service - this.claService.postOrGetUserForGerrit().subscribe((user) => { - localStorage.setItem(generalConstants.USER_MODEL, JSON.stringify(user)); - this.userId = user.user_id; - this.getCompanies(); - }, (error) => { - // Got an auth error, redirect to the login - this.loading = false; - setTimeout(() => this.redirectToLogin()); - }); - } - - openClaEmployeeCompanyConfirmPage(company) { - let data = { - project_id: this.projectId, - company_id: company.company_id, - user_id: this.userId - }; - this.claService.postCheckAndPreparedEmployeeSignature(data).subscribe((response) => { - let errors = response.hasOwnProperty('errors'); - if (errors) { - if (response.errors.hasOwnProperty('missing_ccla')) { - // When the company does NOT have a CCLA with the project: {'errors': {'missing_ccla': 'Company does not have CCLA with this project'}} - this.openClaSendClaManagerEmailModal(company); - } - - if (response.errors.hasOwnProperty('ccla_approval_list')) { - // When the user is not whitelisted with the company: return {'errors': {'ccla_approval_list': 'No user email authorized for this ccla'}} - this.openClaEmployeeCompanyTroubleshootPage(company); - return; - } - } else { - this.signature = response; - - this.navCtrl.push('ClaEmployeeCompanyConfirmPage', { - projectId: this.projectId, - signingType: 'Gerrit', - userId: this.userId, - companyId: company.company_id - }); - } - }); - } - - openClaSendClaManagerEmailModal(company) { - let modal = this.modalCtrl.create('ClaSendClaManagerEmailModal', { - projectId: this.projectId, - userId: this.userId, - companyId: company.company_id, - authenticated: true - }); - modal.present(); - } - - openClaNewCompanyModal() { - let modal = this.modalCtrl.create('ClaNewCompanyModal', { - projectId: this.projectId - }); - modal.present(); - } - - openClaCompanyAdminYesnoModal() { - let modal = this.modalCtrl.create('ClaCompanyAdminYesnoModal', { - projectId: this.projectId, - userId: this.userId, - authenticated: true - }); - modal.present(); - } - - openClaEmployeeCompanyTroubleshootPage(company) { - this.navCtrl.push('ClaEmployeeCompanyTroubleshootPage', { - projectId: this.projectId, - repositoryId: '', - userId: this.userId, - companyId: company.company_id, - gitService: 'Gerrit' - }); - } - - getProject() { - this.claService.getProjectWithAuthToken(this.projectId).subscribe( - (project) => { - this.errorMessage = ''; - localStorage.setItem(generalConstants.PROJECT_MODEL, JSON.stringify(project)); - this.getUserInfo(); - }, - () => { - this.loading = false; - this.errorMessage = 'Invalid project id.'; - } - ); - } - - onSearch(event) { - const searchText = event._value; - if (this.searchTimer !== null) { - clearTimeout(this.searchTimer); - } - this.searchTimer = setTimeout(() => { - if (searchText === '') { - this.filteredCompanies = this.companies; - } else { - this.filteredCompanies = this.companies.filter((a) => { - return a.company_name.toLowerCase().includes(searchText.toLowerCase()); - }); - } - }, 250); - } - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.html b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.html deleted file mode 100644 index 9b0e039f1..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.html +++ /dev/null @@ -1,56 +0,0 @@ - - - -
        - -
        {{errorMessage}}
        - - - -
        - - - -

        {{ project.project_name }}

        -
        -
        -
        -
        - - - - - - - - Individual CLA - -
        -

        - We are generating a document to sign for the purposes of your CLA. Please fill out - accurately and complete the signing process. -

        -

        - If a new tab with the document to sign did not open, you may use - the button below: -

        - -
        -
        -

        - Whoops, It looks like you don't have any signatures in progress. Try logging in with your Linux - Foundation ID again. -

        -
        -
        -
        -
        -
        -
        -
        - - - -
        \ No newline at end of file diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.module.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.module.ts deleted file mode 100644 index 3d9ab642c..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; -import { LayoutModule } from '../../layout/layout.module'; -import { ClaGerritIndividualPage } from './cla-gerrit-individual'; - -@NgModule({ - declarations: [ClaGerritIndividualPage], - imports: [IonicPageModule.forChild(ClaGerritIndividualPage), - LayoutModule, LoadingSpinnerComponentModule], - entryComponents: [ClaGerritIndividualPage] -}) -export class ClaGerritIndividualPageModule { } diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.scss b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.scss deleted file mode 100644 index 20d936410..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.scss +++ /dev/null @@ -1,15 +0,0 @@ -cla-gerrit-individual { - .error { - font-size: 18px; - color: red; - text-align: center; - padding-top: 15px; - } - .card-md p { - margin-bottom: 1rem; - } - - @media (min-width: 768px) and (max-width: 1200px) { - padding: 16px 50px 162px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.ts deleted file mode 100644 index c24a6b86c..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-gerrit-individual/cla-gerrit-individual.ts +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { NavController, NavParams, IonicPage } from 'ionic-angular'; -import { ClaService } from '../../services/cla.service'; -import { AuthService } from '../../services/auth.service'; -import { Restricted } from '../../decorators/restricted'; -import { generalConstants } from '../../constants/general'; - -@Restricted({ - roles: ['isAuthenticated'] -}) -@IonicPage({ - segment: 'cla/gerrit/project/:projectId/individual' -}) -@Component({ - selector: 'cla-gerrit-individual', - templateUrl: 'cla-gerrit-individual.html' -}) -export class ClaGerritIndividualPage { - projectId: string; - project: any; - gerrit: any; - userId: string; - user: any; - signatureIntent: any; - activeSignatures: boolean = true; // we assume true until otherwise - signature: any; - expanded: boolean = true; - errorMessage: string; - loading: boolean; - - constructor( - public navCtrl: NavController, - public navParams: NavParams, - private claService: ClaService, - private authService: AuthService, - ) { - this.getDefaults(); - this.projectId = navParams.get('projectId'); - localStorage.setItem('projectId', this.projectId); - localStorage.setItem('gerritClaType', 'ICLA'); - } - - getDefaults() { - this.project = { - project_name: '' - }; - this.signature = { - sign_url: '' - }; - } - - ngOnInit() { - this.loading = true; - this.authService.userProfile$.subscribe(user => { - if (user !== undefined) { - if (user) { - this.getProject(); - } else { - this.redirectToLogin(); - } - } - }); - } - - redirectToLogin() { - this.navCtrl.setRoot('LoginPage'); - } - - getProject() { - this.claService.getProjectWithAuthToken(this.projectId).subscribe( - (project) => { - this.project = project; - localStorage.setItem(generalConstants.PROJECT_MODEL, JSON.stringify(project)); - // retrieve userInfo from auth0 service - this.getUserDetails(); - }, - () => { - this.loading = false; - this.errorMessage = 'Invalid project id.'; - } - ); - } - - getUserDetails() { - this.claService.postOrGetUserForGerrit().subscribe( - (user) => { - this.userId = user.user_id; - localStorage.setItem(generalConstants.USER_MODEL, JSON.stringify(user)); - // get signatureIntent object, similar to the Github flow. - this.postSignatureRequest(); - }, - (exception) => { - this.loading = false; - this.errorMessage = 'Invalid user details, please login again.'; - } - ); - } - postSignatureRequest() { - let signatureRequest = { - project_id: this.projectId, - user_id: this.userId, - return_url_type: 'Gerrit' - }; - this.claService.postIndividualSignatureRequest(signatureRequest).subscribe( - (response) => { - this.loading = false; - this.signature = response; - }, - () => { - this.loading = false; - this.errorMessage = 'Invalid signature.'; - } - ); - } - - openClaAgreement() { - if (!this.signature.sign_url) { - return; - } - window.open(this.signature.sign_url, '_self'); - } - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.html b/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.html deleted file mode 100644 index 49bdba14a..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.html +++ /dev/null @@ -1,65 +0,0 @@ - - - -
        - - - - {{ project.name }} Logo - - -
        {{ project.project_name }}
        -
        -
        -
        - - - - - - - - Individual CLA - -
        -

        - We are generating a document to sign for the purposes of your CLA. Please fill out - accurately and complete the signing process. -

        -

        - If a new tab with the document to sign did not open, you may use - the button below: -

        - -
        -

        - The Individual CLA template has not been selected by the Project Manager. Please create a ticket to help us resolve this issue. -

        -

        - It looks like something has gone wrong. Please create a ticket to - help - us resolve this issue. -

        -
        - -
        Loading Individual CLA...
        -
        -
        -

        - Whoops, It looks like you don't have any signatures in progress. Try going back to your pull request - and - restarting the signing process from your pull request if necessary. -

        -
        -
        -
        -
        -
        -
        -
        - - -
        diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.module.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.module.ts deleted file mode 100644 index 72ff8d6fe..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaIndividualPage } from './cla-individual'; -import { LayoutModule } from '../../layout/layout.module'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; - -@NgModule({ - declarations: [ClaIndividualPage], - imports: [IonicPageModule.forChild(ClaIndividualPage), LayoutModule, LoadingSpinnerComponentModule], - entryComponents: [ClaIndividualPage] -}) -export class ClaIndividualPageModule { } diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.scss b/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.scss deleted file mode 100644 index ed8028f36..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.scss +++ /dev/null @@ -1,30 +0,0 @@ -cla-individual { - .error { - color: red; - } - .card-md p { - margin-bottom: 1rem; - } - - .project-title { - font-size: 25px; - } - - img { - background-size: contain; - max-height: 150px; - } - - .clear-div { - clear: both; - } - - .error { - color: #db4e4e; - font-size: 12px; - - a { - cursor: pointer; - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.ts deleted file mode 100644 index 4827c5c22..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-individual/cla-individual.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { NavParams, IonicPage } from 'ionic-angular'; -import { ClaService } from '../../services/cla.service'; -import { generalConstants } from '../../constants/general'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId/individual' -}) -@Component({ - selector: 'cla-individual', - templateUrl: 'cla-individual.html' -}) -export class ClaIndividualPage { - projectId: string; - userId: string; - user: any; - project: any; - signatureIntent: any; - activeSignatures: boolean = true; - signature: any; - loadingSignature: boolean = true; - error: any = false; - expanded: boolean = true; - - constructor( - public navParams: NavParams, - private claService: ClaService - ) { - this.getDefaults(); - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - } - - getDefaults() { - this.project = { - project_name: '' - }; - this.signature = { - sign_url: '' - }; - } - - ngOnInit() { - this.user = JSON.parse(localStorage.getItem(generalConstants.USER_MODEL)); - this.project = JSON.parse(localStorage.getItem(generalConstants.PROJECT_MODEL)); - this.getUserSignatureIntent(); - } - - getUserSignatureIntent() { - this.loadingSignature = true; - this.claService.getUserSignatureIntent(this.userId).subscribe((response) => { - this.signatureIntent = response; - if (this.signatureIntent !== null) { - this.postSignatureRequest(); - } else { - this.activeSignatures = false; - } - this.loadingSignature = false; - }); - } - - postSignatureRequest() { - let signatureRequest = { - project_id: this.projectId, - user_id: this.userId, - return_url_type: 'Github', - return_url: this.signatureIntent.return_url - }; - - this.claService.postIndividualSignatureRequest(signatureRequest).subscribe( - (response) => { - if (response.errors) { - this.error = response.errors; - } else { - this.signature = response; - } - }, - (err) => { - this.error = err; - } - ); - } - - createTicket() { - window.open(generalConstants.createTicketURL, '_blank'); - } - - openClaAgreement() { - if (!this.signature.sign_url) { - return; - } - window.open(this.signature.sign_url, '_self'); - } - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.html b/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.html deleted file mode 100755 index 0330a3275..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.html +++ /dev/null @@ -1,47 +0,0 @@ - - - -
        - - - - - {{ project.project_name }} Logo - - - - - -
        {{ project.project_name }}
        -
        - - - - - - - Corporate - -

        Select this if you are contributing code as an employee.

        -
        -
        -
        - - - - - - - Individual - -

        Select this if you are contributing code as an individual.

        -
        -
        -
        -
        -
        -
        -
        -
        - -
        diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.module.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.module.ts deleted file mode 100644 index b20eb0586..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { ClaLandingPage } from './cla-landing'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; -import { LoadingDisplayDirectiveModule } from '../../directives/loading-display/loading-display.module'; -import { LayoutModule } from '../../layout/layout.module'; - -@NgModule({ - declarations: [ClaLandingPage], - imports: [ - LoadingSpinnerComponentModule, - LoadingDisplayDirectiveModule, - IonicPageModule.forChild(ClaLandingPage), - LayoutModule - ], - entryComponents: [ClaLandingPage] -}) -export class ClaLandingPageModule {} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.scss b/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.scss deleted file mode 100755 index 07c369393..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.scss +++ /dev/null @@ -1,20 +0,0 @@ -cla-landing { - img { - max-height: 150px; - } - - .project-name { - text-align: center; - font-size: 25px; - } - - .xc { - display: flex; - align-items: center; - justify-content: center; - } - - @media (min-width: 768px) and (max-width: 1200px) { - padding: 16px 50px 100px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.ts b/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.ts deleted file mode 100755 index df54c6ce9..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/cla-landing/cla-landing.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { IonicPage, ModalController, NavController, NavParams } from 'ionic-angular'; -import { ClaService } from '../../services/cla.service'; -import { generalConstants } from '../../constants/general'; -import { EnvConfig } from '../../services/cla.env.utils'; - -@IonicPage({ - segment: 'cla/project/:projectId/user/:userId' -}) -@Component({ - selector: 'cla-landing', - templateUrl: 'cla-landing.html' -}) -export class ClaLandingPage { - projectId: string; - userId: string; - user: any; - project: any; - expanded: boolean = true; - - constructor( - public navCtrl: NavController, - public navParams: NavParams, - private modalCtrl: ModalController, - private claService: ClaService - ) { - this.projectId = navParams.get('projectId'); - this.userId = navParams.get('userId'); - this.getDefaults(); - } - - getDefaults() { - this.project = { - project_name: '' - }; - } - - ngOnInit() { - this.getUser(); - this.getProject(); - localStorage.removeItem('gerritClaType'); - } - - openClaIndividualPage() { - // send to the individual cla page which will give directions and redirect - this.navCtrl.push('ClaIndividualPage', { - projectId: this.projectId, - userId: this.userId - }); - } - - openClaIndividualEmployeeModal() { - let modal = this.modalCtrl.create('ClaSelectCompanyModal', { - projectId: this.projectId, - userId: this.userId - }); - modal.present(); - } - - getUser() { - this.claService.getUser(this.userId).subscribe((response) => { - localStorage.setItem(generalConstants.USER_MODEL, JSON.stringify(response)); - this.user = response; - }); - } - - getProject() { - this.claService.getProject(this.projectId).subscribe((response) => { - localStorage.setItem(generalConstants.PROJECT_MODEL, JSON.stringify(response)); - this.project = response; - }); - } - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/login/login.html b/cla-frontend-contributor-console/src/ionic/pages/login/login.html deleted file mode 100644 index 580a38761..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/login/login.html +++ /dev/null @@ -1,16 +0,0 @@ - - - -
        - -
        - - - -
        diff --git a/cla-frontend-contributor-console/src/ionic/pages/login/login.module.ts b/cla-frontend-contributor-console/src/ionic/pages/login/login.module.ts deleted file mode 100644 index 60b5eb4b2..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/login/login.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { NgModule } from '@angular/core'; -import { IonicPageModule } from 'ionic-angular'; -import { LoginPage } from './login'; -import { LoadingSpinnerComponentModule } from '../../components/loading-spinner/loading-spinner.module'; -import { LoadingDisplayDirectiveModule } from '../../directives/loading-display/loading-display.module'; -import { LayoutModule } from '../../layout/layout.module'; - -@NgModule({ - declarations: [LoginPage], - imports: [ - LoadingSpinnerComponentModule, - LoadingDisplayDirectiveModule, - IonicPageModule.forChild(LoginPage), - LayoutModule - ], - entryComponents: [LoginPage] -}) -export class LoginPageModule {} diff --git a/cla-frontend-contributor-console/src/ionic/pages/login/login.scss b/cla-frontend-contributor-console/src/ionic/pages/login/login.scss deleted file mode 100644 index d28a65e41..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/login/login.scss +++ /dev/null @@ -1,46 +0,0 @@ -login { - .scroll-content { - background: url("../assets/img/boat.jpeg") no-repeat center center fixed #000; - -webkit-background-size: cover; - -moz-background-size: cover; - -o-background-size: cover; - background-size: cover; - padding: 0 !important; - } - - .page-content { - .login-container { - text-align: center; - margin-top: 215px; - margin-left: auto; - margin-right: auto; - padding: 50px 20px; - background-color: #ececec; - max-width: 400px; - - button { - margin-top: 25px; - width: 250px; - max-width: 100%; - } - } - - @media (min-width: 768px) and (max-width: 1200px) { - padding: 16px 50px 162px; - } - } - - .logo { - display: block; - width: 250px; - max-width: 100%; - height: auto; - margin-left: auto; - margin-right: auto; - margin-bottom: 25px; - } - - .scroll-content { - padding-bottom: 162px; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/pages/login/login.ts b/cla-frontend-contributor-console/src/ionic/pages/login/login.ts deleted file mode 100644 index 440d85051..000000000 --- a/cla-frontend-contributor-console/src/ionic/pages/login/login.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Component } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; -import { AuthService } from '../../services/auth.service'; -import { AUTH_ROUTE } from '../../services/auth.utils'; - -@IonicPage({ - name: 'LoginPage', - segment: 'login' -}) -@Component({ - selector: 'login', - templateUrl: 'login.html' -}) -export class LoginPage { - canAccess: boolean; - expanded: boolean = true; - - constructor( - public authService: AuthService - ) { } - - login() { - this.authService.login(AUTH_ROUTE); - } - - onClickToggle(hasExpanded) { - this.expanded = hasExpanded; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/service-worker.js b/cla-frontend-contributor-console/src/ionic/service-worker.js deleted file mode 100755 index 10d474b7a..000000000 --- a/cla-frontend-contributor-console/src/ionic/service-worker.js +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -/** - * Check out https://googlechrome.github.io/sw-toolbox/docs/master/index.html for - * more info on how to use sw-toolbox to custom configure your service worker. - */ - -'use strict'; -importScripts('./build/sw-toolbox.js'); - -self.toolbox.options.cache = { - name: 'ionic-cache' -}; - -// pre-cache our key assets -self.toolbox.precache(['./build/main.js', './build/main.css', './build/polyfills.js', 'index.html', 'manifest.json']); - -// dynamically cache any other local assets -//self.toolbox.router.any('/*', self.toolbox.cacheFirst); -// disable cache for now: -self.toolbox.router.any('/*', self.toolbox.networkOnly); - -// for any other requests go to the network, cache, -// and then only use that cached resource if your user goes offline -//self.toolbox.router.default = self.toolbox.networkFirst; -// disable cache for now: -self.toolbox.router.default = self.toolbox.networkOnly; diff --git a/cla-frontend-contributor-console/src/ionic/services/auth.service.ts b/cla-frontend-contributor-console/src/ionic/services/auth.service.ts deleted file mode 100644 index 3dc62db46..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/auth.service.ts +++ /dev/null @@ -1,231 +0,0 @@ - -import { Injectable } from '@angular/core'; -import createAuth0Client from '@auth0/auth0-spa-js'; -import Auth0Client from '@auth0/auth0-spa-js/dist/typings/Auth0Client'; -import { - Observable, - BehaviorSubject, - Subject -} from 'rxjs'; -import { tap, catchError, concatMap, shareReplay } from 'rxjs/operators'; -import * as querystring from 'query-string'; -import Url from 'url-parse'; -import { from } from 'rxjs/observable/from'; -import { of } from 'rxjs/observable/of'; -import { reject } from 'lodash'; -import { combineLatest } from 'rxjs/observable/combineLatest'; -import { EnvConfig } from './cla.env.utils'; - -@Injectable() -export class AuthService { - auth0Options = { - clientId: EnvConfig['auth0-clientId'], - domain: EnvConfig['auth0-domain'], - }; - - currentHref = window.location.href; - redirectRoot: Subject = new Subject(); - loading$ = new BehaviorSubject(true); - // Create an observable of Auth0 instance of client - auth0Client$ = (from( - createAuth0Client({ - domain: this.auth0Options.domain, - client_id: this.auth0Options.clientId - }) - ) as Observable).pipe( - shareReplay(1), // Every subscription receives the same shared value - catchError((err) => { - this.loading$.next(false); - return reject(err); - }) - ); - // Define observables for SDK methods that return promises by default - // For each Auth0 SDK method, first ensure the client instance is ready - // concatMap: Using the client instance, call SDK method; SDK returns a promise - // from: Convert that resulting promise into an observable - isAuthenticated$ = this.auth0Client$.pipe( - concatMap((client: Auth0Client) => from(client.isAuthenticated())), - tap((res: any) => { - // *info: once isAuthenticated$ responses , SSO sessiong is loaded - this.loading$.next(false); - this.loggedIn = res; - }) - ); - handleRedirectCallback$ = this.auth0Client$.pipe( - concatMap((client: Auth0Client) => - from(client.handleRedirectCallback(this.currentHref)) - ) - ); - // Create subject and public observable of user profile data - private userProfileSubject$ = new BehaviorSubject(undefined); - userProfile$ = this.userProfileSubject$.asObservable(); - // Create a local property for login status - loggedIn = false; - - constructor() { - // On initial load, check authentication state with authorization server - // Set up local auth streams if user is already authenticated - const params = this.currentHref; - if (params.includes('code=') && params.includes('state=')) { - this.handleAuthCallback(); - } else { - this.localAuthSetup(); - } - // this.handlerReturnToAferlogout(); - } - - handlerReturnToAferlogout() { - const { query } = querystring.parseUrl(this.currentHref); - const returnTo = query.returnTo; - if (returnTo) { - const target = this.getTargetRouteFromReturnTo(returnTo); - this.redirectRoot.next(target); - } - } - // When calling, options can be passed if desired - // https://auth0.github.io/auth0-spa-js/classes/auth0client.html#getuser - getUser$(options?): Observable { - return this.auth0Client$.pipe( - concatMap((client: Auth0Client) => from(client.getUser(options))), - tap((user) => { - this.setSession(user); - this.userProfileSubject$.next(user); - }) - ); - } - - private localAuthSetup() { - // This should only be called on app initialization - // Set up local authentication streams - const checkAuth$ = this.isAuthenticated$.pipe( - concatMap((loggedIn: boolean) => { - if (loggedIn) { - // If authenticated, get user and set in app - // NOTE: you could pass options here if needed - return this.getUser$(); - } - this.auth0Client$ - .pipe(concatMap((client: Auth0Client) => from(client.checkSession()))) - .subscribe((data) => { }); - this.userProfileSubject$.next(null); - // If not authenticated, return stream that emits 'false' - return of(loggedIn); - }) - ); - checkAuth$.subscribe(); - } - - login(redirectPath: string = '/') { - // A desired redirect path can be passed to login method - // (e.g., from a route guard) - // Ensure Auth0 client instance exists - this.auth0Client$.subscribe((client: Auth0Client) => { - // Call method to log in - client.loginWithRedirect({ - redirect_uri: `${window.location.origin}${window.location.search}`, - appState: { target: redirectPath }, - }); - }); - } - - private getTargetRouteFromAppState(appState) { - if (!appState) { - return '/'; - } - - const { returnTo, target, targetUrl } = appState; - - return ( - this.getTargetRouteFromReturnTo(returnTo) || target || targetUrl || '/' - ); - } - - private getTargetRouteFromReturnTo(returnTo) { - if (!returnTo) { - return ''; - } - - const { fragmentIdentifier } = querystring.parseUrl(returnTo, { - parseFragmentIdentifier: true, - }); - - if (fragmentIdentifier) { - return fragmentIdentifier; - } - - const { pathname } = new Url(returnTo); - return pathname || '/'; - } - - private handleAuthCallback() { - // Call when app reloads after user logs in with Auth0 - const params = this.currentHref; - if (params.includes('code=') && params.includes('state=')) { - let targetRoute: string; // Path to redirect to after login processsed - const authComplete$ = this.handleRedirectCallback$.pipe( - // Have client, now call method to handle auth callback redirect - tap((cbRes: any) => { - targetRoute = this.getTargetRouteFromAppState(cbRes.appState); - }), - concatMap(() => { - // Redirect callback complete; get user and login status - return combineLatest([this.getUser$(), this.isAuthenticated$]); - }) - ); - // Subscribe to authentication completion observable - // Response will be an array of user and login status - authComplete$.subscribe(() => { - // console.log('navigating too', { - // current: this.currentHref, - // targetRoute, - // href: window.location.href, - // }); - // Redirect to target route after callback processing - // *info: this url change will remove the code and state from the URL - // * this is need to avoid invalid state in the next refresh - this.redirectRoot.next(targetRoute); - // this.router.navigate([targetRoute]); - }); - } - } - - logout() { - this.auth0Client$.subscribe((client: Auth0Client) => { - // Call method to log out - client.logout({ - client_id: this.auth0Options.clientId, - returnTo: EnvConfig['landing-page'], - }); - }); - } - - getTokenSilently$(options?): Observable { - return this.auth0Client$.pipe( - concatMap((client: Auth0Client) => from(client.getTokenSilently(options))) - ); - } - - public getIdToken(): Promise { - return new Promise((resolve, reject) => { - const token = this.getIdToken$({ ignoreCache: true }).toPromise(); - resolve(token); - }); - } - - private setSession(authResult): void { - localStorage.setItem('userid', authResult.nickname); - localStorage.setItem('user_email', authResult.email); - localStorage.setItem('user_name', authResult.name); - } - - getIdToken$(options?): Observable { - return this.auth0Client$.pipe( - // *info: if getIdToken fails , just return empty in the catchError - concatMap((client: Auth0Client) => - from(client.getIdTokenClaims(options)) - ), - concatMap((claims: any) => of((claims && claims.__raw) || '')), - catchError(() => of('')) - ); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/services/auth.utils.ts b/cla-frontend-contributor-console/src/ionic/services/auth.utils.ts deleted file mode 100644 index 01ed3cf21..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/auth.utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -export const AUTH_ROUTE = '/auth'; diff --git a/cla-frontend-contributor-console/src/ionic/services/cla.env.utils.ts b/cla-frontend-contributor-console/src/ionic/services/cla.env.utils.ts deleted file mode 100644 index 9f435d64a..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/cla.env.utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import * as env from '../../config/cla-env-config.json'; -export const EnvConfig = env as any; diff --git a/cla-frontend-contributor-console/src/ionic/services/cla.service.ts b/cla-frontend-contributor-console/src/ionic/services/cla.service.ts deleted file mode 100644 index 8fc6c5dfe..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/cla.service.ts +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; - -import 'rxjs/Rx'; - -@Injectable() -export class ClaService { - http: any; - claApiUrl: string = ''; - v4ApiUrl: string = ''; - localTesting = false; - v1ClaAPIURLLocal = 'http://localhost:5000'; - v2ClaAPIURLLocal = 'http://localhost:5000'; - v3ClaAPIURLLocal = 'http://localhost:8080'; - v4ClaAPIURLLocal = 'http://localhost:8080'; - - constructor(http: Http) { - this.http = http; - } - - /** - * Constructs a URL based on the path and endpoint host:port. - * @param path the URL path - * @returns a URL to the V1 endpoint with the specified path. If running in local mode, the endpoint will point to a - * local host:port - otherwise the endpoint will point the appropriate environment endpoint running in the cloud. - */ - private getV1Endpoint(path: string) { - let url: URL; - if (this.localTesting) { - url = new URL(this.v1ClaAPIURLLocal + path); - } else { - url = new URL(this.claApiUrl + path); - } - return url; - } - - /** - * Constructs a URL based on the path and endpoint host:port. - * @param path the URL path - * @returns a URL to the V2 endpoint with the specified path. If running in local mode, the endpoint will point to a - * local host:port - otherwise the endpoint will point the appropriate environment endpoint running in the cloud. - */ - private getV2Endpoint(path: string) { - let url: URL; - if (this.localTesting) { - url = new URL(this.v2ClaAPIURLLocal + path); - } else { - url = new URL(this.claApiUrl + path); - } - return url; - } - - - private getV4Endpoint(path: string) { - let url: URL; - if (this.localTesting) { - url = new URL(this.v2ClaAPIURLLocal + path); - } else { - url = new URL(this.v4ApiUrl + path); - } - return url; - } - - /** - * Constructs a URL based on the path and endpoint host:port. - * @param path the URL path - * @returns a URL to the V3 endpoint with the specified path. If running in local mode, the endpoint will point to a - * local host:port - otherwise the endpoint will point the appropriate environment endpoint running in the cloud. - */ - private getV3Endpoint(path: string) { - let url: URL; - if (this.localTesting) { - url = new URL(this.v3ClaAPIURLLocal + path); - } else { - url = new URL(this.claApiUrl + path); - } - return url; - } - - public isLocalTesting(flag: boolean) { - if (flag) { - console.log('Running in local services mode'); - } else { - console.log('Running in deployed services mode'); - } - this.localTesting = flag; - } - - public setApiUrl(claApiUrl: string) { - this.claApiUrl = claApiUrl; - } - - public setV4ApiUrl(v4ApiUrl: string) { - this.v4ApiUrl = v4ApiUrl + '/cla-service'; - } - - public setHttp(http: any) { - this.http = http; // allow configuration for alternate http library - } - - /** - * /user/{user_id} - */ - getUser(userId) { - const url: URL = this.getV2Endpoint('/v2/user/' + userId); - return this.http.get(url).map((res) => res.json()); - } - - getUserWithAuthToken(userId) { - const url: URL = this.getV2Endpoint('/v2/user/' + userId); - return this.http.securedGet(url).map((res) => res.json()); - } - - // creates a new account for Gerrit users, with email. - postOrGetUserForGerrit() { - const url: URL = this.getV1Endpoint('/v1/user/gerrit'); - return this.http.securedPost(url).map((res) => res.json()); - } - - /** - * Request to be added to the company Approved List (formerly WhiteList) - * - * /user/{user_id}/request-company-whitelist/{company_id} - */ - requestToBeOnCompanyApprovedList(userId, companyId, projectId, data) { - //const url: URL = this.getV2Endpoint('/v2/user/' + userId + '/request-company-whitelist/' + companyId); - const url: URL = this.getV3Endpoint(`/v3/company/${companyId}/ccla-whitelist-requests/${projectId}`); - return this.http.post(url, data);// no response .map((res) => res.json()); - } - - /** - * /user/{user_id}/invite-company-admin - */ - postEmailToCompanyAdmin(userId, data) { - const url: URL = this.getV4Endpoint('/v4/user/' + userId + '/request-company-admin'); - return this.http.post(url, data).map((res) => res); - } - - /** - * /user/{user_id}/active-signature - */ - getUserSignatureIntent(userId) { - const url: URL = this.getV2Endpoint('/v2/user/' + userId + '/active-signature'); - return this.http.get(url).map((res) => res.json()); - } - - /** - * /user/{user_id}/project/{project_id}/last-signature - **/ - - getLastIndividualSignature(userId, projectId) { - const url: URL = this.getV2Endpoint('/v2/user/' + userId + '/project/' + projectId + '/last-signature'); - return this.http.get(url).map((res) => res.json()); - } - - /** - * GET /v3/signatures/project/{project_id}/company/{company_id} - * - * @param companyId the company ID - * @param projectId the project ID - * @param pageSize the optional page size - default is 50 - * @param nextKey the next key used when asking for the next page of results - */ - getCompanyProjectSignatures(companyId, projectId, pageSize = 50, nextKey = '') { - let path: string = '/v3/signatures/project/' + projectId + '/company/' + companyId + '?pageSize=' + pageSize; - if (nextKey != null && nextKey !== '' && nextKey.trim().length > 0) { - path += '&nextKey=' + nextKey; - } - const url: URL = this.getV3Endpoint(path); - return this.http.getWithCreds(url).map((res) => res.json()); - } - - getAllCompanies() { - const url: URL = this.getV2Endpoint('/v2/company'); - return this.http.get(url).map((res) => res.json()); - } - - /** - * /company/{company_id} - **/ - getCompany(companyId) { - const url: URL = this.getV2Endpoint('/v2/company/' + companyId); - return this.http.get(url).map((res) => res.json()); - } - - /** - * /project/{project_id} - **/ - getProject(projectId) { - const url: URL = this.getV2Endpoint('/v2/project/' + projectId); - return this.http.get(url).map((res) => res.json()); - } - - getProjectWithAuthToken(projectId) { - const url: URL = this.getV2Endpoint('/v2/project/' + projectId); - return this.http.securedGet(url).map((res) => res.json()); - } - - /** - * /request-individual-signature - **/ - postIndividualSignatureRequest(signatureRequest) { - const url: URL = this.getV2Endpoint('/v2/request-individual-signature'); - return this.http.post(url, signatureRequest).map((res) => res.json()); - } - - /** - * /check-prepare-employee-signature - **/ - postCheckAndPreparedEmployeeSignature(data) { - const url: URL = this.getV2Endpoint('/v2/check-prepare-employee-signature'); - return this.http.post(url, data).map((res) => res.json()); - } - - /** - * /request-employee-signature - **/ - postEmployeeSignatureRequest(signatureRequest) { - const url: URL = this.getV2Endpoint('/v2/request-employee-signature'); - return this.http.post(url, signatureRequest).map((res) => res.json()); - } - - /** - * /company/{companyID}/ccla-whitelist-requests/{projectID} - */ - postCCLAWhitelistRequest(companyID, projectID, user) { - const url: URL = this.getV3Endpoint('/v3/company/' + companyID + '/ccla-whitelist-requests/' + projectID); - return this.http.post(url, user); - } - - getGerrit(gerritId) { - const url: URL = this.getV2Endpoint('/v2/gerrit/' + gerritId); - return this.http.securedGet(url).map((res) => res.json()); - } - - getProjectGerrits(projectId) { - const url: URL = this.getV1Endpoint('/v1/project/' + projectId + '/gerrits'); - return this.http.securedGet(url).map((res) => res.json()); - } - - getReleaseVersion() { - const url: URL = this.getV3Endpoint('/v3/ops/version'); - return this.http.getWithoutAuth(url).map((res) => res.json()); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/services/constants.ts b/cla-frontend-contributor-console/src/ionic/services/constants.ts deleted file mode 100644 index 3c6fc56af..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -export const CINCO_API_URL: string = 'https://cinco_api_endpoint'; -export const CLA_API_URL: string = 'https://cla_api_endpoint/dev-runze'; -export const ANALYTICS_API_URL: string = 'https://analytics_api_endpoint'; diff --git a/cla-frontend-contributor-console/src/ionic/services/http-client.ts b/cla-frontend-contributor-console/src/ionic/services/http-client.ts deleted file mode 100644 index 31a044f5d..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/http-client.ts +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Injectable } from '@angular/core'; -import { Headers, Http } from '@angular/http'; -import { KeycloakService } from './keycloak/keycloak.service'; -import { Observable } from 'rxjs/Rx'; -import { AuthService } from './auth.service'; - -@Injectable() -export class HttpClient { - constructor( - public http: Http, - private keycloak: KeycloakService, - private authService: AuthService - ) { } - - private buildAuthHeaders(contentType: string = 'application/json') { - let headers = new Headers({ - Accept: 'application/json', - 'Content-Type': contentType - }); - return this.authService.getIdToken().then((token) => { - if (token) { - headers.append('Authorization', 'Bearer ' + token); - return headers; - } - }); - } - - private buildWithoutAuthHeaders(contentType: string = 'application/json') { - let headers = new Headers({ - Accept: 'application/json', - 'Content-Type': contentType - }); - return Promise.resolve(headers); - } - - getWithoutAuth(url) { - return Observable.fromPromise(this.buildWithoutAuthHeaders()).switchMap((headers) => - this.http.get(url, { headers: headers }) - ); - } - - setHttp(http: Http) { - this.http = http; // allow alternate http library - } - - buildHeaders(contentType: string = 'application/json') { - let headers = new Headers({ - Accept: 'application/json', - 'Content-Type': contentType - }); - return Promise.resolve(headers); - } - - get(url) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.get(url, { headers: headers }) - ); - } - - getWithCreds(url) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.get(url, { headers: headers, withCredentials: true }) - ); - } - - post(url, data) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.post(url, data, { headers: headers }) - ); - } - - postWithCreds(url, data) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.post(url, data, { headers: headers, withCredentials: true }) - ); - } - - put(url, data, contentType: string = 'application/json') { - return Observable.fromPromise(this.buildAuthHeaders(contentType)).switchMap((headers) => - this.http.put(url, data, { headers: headers }) - ); - } - - putWithCreds(url, data, contentType: string = 'application/json') { - return Observable.fromPromise(this.buildAuthHeaders(contentType)).switchMap((headers) => - this.http.put(url, data, { headers: headers, withCredentials: true }) - ); - } - - patch(url, data, contentType: string = 'application/json') { - return Observable.fromPromise(this.buildAuthHeaders(contentType)).switchMap((headers) => - this.http.patch(url, data, { headers: headers }) - ); - } - - delete(url) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.delete(url, { headers: headers }) - ); - } - - deleteWithBody(url, body) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.delete(url, { body: body, headers: headers }) - ); - } - - deleteWithCredsAndBody(url, body) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.delete(url, { body: body, headers: headers, withCredentials: true }) - ); - } - securedGet(url) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.get(url, { headers: headers }) - ); - } - - securedPost(url, data) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.post(url, data, { headers: headers }) - ); - } - - securedPut(url, data, contentType: string = 'application/json') { - return Observable.fromPromise(this.buildAuthHeaders(contentType)).switchMap((headers) => - this.http.put(url, data, { headers: headers }) - ); - } - - securedPatch(url, data, contentType: string = 'application/json') { - return Observable.fromPromise(this.buildAuthHeaders(contentType)).switchMap((headers) => - this.http.patch(url, data, { headers: headers }) - ); - } - - securedDelete(url) { - return Observable.fromPromise(this.buildAuthHeaders()).switchMap((headers) => - this.http.delete(url, { headers: headers }) - ); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.d.ts b/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.d.ts deleted file mode 100644 index 1a84aa8f1..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.d.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -declare module KeycloakModule { - export interface Promise { - success(callback: Function): Promise; - error(callback: Function): Promise; - } - - export type ResponseModes = 'query' | 'fragment'; - export type Flows = 'standard' | 'implicit' | 'hybrid'; - - export interface InitOptions { - checkLoginIframe?: boolean; - checkLoginIframeInterval?: number; - onLoad?: string; - adapter?: string; - responseMode?: ResponseModes; - flow?: Flows; - token?: string; - refreshToken?: string; - idToken?: string; - timeSkew?: number; - } - - export interface LoginOptions { - redirectUri?: string; - prompt?: string; - maxAge?: number; - loginHint?: string; - action?: string; - locale?: string; - } - - export interface LogoutOptions { - redirectUri?: string; - } - - export interface RedirectUriOptions { - redirectUri?: string; - } - - export interface KeycloakClient { - init(options?: InitOptions): Promise; - login(options?: LoginOptions): Promise; - logout(options?: LogoutOptions): Promise; - createLoginUrl(options?: LoginOptions): string; - updateToken(options?: RedirectUriOptions): Promise; - createLogoutUrl(options?: RedirectUriOptions): string; - register(options?: LoginOptions): Promise; - createRegisterUrl(options?: RedirectUriOptions): string; - accountManagement(): Promise; - createAccountUrl(options?: RedirectUriOptions): string; - hasRealmRole(role: string): boolean; - hasResourceRole(role: string, resource?: string): boolean; - loadUserProfile(): Promise; - isTokenExpired(minValidity: number): boolean; - updateToken(minValidity: number): Promise; - clearToken(): any; - - realm: string; - clientId: string; - authServerUrl: string; - - token: string; - tokenParsed: any; - refreshToken: string; - refreshTokenParsed: any; - idToken: string; - idTokenParsed: any; - realmAccess: any; - resourceAccess: any; - authenticated: boolean; - subject: string; - timeSkew: number; - responseMode: ResponseModes; - flow: Flows; - responseType: string; - - onReady: Function; - onAuthSuccess: Function; - onAuthError: Function; - onAuthRefreshSuccess: Function; - onAuthRefreshError: Function; - onAuthLogout: Function; - onTokenExpired: Function; - } -} - -declare var Keycloak: { - new (config?: any): KeycloakModule.KeycloakClient; -}; diff --git a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.http.ts b/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.http.ts deleted file mode 100644 index c611ba43b..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.http.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Injectable } from '@angular/core'; -import { - Http, - Request, - XHRBackend, - ConnectionBackend, - RequestOptions, - RequestOptionsArgs, - Response, - Headers -} from '@angular/http'; - -import { KeycloakService } from './keycloak.service'; -import { Observable } from 'rxjs/Rx'; - -/** - * This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request. - */ -@Injectable() -export class KeycloakHttp extends Http { - constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) { - super(_backend, _defaultOptions); - } - - request(url: string | Request, options?: RequestOptionsArgs): Observable { - if (!this._keycloakService.authenticated()) return super.request(url, options); - - const tokenPromise: Promise = this._keycloakService.getToken(); - const tokenObservable: Observable = Observable.fromPromise(tokenPromise); - - if (typeof url === 'string') { - return tokenObservable - .map((token) => { - const authOptions = new RequestOptions({ headers: new Headers({ Authorization: 'Bearer ' + token }) }); - return new RequestOptions().merge(options).merge(authOptions); - }) - .concatMap((opts) => super.request(url, opts)); - } else if (url instanceof Request) { - return tokenObservable - .map((token) => { - url.headers.set('Authorization', 'Bearer ' + token); - return url; - }) - .concatMap((request) => super.request(request)); - } - } -} - -export function keycloakHttpFactory( - backend: XHRBackend, - defaultOptions: RequestOptions, - keycloakService: KeycloakService -) { - return new KeycloakHttp(backend, defaultOptions, keycloakService); -} - -export const KEYCLOAK_HTTP_PROVIDER = { - provide: Http, - useFactory: keycloakHttpFactory, - deps: [XHRBackend, RequestOptions, KeycloakService] -}; diff --git a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.js b/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.js deleted file mode 100644 index 7534e0826..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.js +++ /dev/null @@ -1,1327 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -(function(window, undefined) { - var Keycloak = function(config) { - if (!(this instanceof Keycloak)) { - return new Keycloak(config); - } - - var kc = this; - var adapter; - var refreshQueue = []; - var callbackStorage; - - var loginIframe = { - enable: true, - callbackList: [], - interval: 5 - }; - - var scripts = document.getElementsByTagName('script'); - for (var i = 0; i < scripts.length; i++) { - if ( - (scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && - scripts[i].src.indexOf('version=') !== -1 - ) { - kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0]; - } - } - - kc.init = function(initOptions) { - kc.authenticated = false; - - callbackStorage = createCallbackStorage(); - - if (initOptions && initOptions.adapter === 'cordova') { - adapter = loadAdapter('cordova'); - } else if (initOptions && initOptions.adapter === 'default') { - adapter = loadAdapter(); - } else { - if (window.Cordova || window.cordova) { - adapter = loadAdapter('cordova'); - } else { - adapter = loadAdapter(); - } - } - - if (initOptions) { - if (typeof initOptions.checkLoginIframe !== 'undefined') { - loginIframe.enable = initOptions.checkLoginIframe; - } - - if (initOptions.checkLoginIframeInterval) { - loginIframe.interval = initOptions.checkLoginIframeInterval; - } - - if (initOptions.onLoad === 'login-required') { - kc.loginRequired = true; - } - - if (initOptions.responseMode) { - if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') { - kc.responseMode = initOptions.responseMode; - } else { - throw 'Invalid value for responseMode'; - } - } - - if (initOptions.flow) { - switch (initOptions.flow) { - case 'standard': - kc.responseType = 'code'; - break; - case 'implicit': - kc.responseType = 'id_token token'; - break; - case 'hybrid': - kc.responseType = 'code id_token token'; - break; - default: - throw 'Invalid value for flow'; - } - kc.flow = initOptions.flow; - } - - if (initOptions.timeSkew != null) { - kc.timeSkew = initOptions.timeSkew; - } - } - - if (!kc.responseMode) { - kc.responseMode = 'fragment'; - } - if (!kc.responseType) { - kc.responseType = 'code'; - kc.flow = 'standard'; - } - - var promise = createPromise(); - - var initPromise = createPromise(); - initPromise.promise - .success(function() { - kc.onReady && kc.onReady(kc.authenticated); - promise.setSuccess(kc.authenticated); - }) - .error(function(errorData) { - promise.setError(errorData); - }); - - var configPromise = loadConfig(config); - - function onLoad() { - var doLogin = function(prompt) { - if (!prompt) { - options.prompt = 'none'; - } - kc.login(options) - .success(function() { - initPromise.setSuccess(); - }) - .error(function() { - initPromise.setError(); - }); - }; - - var options = {}; - switch (initOptions.onLoad) { - case 'check-sso': - if (loginIframe.enable) { - setupCheckLoginIframe().success(function() { - checkLoginIframe() - .success(function() { - doLogin(false); - }) - .error(function() { - initPromise.setSuccess(); - }); - }); - } else { - doLogin(false); - } - break; - case 'login-required': - doLogin(true); - break; - default: - throw 'Invalid value for onLoad'; - } - } - - function processInit() { - var callback = parseCallback(window.location.href); - - if (callback) { - setupCheckLoginIframe(); - window.history.replaceState({}, null, callback.newUrl); - processCallback(callback, initPromise); - return; - } else if (initOptions) { - if (initOptions.token && initOptions.refreshToken) { - setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); - - if (loginIframe.enable) { - setupCheckLoginIframe().success(function() { - checkLoginIframe() - .success(function() { - kc.onAuthSuccess && kc.onAuthSuccess(); - initPromise.setSuccess(); - }) - .error(function() { - setToken(null, null, null); - initPromise.setSuccess(); - }); - }); - } else { - kc.updateToken(-1) - .success(function() { - kc.onAuthSuccess && kc.onAuthSuccess(); - initPromise.setSuccess(); - }) - .error(function() { - kc.onAuthError && kc.onAuthError(); - if (initOptions.onLoad) { - onLoad(); - } else { - initPromise.setError(); - } - }); - } - } else if (initOptions.onLoad) { - onLoad(); - } else { - initPromise.setSuccess(); - } - } else { - initPromise.setSuccess(); - } - } - - configPromise.success(processInit); - configPromise.error(function() { - promise.setError(); - }); - - return promise.promise; - }; - - kc.login = function(options) { - return adapter.login(options); - }; - - kc.createLoginUrl = function(options) { - var state = createUUID(); - var nonce = createUUID(); - - var redirectUri = adapter.redirectUri(options); - - var callbackState = { - state: state, - nonce: nonce, - redirectUri: encodeURIComponent(redirectUri) - }; - - if (options && options.prompt) { - callbackState.prompt = options.prompt; - } - - callbackStorage.add(callbackState); - - var action = 'auth'; - if (options && options.action == 'register') { - action = 'registrations'; - } - - var scope = options && options.scope ? 'openid ' + options.scope : 'openid'; - - var url = - getRealmUrl() + - '/protocol/openid-connect/' + - action + - '?client_id=' + - encodeURIComponent(kc.clientId) + - '&redirect_uri=' + - encodeURIComponent(redirectUri) + - '&state=' + - encodeURIComponent(state) + - '&nonce=' + - encodeURIComponent(nonce) + - '&response_mode=' + - encodeURIComponent(kc.responseMode) + - '&response_type=' + - encodeURIComponent(kc.responseType) + - '&scope=' + - encodeURIComponent(scope); - - if (options && options.prompt) { - url += '&prompt=' + encodeURIComponent(options.prompt); - } - - if (options && options.maxAge) { - url += '&max_age=' + encodeURIComponent(options.maxAge); - } - - if (options && options.loginHint) { - url += '&login_hint=' + encodeURIComponent(options.loginHint); - } - - if (options && options.idpHint) { - url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint); - } - - if (options && options.locale) { - url += '&ui_locales=' + encodeURIComponent(options.locale); - } - - return url; - }; - - kc.logout = function(options) { - return adapter.logout(options); - }; - - kc.createLogoutUrl = function(options) { - var url = - getRealmUrl() + - '/protocol/openid-connect/logout' + - '?redirect_uri=' + - encodeURIComponent(adapter.redirectUri(options, false)); - - return url; - }; - - kc.register = function(options) { - return adapter.register(options); - }; - - kc.createRegisterUrl = function(options) { - if (!options) { - options = {}; - } - options.action = 'register'; - return kc.createLoginUrl(options); - }; - - kc.createAccountUrl = function(options) { - var url = - getRealmUrl() + - '/account' + - '?referrer=' + - encodeURIComponent(kc.clientId) + - '&referrer_uri=' + - encodeURIComponent(adapter.redirectUri(options)); - - return url; - }; - - kc.accountManagement = function() { - return adapter.accountManagement(); - }; - - kc.hasRealmRole = function(role) { - var access = kc.realmAccess; - return !!access && access.roles.indexOf(role) >= 0; - }; - - kc.hasResourceRole = function(role, resource) { - if (!kc.resourceAccess) { - return false; - } - - var access = kc.resourceAccess[resource || kc.clientId]; - return !!access && access.roles.indexOf(role) >= 0; - }; - - kc.loadUserProfile = function() { - var url = getRealmUrl() + '/account'; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Accept', 'application/json'); - req.setRequestHeader('Authorization', 'bearer ' + kc.token); - - var promise = createPromise(); - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200) { - kc.profile = JSON.parse(req.responseText); - promise.setSuccess(kc.profile); - } else { - promise.setError(); - } - } - }; - - req.send(); - - return promise.promise; - }; - - kc.loadUserInfo = function() { - var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Accept', 'application/json'); - req.setRequestHeader('Authorization', 'bearer ' + kc.token); - - var promise = createPromise(); - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200) { - kc.userInfo = JSON.parse(req.responseText); - promise.setSuccess(kc.userInfo); - } else { - promise.setError(); - } - } - }; - - req.send(); - - return promise.promise; - }; - - kc.isTokenExpired = function(minValidity) { - if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit')) { - throw 'Not authenticated'; - } - - if (kc.timeSkew == null) { - console.info('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set'); - return true; - } - - var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew; - if (minValidity) { - expiresIn -= minValidity; - } - return expiresIn < 0; - }; - - kc.updateToken = function(minValidity) { - var promise = createPromise(); - - if (!kc.refreshToken) { - promise.setError(); - return promise.promise; - } - - minValidity = minValidity || 5; - - var exec = function() { - var refreshToken = false; - if (minValidity == -1) { - refreshToken = true; - console.info('[KEYCLOAK] Refreshing token: forced refresh'); - } else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) { - refreshToken = true; - console.info('[KEYCLOAK] Refreshing token: token expired'); - } - - if (!refreshToken) { - promise.setSuccess(false); - } else { - var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; - var url = getRealmUrl() + '/protocol/openid-connect/token'; - - refreshQueue.push(promise); - - if (refreshQueue.length == 1) { - var req = new XMLHttpRequest(); - req.open('POST', url, true); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - req.withCredentials = false; - - if (kc.clientId && kc.clientSecret) { - req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); - } else { - params += '&client_id=' + encodeURIComponent(kc.clientId); - } - - var timeLocal = new Date().getTime(); - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200) { - console.info('[KEYCLOAK] Token refreshed'); - - timeLocal = (timeLocal + new Date().getTime()) / 2; - - var tokenResponse = JSON.parse(req.responseText); - - setToken( - tokenResponse['access_token'], - tokenResponse['refresh_token'], - tokenResponse['id_token'], - timeLocal - ); - - kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); - for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { - p.setSuccess(true); - } - } else { - console.warn('[KEYCLOAK] Failed to refresh token'); - - kc.onAuthRefreshError && kc.onAuthRefreshError(); - for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { - p.setError(true); - } - } - } - }; - - req.send(params); - } - } - }; - - if (loginIframe.enable) { - var iframePromise = checkLoginIframe(); - iframePromise - .success(function() { - exec(); - }) - .error(function() { - promise.setError(); - }); - } else { - exec(); - } - - return promise.promise; - }; - - kc.clearToken = function() { - if (kc.token) { - setToken(null, null, null); - kc.onAuthLogout && kc.onAuthLogout(); - if (kc.loginRequired) { - kc.login(); - } - } - }; - - function getRealmUrl() { - if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { - return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); - } else { - return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); - } - } - - function getOrigin() { - if (!window.location.origin) { - return ( - window.location.protocol + - '//' + - window.location.hostname + - (window.location.port ? ':' + window.location.port : '') - ); - } else { - return window.location.origin; - } - } - - function processCallback(oauth, promise) { - var code = oauth.code; - var error = oauth.error; - var prompt = oauth.prompt; - - var timeLocal = new Date().getTime(); - - if (error) { - if (prompt != 'none') { - var errorData = { error: error, error_description: oauth.error_description }; - kc.onAuthError && kc.onAuthError(errorData); - promise && promise.setError(errorData); - } else { - promise && promise.setSuccess(); - } - return; - } else if (kc.flow != 'standard' && (oauth.access_token || oauth.id_token)) { - authSuccess(oauth.access_token, null, oauth.id_token, true); - } - - if (kc.flow != 'implicit' && code) { - var params = 'code=' + code + '&grant_type=authorization_code'; - var url = getRealmUrl() + '/protocol/openid-connect/token'; - - var req = new XMLHttpRequest(); - req.open('POST', url, true); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - - if (kc.clientId && kc.clientSecret) { - req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); - } else { - params += '&client_id=' + encodeURIComponent(kc.clientId); - } - - params += '&redirect_uri=' + oauth.redirectUri; - - req.withCredentials = false; - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200) { - var tokenResponse = JSON.parse(req.responseText); - authSuccess( - tokenResponse['access_token'], - tokenResponse['refresh_token'], - tokenResponse['id_token'], - kc.flow === 'standard' - ); - } else { - kc.onAuthError && kc.onAuthError(); - promise && promise.setError(); - } - } - }; - - req.send(params); - } - - function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) { - timeLocal = (timeLocal + new Date().getTime()) / 2; - - setToken(accessToken, refreshToken, idToken, timeLocal); - - if ( - (kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || - (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) || - (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce) - ) { - console.info('[KEYCLOAK] Invalid nonce, clearing token'); - kc.clearToken(); - promise && promise.setError(); - } else { - if (fulfillPromise) { - kc.onAuthSuccess && kc.onAuthSuccess(); - promise && promise.setSuccess(); - } - } - } - } - - function loadConfig(url) { - var promise = createPromise(); - var configUrl; - - if (!config) { - configUrl = 'keycloak.json'; - } else if (typeof config === 'string') { - configUrl = config; - } - - if (configUrl) { - var req = new XMLHttpRequest(); - req.open('GET', configUrl, true); - req.setRequestHeader('Accept', 'application/json'); - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200 || fileLoaded(req)) { - var config = JSON.parse(req.responseText); - - kc.authServerUrl = config['auth-server-url']; - kc.realm = config['realm']; - kc.clientId = config['resource']; - kc.clientSecret = (config['credentials'] || {})['secret']; - - promise.setSuccess(); - } else { - promise.setError(); - } - } - }; - - req.send(); - } else { - if (!config['url']) { - var scripts = document.getElementsByTagName('script'); - for (var i = 0; i < scripts.length; i++) { - if (scripts[i].src.match(/.*keycloak\.js/)) { - config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); - break; - } - } - } - - if (!config.realm) { - throw 'realm missing'; - } - - if (!config.clientId) { - throw 'clientId missing'; - } - - kc.authServerUrl = config.url; - kc.realm = config.realm; - kc.clientId = config.clientId; - kc.clientSecret = (config.credentials || {}).secret; - - promise.setSuccess(); - } - - return promise.promise; - } - - function fileLoaded(xhr) { - return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:'); - } - - function setToken(token, refreshToken, idToken, timeLocal) { - if (kc.tokenTimeoutHandle) { - clearTimeout(kc.tokenTimeoutHandle); - kc.tokenTimeoutHandle = null; - } - - if (refreshToken) { - kc.refreshToken = refreshToken; - kc.refreshTokenParsed = decodeToken(refreshToken); - } else { - delete kc.refreshToken; - delete kc.refreshTokenParsed; - } - - if (idToken) { - kc.idToken = idToken; - kc.idTokenParsed = decodeToken(idToken); - } else { - delete kc.idToken; - delete kc.idTokenParsed; - } - - if (token) { - kc.token = token; - kc.tokenParsed = decodeToken(token); - kc.sessionId = kc.tokenParsed.session_state; - kc.authenticated = true; - kc.subject = kc.tokenParsed.sub; - kc.realmAccess = kc.tokenParsed.realm_access; - kc.resourceAccess = kc.tokenParsed.resource_access; - - if (timeLocal) { - kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; - } - - if (kc.timeSkew != null) { - console.info( - '[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds' - ); - - if (kc.onTokenExpired) { - var expiresIn = (kc.tokenParsed['exp'] - new Date().getTime() / 1000 + kc.timeSkew) * 1000; - console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s'); - if (expiresIn <= 0) { - kc.onTokenExpired(); - } else { - kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn); - } - } - } - } else { - delete kc.token; - delete kc.tokenParsed; - delete kc.subject; - delete kc.realmAccess; - delete kc.resourceAccess; - - kc.authenticated = false; - } - } - - function decodeToken(str) { - str = str.split('.')[1]; - - str = str.replace('/-/g', '+'); - str = str.replace('/_/g', '/'); - switch (str.length % 4) { - case 0: - break; - case 2: - str += '=='; - break; - case 3: - str += '='; - break; - default: - throw 'Invalid token'; - } - - str = (str + '===').slice(0, str.length + (str.length % 4)); - str = str.replace(/-/g, '+').replace(/_/g, '/'); - - str = decodeURIComponent(escape(atob(str))); - - str = JSON.parse(str); - return str; - } - - function createUUID() { - var s = []; - var hexDigits = '0123456789abcdef'; - for (var i = 0; i < 36; i++) { - s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); - } - s[14] = '4'; - s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); - s[8] = s[13] = s[18] = s[23] = '-'; - var uuid = s.join(''); - return uuid; - } - - kc.callback_id = 0; - - function createCallbackId() { - var id = ''; - return id; - } - - function parseCallback(url) { - var oauth = new CallbackParser(url, kc.responseMode).parseUri(); - var oauthState = callbackStorage.get(oauth.state); - - if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) { - oauth.redirectUri = oauthState.redirectUri; - oauth.storedNonce = oauthState.nonce; - oauth.prompt = oauthState.prompt; - - if (oauth.fragment) { - oauth.newUrl += '#' + oauth.fragment; - } - - return oauth; - } - } - - function createPromise() { - var p = { - setSuccess: function(result) { - p.success = true; - p.result = result; - if (p.successCallback) { - p.successCallback(result); - } - }, - - setError: function(result) { - p.error = true; - p.result = result; - if (p.errorCallback) { - p.errorCallback(result); - } - }, - - promise: { - success: function(callback) { - if (p.success) { - callback(p.result); - } else if (!p.error) { - p.successCallback = callback; - } - return p.promise; - }, - error: function(callback) { - if (p.error) { - callback(p.result); - } else if (!p.success) { - p.errorCallback = callback; - } - return p.promise; - } - } - }; - return p; - } - - function setupCheckLoginIframe() { - var promise = createPromise(); - - if (!loginIframe.enable) { - promise.setSuccess(); - return promise.promise; - } - - if (loginIframe.iframe) { - promise.setSuccess(); - return promise.promise; - } - - var iframe = document.createElement('iframe'); - loginIframe.iframe = iframe; - - iframe.onload = function() { - var realmUrl = getRealmUrl(); - if (realmUrl.charAt(0) === '/') { - loginIframe.iframeOrigin = getOrigin(); - } else { - loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); - } - promise.setSuccess(); - - setTimeout(check, loginIframe.interval * 1000); - }; - - var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; - if (kc.iframeVersion) { - src = src + '?version=' + kc.iframeVersion; - } - - iframe.setAttribute('src', src); - iframe.setAttribute('title', 'keycloak-session-iframe'); - iframe.style.display = 'none'; - document.body.appendChild(iframe); - - var messageCallback = function(event) { - if (event.origin !== loginIframe.iframeOrigin || loginIframe.iframe.contentWindow !== event.source) { - return; - } - - if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) { - return; - } - - if (event.data != 'unchanged') { - kc.clearToken(); - } - - var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length); - - for (var i = callbacks.length - 1; i >= 0; --i) { - var promise = callbacks[i]; - if (event.data == 'unchanged') { - promise.setSuccess(); - } else { - promise.setError(); - } - } - }; - - window.addEventListener('message', messageCallback, false); - - var check = function() { - checkLoginIframe(); - if (kc.token) { - setTimeout(check, loginIframe.interval * 1000); - } - }; - - return promise.promise; - } - - function checkLoginIframe() { - var promise = createPromise(); - - if (loginIframe.iframe && loginIframe.iframeOrigin) { - var msg = kc.clientId + ' ' + kc.sessionId; - loginIframe.callbackList.push(promise); - var origin = loginIframe.iframeOrigin; - if (loginIframe.callbackList.length == 1) { - loginIframe.iframe.contentWindow.postMessage(msg, origin); - } - } else { - promise.setSuccess(); - } - - return promise.promise; - } - - function loadAdapter(type) { - if (!type || type == 'default') { - return { - login: function(options) { - window.location.href = kc.createLoginUrl(options); - return createPromise().promise; - }, - - logout: function(options) { - window.location.href = kc.createLogoutUrl(options); - return createPromise().promise; - }, - - register: function(options) { - window.location.href = kc.createRegisterUrl(options); - return createPromise().promise; - }, - - accountManagement: function() { - window.location.href = kc.createAccountUrl(); - return createPromise().promise; - }, - - redirectUri: function(options, encodeHash) { - if (arguments.length == 1) { - encodeHash = true; - } - - if (options && options.redirectUri) { - return options.redirectUri; - } else if (kc.redirectUri) { - return kc.redirectUri; - } else { - var redirectUri = location.href; - if (location.hash && encodeHash) { - redirectUri = redirectUri.substring(0, location.href.indexOf('#')); - redirectUri += - (redirectUri.indexOf('?') == -1 ? '?' : '&') + - 'redirect_fragment=' + - encodeURIComponent(location.hash.substring(1)); - } - return redirectUri; - } - } - }; - } - - if (type == 'cordova') { - loginIframe.enable = false; - var cordovaOpenWindowWrapper = function(loginUrl, target, options) { - if (window.cordova && window.cordova.InAppBrowser) { - // Use inappbrowser for IOS and Android if available - return window.cordova.InAppBrowser.open(loginUrl, target, options); - } else { - return window.open(loginUrl, target, options); - } - }; - return { - login: function(options) { - var promise = createPromise(); - - var o = 'location=no'; - if (options && options.prompt == 'none') { - o += ',hidden=yes'; - } - - var loginUrl = kc.createLoginUrl(options); - var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o); - var completed = false; - - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - var callback = parseCallback(event.url); - processCallback(callback, promise); - ref.close(); - completed = true; - } - }); - - ref.addEventListener('loaderror', function(event) { - if (!completed) { - if (event.url.indexOf('http://localhost') == 0) { - var callback = parseCallback(event.url); - processCallback(callback, promise); - ref.close(); - completed = true; - } else { - promise.setError(); - ref.close(); - } - } - }); - - return promise.promise; - }, - - logout: function(options) { - var promise = createPromise(); - - var logoutUrl = kc.createLogoutUrl(options); - var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); - - var error; - - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - - ref.addEventListener('loaderror', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } else { - error = true; - ref.close(); - } - }); - - ref.addEventListener('exit', function(event) { - if (error) { - promise.setError(); - } else { - kc.clearToken(); - promise.setSuccess(); - } - }); - - return promise.promise; - }, - - register: function() { - var registerUrl = kc.createRegisterUrl(); - var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no'); - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - }, - - accountManagement: function() { - var accountUrl = kc.createAccountUrl(); - var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no'); - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - }, - - redirectUri: function(options) { - return 'http://localhost'; - } - }; - } - - throw 'invalid adapter type: ' + type; - } - - var LocalStorage = function() { - if (!(this instanceof LocalStorage)) { - return new LocalStorage(); - } - - localStorage.setItem('kc-test', 'test'); - localStorage.removeItem('kc-test'); - - var cs = this; - - function clearExpired() { - var time = new Date().getTime(); - for (var i = 0; i < localStorage.length; i++) { - var key = localStorage.key(i); - if (key && key.indexOf('kc-callback-') == 0) { - var value = localStorage.getItem(key); - if (value) { - try { - var expires = JSON.parse(value).expires; - if (!expires || expires < time) { - localStorage.removeItem(key); - } - } catch (err) { - localStorage.removeItem(key); - } - } - } - } - } - - cs.get = function(state) { - if (!state) { - return; - } - - var key = 'kc-callback-' + state; - var value = localStorage.getItem(key); - if (value) { - localStorage.removeItem(key); - value = JSON.parse(value); - } - - clearExpired(); - return value; - }; - - cs.add = function(state) { - clearExpired(); - - var key = 'kc-callback-' + state.state; - state.expires = new Date().getTime() + 60 * 60 * 1000; - localStorage.setItem(key, JSON.stringify(state)); - }; - }; - - var CookieStorage = function() { - if (!(this instanceof CookieStorage)) { - return new CookieStorage(); - } - - var cs = this; - - cs.get = function(state) { - if (!state) { - return; - } - - var value = getCookie('kc-callback-' + state); - setCookie('kc-callback-' + state, '', cookieExpiration(-100)); - if (value) { - return JSON.parse(value); - } - }; - - cs.add = function(state) { - setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60)); - }; - - cs.removeItem = function(key) { - setCookie(key, '', cookieExpiration(-100)); - }; - - var cookieExpiration = function(minutes) { - var exp = new Date(); - exp.setTime(exp.getTime() + minutes * 60 * 1000); - return exp; - }; - - var getCookie = function(key) { - var name = key + '='; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return ''; - }; - - var setCookie = function(key, value, expirationDate) { - var cookie = key + '=' + value + '; ' + 'expires=' + expirationDate.toUTCString() + '; '; - document.cookie = cookie; - }; - }; - - function createCallbackStorage() { - try { - return new LocalStorage(); - } catch (err) {} - - return new CookieStorage(); - } - - var CallbackParser = function(uriToParse, responseMode) { - if (!(this instanceof CallbackParser)) { - return new CallbackParser(uriToParse, responseMode); - } - var parser = this; - - var initialParse = function() { - var baseUri = null; - var queryString = null; - var fragmentString = null; - - var questionMarkIndex = uriToParse.indexOf('?'); - var fragmentIndex = uriToParse.indexOf('#', questionMarkIndex + 1); - if (questionMarkIndex == -1 && fragmentIndex == -1) { - baseUri = uriToParse; - } else if (questionMarkIndex != -1) { - baseUri = uriToParse.substring(0, questionMarkIndex); - queryString = uriToParse.substring(questionMarkIndex + 1); - if (fragmentIndex != -1) { - fragmentIndex = queryString.indexOf('#'); - fragmentString = queryString.substring(fragmentIndex + 1); - queryString = queryString.substring(0, fragmentIndex); - } - } else { - baseUri = uriToParse.substring(0, fragmentIndex); - fragmentString = uriToParse.substring(fragmentIndex + 1); - } - - return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString }; - }; - - var parseParams = function(paramString) { - var result = {}; - var params = paramString.split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - var paramName = decodeURIComponent(p[0]); - var paramValue = decodeURIComponent(p[1]); - result[paramName] = paramValue; - } - return result; - }; - - var handleQueryParam = function(paramName, paramValue, oauth) { - var supportedOAuthParams = ['code', 'state', 'error', 'error_description']; - - for (var i = 0; i < supportedOAuthParams.length; i++) { - if (paramName === supportedOAuthParams[i]) { - oauth[paramName] = paramValue; - return true; - } - } - return false; - }; - - parser.parseUri = function() { - var parsedUri = initialParse(); - - var queryParams = {}; - if (parsedUri.queryString) { - queryParams = parseParams(parsedUri.queryString); - } - - var oauth = { newUrl: parsedUri.baseUri }; - for (var param in queryParams) { - switch (param) { - case 'redirect_fragment': - oauth.fragment = queryParams[param]; - break; - default: - if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { - oauth.newUrl += - (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); - } - break; - } - } - - if (responseMode === 'fragment') { - var fragmentParams = {}; - if (parsedUri.fragmentString) { - fragmentParams = parseParams(parsedUri.fragmentString); - } - for (var param in fragmentParams) { - oauth[param] = fragmentParams[param]; - } - } - - return oauth; - }; - }; - }; - - if (typeof module === 'object' && module && typeof module.exports === 'object') { - module.exports = Keycloak; - } else { - window.Keycloak = Keycloak; - - if (typeof define === 'function' && define.amd) { - define('keycloak', [], function() { - return Keycloak; - }); - } - } -})(window); diff --git a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.service.ts b/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.service.ts deleted file mode 100644 index 8bb2c4b75..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/keycloak/keycloak.service.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// - -import { Injectable } from '@angular/core'; - -var Keycloak = require('./keycloak'); // load keycloak.js locally -type KeycloakClient = KeycloakModule.KeycloakClient; - -@Injectable() -export class KeycloakService { - static keycloakAuth: KeycloakClient = Keycloak('assets/keycloak.json'); - - static init(options?: any): Promise { - return new Promise((resolve, reject) => { - KeycloakService.keycloakAuth - .init(options) - .success(() => { - resolve(); - }) - .error((errorData: any) => { - reject(errorData); - }); - }); - } - - authenticated = (): boolean => { - return KeycloakService.keycloakAuth.authenticated; - }; - - login() { - KeycloakService.keycloakAuth.login(); - } - - logout = () => { - return KeycloakService.keycloakAuth.logout(); - }; - - account() { - KeycloakService.keycloakAuth.accountManagement(); - } - - createLogoutUrl = () => { - return KeycloakService.keycloakAuth.createLogoutUrl(); - }; - - profile(): Promise { - return new Promise((resolve, reject) => { - KeycloakService.keycloakAuth - .loadUserProfile() - .success((profile: any) => { - resolve(profile); - }) - .error((errorData: any) => { - reject(errorData); - }); - }); - } - - getToken(): Promise { - return new Promise((resolve, reject) => { - if (KeycloakService.keycloakAuth.token) { - KeycloakService.keycloakAuth - .updateToken(5) - .success(() => { - resolve(KeycloakService.keycloakAuth.token); - }) - .error(() => { - this.login(); - return reject('Failed to refresh token'); - }); - } else { - this.login(); - return reject('Not logged in'); - } - }); - } - - getTokenParsed(): Promise { - return new Promise((resolve, reject) => { - if (KeycloakService.keycloakAuth.tokenParsed) { - KeycloakService.keycloakAuth - .updateToken(5) - .success(() => { - resolve(KeycloakService.keycloakAuth.tokenParsed); - }) - .error(() => { - this.login(); - return reject('Failed to refresh token'); - }); - } else { - this.login(); - return reject('Not logged in'); - } - }); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/services/lfx-header.service.ts b/cla-frontend-contributor-console/src/ionic/services/lfx-header.service.ts deleted file mode 100644 index 0d6610b23..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/lfx-header.service.ts +++ /dev/null @@ -1,27 +0,0 @@ - -import { Injectable } from '@angular/core'; -import { AuthService } from './auth.service'; - -@Injectable() -export class LfxHeaderService { - - constructor( - private auth: AuthService - ) { - this.setUserInLFxHeader(); - } - - setUserInLFxHeader(): void { - setTimeout(() => { - const lfHeaderEl: any = document.getElementById('lfx-header'); - if (!lfHeaderEl) { - return; - } - this.auth.userProfile$.subscribe((data) => { - if (data) { - lfHeaderEl.authuser = data; - } - }); - }, 1000); - } -} diff --git a/cla-frontend-contributor-console/src/ionic/services/roles.service.ts b/cla-frontend-contributor-console/src/ionic/services/roles.service.ts deleted file mode 100644 index b3951450e..000000000 --- a/cla-frontend-contributor-console/src/ionic/services/roles.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { AuthService } from './auth.service'; - -@Injectable() -export class RolesService { - // public userAuthenticated: boolean; - // public userRoleDefaults: any; - // public userRoles: any; - // private getDataObserver: any; - // public getData: any; - // private rolesFetched: boolean; - - // private LF_USERNAME_CLAIM = 'https://sso.linuxfoundation.org/claims/username'; - // private CLA_PROJECT_ADMIN = 'cla-system-admin'; - - // constructor(private authService: AuthService) { - // this.rolesFetched = false; - // this.userRoleDefaults = { - // isAuthenticated: this.authService.isAuthenticated(), - // isPmcUser: false, - // isStaffInc: false, - // isDirectorInc: false, - // isStaffDirect: false, - // isDirectorDirect: false, - // isExec: false, - // isAdmin: false - // }; - // this.userRoles = this.userRoleDefaults; - // } - - // ////////////////////////////////////////////////////////////////////////////// - - // /** - // * This service should ONLY contain methods for user roles - // **/ - - // ////////////////////////////////////////////////////////////////////////////// - // ////////////////////////////////////////////////////////////////////////////// - - // getUserRolesPromise() { - // console.log('Get UserRole Promise.'); - // if (this.authService.isAuthenticated()) { - // return this.authService - // .getIdToken() - // .then((token) => { - // return this.authService.parseIdToken(token); - // }) - // .then((tokenParsed) => { - // if (tokenParsed && tokenParsed[this.LF_USERNAME_CLAIM]) { - // this.userRoles = { - // isAuthenticated: this.authService.isAuthenticated(), - // isPmcUser: false, - // isStaffInc: false, - // isDirectorInc: false, - // isStaffDirect: false, - // isDirectorDirect: false, - // isExec: false, - // isAdmin: false - // }; - - // return this.userRoles; - // } - - // return this.userRoleDefaults; - // }) - // .catch((error) => { - // return Promise.resolve(this.userRoleDefaults); - // }); - // } else { - // // not authenticated. can't decode token. just return defaults - // return Promise.resolve(this.userRoleDefaults); - // } - // } - - // private isInArray(roles, role) { - // for (let i = 0; i < roles.length; i++) { - // if (roles[i].toLowerCase() === role.toLowerCase()) { - // return true; - // } - // } - // return false; - // } - - ////////////////////////////////////////////////////////////////////////////// -} diff --git a/cla-frontend-contributor-console/src/ionic/theme/footer.scss b/cla-frontend-contributor-console/src/ionic/theme/footer.scss deleted file mode 100644 index c1a711204..000000000 --- a/cla-frontend-contributor-console/src/ionic/theme/footer.scss +++ /dev/null @@ -1,36 +0,0 @@ -ion-footer { - background-image: none; - background-color: #4c79b6; - color: #ffffff; - .grid { - padding: 0; - .col { - padding: 15px 15px; - } - } - a { - color: #ffffff; - text-decoration: none; - } - - hr { - border: 0 none; - border-bottom: 1px solid #ffffff; - margin: 0; - } - picture, - img { - margin: -10px 0 -10px -13px; - width: 175px; - padding: 1rem; - } - button { - background-color: #ffa400; - padding: 8px 12px; - color: #ffffff; - font-size: 12px; - border-radius: 200px; - cursor: pointer; - border: 0; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/theme/modal.scss b/cla-frontend-contributor-console/src/ionic/theme/modal.scss deleted file mode 100644 index 1b61fd252..000000000 --- a/cla-frontend-contributor-console/src/ionic/theme/modal.scss +++ /dev/null @@ -1,28 +0,0 @@ -ion-modal { - .grid { - padding: 0 16px; - } - .toolbar { - padding: 1rem 1rem 1rem 2rem; - } - .toolbar { - background-color: #f5f5f5; - color: black; - } - .footer-seamless { - background-color: transparent; - border-color: transparent; - &:before { - display: none; - } - .toolbar { - background-color: inherit; - } - .toolbar-background { - border-color: inherit; - } - } - .content { - background-color: #fff; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/theme/styles.scss b/cla-frontend-contributor-console/src/ionic/theme/styles.scss deleted file mode 100644 index c310e8de0..000000000 --- a/cla-frontend-contributor-console/src/ionic/theme/styles.scss +++ /dev/null @@ -1,117 +0,0 @@ -@import './table'; -@import './modal'; -@import './footer'; - -ion-icon { - color: color($colors, gray); -} - -ion-icon[name='menu'] { - color: color($colors, white); -} - -.toolbar { - background-color: color($colors, white); - color: color($colors, gray); - padding: 4px 25px; - .bar-button { - color: inherit; - &:hover:not(.disable-hover) { - color: inherit; - } - } - .toolbar-background { - background-color: inherit; - } - .toolbar-title { - color: inherit; - } - .toolbar-content { - display: flex; - } -} - -.content { - background-color: color($colors, light); -} - -.grid { - max-width: 160rem; -} - -ion-content .scroll-content > header { - margin-top: -16px; - margin-right: -16px; - margin-left: -16px; - padding: 16px; - position: relative; - background-color: #fff; - &:after { - content: ''; - @extend .header-md::after; - } -} - -ion-menu { - .header { - &:after { - background-image: none; - border-top: 1px solid color($colors, dark); - } - .app-logo { - display: block; - margin: 2rem 2rem; - max-width: 150px; - } - } - - .menu-list { - padding-left: 2rem; - .item { - font-weight: bold; - } - .menu-list { - padding-bottom: 2rem; - .item { - font-weight: normal; - } - } - } - - .item-md { - &.item-block .item-inner { - border-bottom: 0; - } - } - - .scroll-content { - overflow-y: auto; - } -} - -.clickable { - cursor: pointer; -} - -.loading-display { - &-initial { - // display: none; - opacity: 0; - } - - &-loaded { - // display: block; - opacity: 1; - animation-name: loadingDisplayFadeIn; - animation-duration: 0.8s; - } -} - -@keyframes loadingDisplayFadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/cla-frontend-contributor-console/src/ionic/theme/table.scss b/cla-frontend-contributor-console/src/ionic/theme/table.scss deleted file mode 100644 index e32268d23..000000000 --- a/cla-frontend-contributor-console/src/ionic/theme/table.scss +++ /dev/null @@ -1,238 +0,0 @@ -// Tables -// -// ----------------- - -// Variables -// --------------------- -$table-header-font-weight: 600; -$table-header-font-color: #757575; - -$table-cell-padding: 1.6rem; -$table-condensed-cell-padding: $table-cell-padding/2; - -$table-bg: #fff; -$table-bg-accent: #f5f5f5; -$table-bg-hover: rgba(0, 0, 0, 0.12); -$table-bg-active: $table-bg-hover; -$table-border-color: #e0e0e0; - -// Baseline styles -.table { - width: 100%; - max-width: 100%; - margin-bottom: 2rem; - background-color: $table-bg; - > thead, - > tbody, - > tfoot { - > tr { - // .transition(all .3s ease); - > th, - > td { - text-align: left; - padding: $table-cell-padding; - vertical-align: top; - border-top: 0; - // .transition(all .3s ease); - } - } - } - > thead > tr > th { - font-weight: $table-header-font-weight; - color: $table-header-font-color; - vertical-align: bottom; - border-bottom: 1px solid rgba(0, 0, 0, 0.12); - } - > caption + thead, - > colgroup + thead, - > thead:first-child { - > tr:first-child { - > th, - > td { - border-top: 0; - } - } - } - > tbody + tbody { - border-top: 1px solid rgba(0, 0, 0, 0.12); - } - - // Nesting - .table { - background-color: $table-bg; - } - - // Remove border - .no-border { - border: 0; - } -} - -// Condensed table w/ half padding -.table-condensed { - > thead, - > tbody, - > tfoot { - > tr { - > th, - > td { - padding: $table-condensed-cell-padding; - } - } - } -} - -// Bordered version -// -// Add horizontal borders between columns. -.table-bordered { - border: 0; - > thead, - > tbody, - > tfoot { - > tr { - > th, - > td { - border: 0; - border-bottom: 1px solid $table-border-color; - } - } - } - > thead > tr { - > th, - > td { - border-bottom-width: 2px; - } - } -} - -// Zebra-striping -// -// Default zebra-stripe styles (alternating gray and transparent backgrounds) -.table-striped { - > tbody > tr:nth-child(odd) { - > td, - > th { - background-color: $table-bg-accent; - } - } -} - -// Hover effect -// -.table-hover { - > tbody > tr:hover { - > td, - > th { - background-color: $table-bg-hover; - } - } -} - -// Responsive tables (vertical) -// -// Wrap your tables in `.table-responsive-vertical` and we'll make them mobile friendly -// by vertical table-cell display. Only applies <768px. Everything above that will display normally. -// For correct display you must add 'data-title' to each 'td' -.table-responsive-vertical { - table { - border-collapse: collapse; - border-style: hidden; - } - tr { - background-color: #fafafa; - border: 1px solid #ddd; - border-width: 1px; - border-style: inset; - } - - @media screen and (max-width: 768px) { - // Tighten up spacing - > .table { - margin-bottom: 0; - background-color: transparent; - > thead, - > tfoot { - display: none; - } - - > tbody { - display: block; - - > tr { - display: block; - border: 1px solid $table-border-color; - border-radius: 2px; - margin-bottom: $table-cell-padding; - - > td { - background-color: $table-bg; - display: block; - vertical-align: middle; - text-align: right; - } - > td[data-title]:before { - content: attr(data-title); - float: left; - font-size: inherit; - font-weight: $table-header-font-weight; - color: $table-header-font-color; - } - } - } - } - - // Special overrides for shadows - &.shadow-z-1 { - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - > .table > tbody > tr { - border: none; - // .shadow-z-1(); - } - } - - // Special overrides for the bordered tables - > .table-bordered { - border: 0; - - // Nuke the appropriate borders so that the parent can handle them - > tbody { - > tr { - > td { - border: 0; - border-bottom: 1px solid $table-border-color; - } - > td:last-child { - border-bottom: 0; - } - } - } - } - - // Special overrides for the striped tables - > .table-striped { - > tbody > tr > td, - > tbody > tr:nth-child(odd) { - background-color: $table-bg; - } - > tbody > tr > td:nth-child(odd) { - background-color: $table-bg-accent; - } - } - - // Special overrides for hover tables - > .table-hover { - > tbody { - > tr:hover > td, - > tr:hover { - background-color: $table-bg; - } - > tr > td:hover { - background-color: $table-bg-hover; - } - } - } - } -} diff --git a/cla-frontend-contributor-console/src/ionic/theme/variables.scss b/cla-frontend-contributor-console/src/ionic/theme/variables.scss deleted file mode 100755 index 897bb4059..000000000 --- a/cla-frontend-contributor-console/src/ionic/theme/variables.scss +++ /dev/null @@ -1,65 +0,0 @@ -// Ionic Variables and Theming. For more info, please see: -// http://ionicframework.com/docs/v2/theming/ -$font-path: '../assets/fonts'; - -@import 'ionic.globals'; - -// Shared Variables -// -------------------------------------------------- -// To customize the look and feel of this app, you can override -// the Sass variables found in Ionic's source scss files. -// To view all the possible Ionic variables, see: -// http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ - -// Named Color Variables -// -------------------------------------------------- -// Named colors makes it easy to reuse colors on various components. -// It's highly recommended to change the default colors -// to match your app's branding. Ionic uses a Sass map of -// colors so you can add, rename and remove colors as needed. -// The "primary" color is the only required color in the map. - -$colors: ( - primary: #003764, - secondary: #00a0fc, - danger: #f53d3d, - light: #fafafa, - white: #ffffff, - dark: #222, - gray: gray, - green: green -); - -// App iOS Variables -// -------------------------------------------------- -// iOS only Sass variables can go here - -// App Material Design Variables -// -------------------------------------------------- -// Material Design only Sass variables can go here - -// App Windows Variables -// -------------------------------------------------- -// Windows only Sass variables can go here - -// App Theme -// -------------------------------------------------- -// Ionic apps can have different themes applied, which can -// then be future customized. This import comes last -// so that the above variables are used and Ionic's -// default are overridden. - -@import 'ionic.theme.default'; - -// Ionicons -// -------------------------------------------------- -// The premium icon font for Ionic. For more info, please see: -// http://ionicframework.com/docs/v2/ionicons/ - -@import 'ionic.ionicons'; - -// Fonts -// -------------------------------------------------- - -@import 'roboto'; -@import 'noto-sans'; diff --git a/cla-frontend-contributor-console/src/ionic/validators/calendarlink.ts b/cla-frontend-contributor-console/src/ionic/validators/calendarlink.ts deleted file mode 100644 index 062a5adfe..000000000 --- a/cla-frontend-contributor-console/src/ionic/validators/calendarlink.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright The Linux Foundation and each contributor to CommunityBridge. -// SPDX-License-Identifier: MIT - -import { FormControl } from '@angular/forms'; - -export class CalendarLinkValidator { - static isValid(control: FormControl): any { - let entered_url = control.value; - if (entered_url == null || entered_url == '') { - return null; - } - let calendar_url = 'https://calendar.google.com/calendar/embed'; - let calendar_embed = '